зеркало из https://github.com/mislav/hub.git
Merge branch 'master' into cross-compiling
This commit is contained in:
Коммит
fc08248073
|
@ -1,6 +1,9 @@
|
|||
Contributing to hub
|
||||
===================
|
||||
|
||||
This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this code.
|
||||
[code-of-conduct]: http://todogroup.org/opencodeofconduct/#Hub/opensource@github.com
|
||||
|
||||
You will need:
|
||||
|
||||
1. Go 1.4 or better
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
{
|
||||
"ImportPath": "github.com/github/hub",
|
||||
"GoVersion": "go1.4",
|
||||
"GoVersion": "go1.4.2",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "bitbucket.org/kardianos/osext",
|
||||
"Comment": "null-15",
|
||||
"Rev": "44140c5fc69ecf1102c5ef451d73cd98ef59b178"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/BurntSushi/toml",
|
||||
"Comment": "v0.1.0-9-g3883ac1",
|
||||
|
@ -26,11 +21,11 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/howeyc/gopass",
|
||||
"Rev": "62ab5a80502a82291f265e6980d72310b8f480d5"
|
||||
"Rev": "2c70fa70727c953c51695f800f25d6b44abb368e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/inconshreveable/go-update",
|
||||
"Rev": "221d034a558b4c21b0624b2a450c076913854a57"
|
||||
"Rev": "68f5725818189545231c1fd8694793d45f2fc529"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jingweno/go-sawyer",
|
||||
|
@ -41,6 +36,10 @@
|
|||
"Comment": "0.1-14-g0a85813",
|
||||
"Rev": "0a85813ecac22e3cbe916ab9480b33f2f4a06b2e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kardianos/osext",
|
||||
"Rev": "6e7f843663477789fac7c02def0d0909e969b4e5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kballard/go-shellquote",
|
||||
"Rev": "e5c918b80c17694cbc49aab32a759f9a40067f5d"
|
||||
|
@ -58,6 +57,10 @@
|
|||
"ImportPath": "github.com/kr/text",
|
||||
"Rev": "6807e777504f54ad073ecef66747de158294b639"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-colorable",
|
||||
"Rev": "d67e0b7d1797975196499f79bcc322c08b9f218b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-isatty",
|
||||
"Rev": "6152ce208cfa13d58f065348a3312b4160fb98d1"
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin linux freebsd netbsd windows
|
||||
|
||||
package osext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
oexec "os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH"
|
||||
|
||||
func TestExecPath(t *testing.T) {
|
||||
ep, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecPath failed: %v", err)
|
||||
}
|
||||
// we want fn to be of the form "dir/prog"
|
||||
dir := filepath.Dir(filepath.Dir(ep))
|
||||
fn, err := filepath.Rel(dir, ep)
|
||||
if err != nil {
|
||||
t.Fatalf("filepath.Rel: %v", err)
|
||||
}
|
||||
cmd := &oexec.Cmd{}
|
||||
// make child start with a relative program path
|
||||
cmd.Dir = dir
|
||||
cmd.Path = fn
|
||||
// forge argv[0] for child, so that we can verify we could correctly
|
||||
// get real path of the executable without influenced by argv[0].
|
||||
cmd.Args = []string{"-", "-test.run=XXXX"}
|
||||
cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)}
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("exec(self) failed: %v", err)
|
||||
}
|
||||
outs := string(out)
|
||||
if !filepath.IsAbs(outs) {
|
||||
t.Fatalf("Child returned %q, want an absolute path", out)
|
||||
}
|
||||
if !sameFile(outs, ep) {
|
||||
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
||||
}
|
||||
}
|
||||
|
||||
func sameFile(fn1, fn2 string) bool {
|
||||
fi1, err := os.Stat(fn1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fi2, err := os.Stat(fn2)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return os.SameFile(fi1, fi2)
|
||||
}
|
||||
|
||||
func init() {
|
||||
if e := os.Getenv(execPath_EnvVar); e != "" {
|
||||
// first chdir to another path
|
||||
dir := "/"
|
||||
if runtime.GOOS == "windows" {
|
||||
dir = filepath.VolumeName(".")
|
||||
}
|
||||
os.Chdir(dir)
|
||||
if ep, err := Executable(); err != nil {
|
||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, ep)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ func getPasswd(masked bool) []byte {
|
|||
}
|
||||
} else if v == 13 || v == 10 {
|
||||
break
|
||||
} else {
|
||||
} else if v != 0 {
|
||||
pass = append(pass, v)
|
||||
os.Stdout.Write(mask)
|
||||
}
|
||||
|
|
|
@ -29,12 +29,7 @@ func getch() byte {
|
|||
var n uint16
|
||||
procReadConsole.Call(uintptr(syscall.Stdin), uintptr(unsafe.Pointer(pLine)), uintptr(len(line)), uintptr(unsafe.Pointer(&n)))
|
||||
|
||||
// For some reason n returned seems to big by 2 (Null terminated maybe?)
|
||||
if n > 2 {
|
||||
n -= 2
|
||||
}
|
||||
|
||||
b := []byte(string(utf16.Decode(line[:n])))
|
||||
b := []byte(string(utf16.Decode(line)))
|
||||
|
||||
procSetConsoleMode.Call(uintptr(syscall.Stdin), uintptr(mode))
|
||||
|
||||
|
|
2
Godeps/_workspace/src/github.com/inconshreveable/go-update/check/check.go
сгенерированный
поставляемый
2
Godeps/_workspace/src/github.com/inconshreveable/go-update/check/check.go
сгенерированный
поставляемый
|
@ -10,8 +10,8 @@ import (
|
|||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/github/hub/Godeps/_workspace/src/bitbucket.org/kardianos/osext"
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/inconshreveable/go-update"
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
type Initiative string
|
||||
|
|
|
@ -113,7 +113,6 @@ while outputting a progress meter and supports resuming partial downloads.
|
|||
package update
|
||||
|
||||
import (
|
||||
"github.com/github/hub/Godeps/_workspace/src/bitbucket.org/kardianos/osext"
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
|
@ -122,12 +121,14 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/inconshreveable/go-update/download"
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/kr/binarydist"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/inconshreveable/go-update/download"
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/kardianos/osext"
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/kr/binarydist"
|
||||
)
|
||||
|
||||
// The type of a binary patch, if any. Only bsdiff is supported
|
||||
|
|
0
Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE → Godeps/_workspace/src/github.com/kardianos/osext/LICENSE
сгенерированный
поставляемый
0
Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE → Godeps/_workspace/src/github.com/kardianos/osext/LICENSE
сгенерированный
поставляемый
16
Godeps/_workspace/src/github.com/kardianos/osext/README.md
сгенерированный
поставляемый
Normal file
16
Godeps/_workspace/src/github.com/kardianos/osext/README.md
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,16 @@
|
|||
### Extensions to the "os" package.
|
||||
|
||||
## Find the current Executable and ExecutableFolder.
|
||||
|
||||
There is sometimes utility in finding the current executable file
|
||||
that is running. This can be used for upgrading the current executable
|
||||
or finding resources located relative to the executable file. Both
|
||||
working directory and the os.Args[0] value are arbitrary and cannot
|
||||
be relied on; os.Args[0] can be "faked".
|
||||
|
||||
Multi-platform and supports:
|
||||
* Linux
|
||||
* OS X
|
||||
* Windows
|
||||
* Plan 9
|
||||
* BSDs.
|
9
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go → Godeps/_workspace/src/github.com/kardianos/osext/osext.go
сгенерированный
поставляемый
9
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go → Godeps/_workspace/src/github.com/kardianos/osext/osext.go
сгенерированный
поставляемый
|
@ -16,17 +16,12 @@ func Executable() (string, error) {
|
|||
}
|
||||
|
||||
// Returns same path as Executable, returns just the folder
|
||||
// path. Excludes the executable name.
|
||||
// path. Excludes the executable name and any trailing slash.
|
||||
func ExecutableFolder() (string, error) {
|
||||
p, err := Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
folder, _ := filepath.Split(p)
|
||||
return folder, nil
|
||||
}
|
||||
|
||||
// Depricated. Same as Executable().
|
||||
func GetExePath() (exePath string, err error) {
|
||||
return Executable()
|
||||
return filepath.Dir(p), nil
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux netbsd openbsd solaris
|
||||
// +build linux netbsd openbsd solaris dragonfly
|
||||
|
||||
package osext
|
||||
|
||||
|
@ -11,15 +11,23 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func executable() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return os.Readlink("/proc/self/exe")
|
||||
const deletedTag = " (deleted)"
|
||||
execpath, err := os.Readlink("/proc/self/exe")
|
||||
if err != nil {
|
||||
return execpath, err
|
||||
}
|
||||
execpath = strings.TrimSuffix(execpath, deletedTag)
|
||||
execpath = strings.TrimPrefix(execpath, deletedTag)
|
||||
return execpath, nil
|
||||
case "netbsd":
|
||||
return os.Readlink("/proc/curproc/exe")
|
||||
case "openbsd":
|
||||
case "openbsd", "dragonfly":
|
||||
return os.Readlink("/proc/curproc/file")
|
||||
case "solaris":
|
||||
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
|
203
Godeps/_workspace/src/github.com/kardianos/osext/osext_test.go
сгенерированный
поставляемый
Normal file
203
Godeps/_workspace/src/github.com/kardianos/osext/osext_test.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,203 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin linux freebsd netbsd windows
|
||||
|
||||
package osext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
|
||||
|
||||
executableEnvValueMatch = "match"
|
||||
executableEnvValueDelete = "delete"
|
||||
)
|
||||
|
||||
func TestPrintExecutable(t *testing.T) {
|
||||
ef, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
t.Log("Executable:", ef)
|
||||
}
|
||||
func TestPrintExecutableFolder(t *testing.T) {
|
||||
ef, err := ExecutableFolder()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||
}
|
||||
t.Log("Executable Folder:", ef)
|
||||
}
|
||||
func TestExecutableFolder(t *testing.T) {
|
||||
ef, err := ExecutableFolder()
|
||||
if err != nil {
|
||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||
}
|
||||
if ef[len(ef)-1] == filepath.Separator {
|
||||
t.Fatal("ExecutableFolder ends with a trailing slash.")
|
||||
}
|
||||
}
|
||||
func TestExecutableMatch(t *testing.T) {
|
||||
ep, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
|
||||
// fullpath to be of the form "dir/prog".
|
||||
dir := filepath.Dir(filepath.Dir(ep))
|
||||
fullpath, err := filepath.Rel(dir, ep)
|
||||
if err != nil {
|
||||
t.Fatalf("filepath.Rel: %v", err)
|
||||
}
|
||||
// Make child start with a relative program path.
|
||||
// Alter argv[0] for child to verify getting real path without argv[0].
|
||||
cmd := &exec.Cmd{
|
||||
Dir: dir,
|
||||
Path: fullpath,
|
||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("exec(self) failed: %v", err)
|
||||
}
|
||||
outs := string(out)
|
||||
if !filepath.IsAbs(outs) {
|
||||
t.Fatalf("Child returned %q, want an absolute path", out)
|
||||
}
|
||||
if !sameFile(outs, ep) {
|
||||
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutableDelete(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip()
|
||||
}
|
||||
fpath, err := Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("Executable failed: %v", err)
|
||||
}
|
||||
|
||||
r, w := io.Pipe()
|
||||
stderrBuff := &bytes.Buffer{}
|
||||
stdoutBuff := &bytes.Buffer{}
|
||||
cmd := &exec.Cmd{
|
||||
Path: fpath,
|
||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
|
||||
Stdin: r,
|
||||
Stderr: stderrBuff,
|
||||
Stdout: stdoutBuff,
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("exec(self) start failed: %v", err)
|
||||
}
|
||||
|
||||
tempPath := fpath + "_copy"
|
||||
_ = os.Remove(tempPath)
|
||||
|
||||
err = copyFile(tempPath, fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("copy file failed: %v", err)
|
||||
}
|
||||
err = os.Remove(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("remove running test file failed: %v", err)
|
||||
}
|
||||
err = os.Rename(tempPath, fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("rename copy to previous name failed: %v", err)
|
||||
}
|
||||
|
||||
w.Write([]byte{0})
|
||||
w.Close()
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("exec wait failed: %v", err)
|
||||
}
|
||||
|
||||
childPath := stderrBuff.String()
|
||||
if !filepath.IsAbs(childPath) {
|
||||
t.Fatalf("Child returned %q, want an absolute path", childPath)
|
||||
}
|
||||
if !sameFile(childPath, fpath) {
|
||||
t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
|
||||
}
|
||||
}
|
||||
|
||||
func sameFile(fn1, fn2 string) bool {
|
||||
fi1, err := os.Stat(fn1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fi2, err := os.Stat(fn2)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return os.SameFile(fi1, fi2)
|
||||
}
|
||||
func copyFile(dest, src string) error {
|
||||
df, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer df.Close()
|
||||
|
||||
sf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sf.Close()
|
||||
|
||||
_, err = io.Copy(df, sf)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
env := os.Getenv(executableEnvVar)
|
||||
switch env {
|
||||
case "":
|
||||
os.Exit(m.Run())
|
||||
case executableEnvValueMatch:
|
||||
// First chdir to another path.
|
||||
dir := "/"
|
||||
if runtime.GOOS == "windows" {
|
||||
dir = filepath.VolumeName(".")
|
||||
}
|
||||
os.Chdir(dir)
|
||||
if ep, err := Executable(); err != nil {
|
||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, ep)
|
||||
}
|
||||
case executableEnvValueDelete:
|
||||
bb := make([]byte, 1)
|
||||
var err error
|
||||
n, err := os.Stdin.Read(bb)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
if n != 1 {
|
||||
fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
|
||||
os.Exit(2)
|
||||
}
|
||||
if ep, err := Executable(); err != nil {
|
||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, ep)
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
42
Godeps/_workspace/src/github.com/mattn/go-colorable/README.md
сгенерированный
поставляемый
Normal file
42
Godeps/_workspace/src/github.com/mattn/go-colorable/README.md
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,42 @@
|
|||
# go-colorable
|
||||
|
||||
Colorable writer for windows.
|
||||
|
||||
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
|
||||
This package is possible to handle escape sequence for ansi color on windows.
|
||||
|
||||
## Too Bad!
|
||||
|
||||
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png)
|
||||
|
||||
|
||||
## So Good!
|
||||
|
||||
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png)
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
logrus.SetOutput(colorable.NewColorableStdout())
|
||||
|
||||
logrus.Info("succeeded")
|
||||
logrus.Warn("not correct")
|
||||
logrus.Error("something error")
|
||||
logrus.Fatal("panic")
|
||||
```
|
||||
|
||||
You can compile above code on non-windows OSs.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-colorable
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
16
Godeps/_workspace/src/github.com/mattn/go-colorable/colorable_others.go
сгенерированный
поставляемый
Normal file
16
Godeps/_workspace/src/github.com/mattn/go-colorable/colorable_others.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,16 @@
|
|||
// +build !windows
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
594
Godeps/_workspace/src/github.com/mattn/go-colorable/colorable_windows.go
сгенерированный
поставляемый
Normal file
594
Godeps/_workspace/src/github.com/mattn/go-colorable/colorable_windows.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,594 @@
|
|||
package colorable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const (
|
||||
foregroundBlue = 0x1
|
||||
foregroundGreen = 0x2
|
||||
foregroundRed = 0x4
|
||||
foregroundIntensity = 0x8
|
||||
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
|
||||
backgroundBlue = 0x10
|
||||
backgroundGreen = 0x20
|
||||
backgroundRed = 0x40
|
||||
backgroundIntensity = 0x80
|
||||
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
|
||||
)
|
||||
|
||||
type wchar uint16
|
||||
type short int16
|
||||
type dword uint32
|
||||
type word uint16
|
||||
|
||||
type coord struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
|
||||
type smallRect struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
size coord
|
||||
cursorPosition coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize coord
|
||||
}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
out io.Writer
|
||||
handle syscall.Handle
|
||||
lastbuf bytes.Buffer
|
||||
oldattr word
|
||||
}
|
||||
|
||||
func NewColorableStdout() io.Writer {
|
||||
var csbi consoleScreenBufferInfo
|
||||
out := os.Stdout
|
||||
if !isatty.IsTerminal(out.Fd()) {
|
||||
return out
|
||||
}
|
||||
handle := syscall.Handle(out.Fd())
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
return &Writer{out: out, handle: handle, oldattr: csbi.attributes}
|
||||
}
|
||||
|
||||
func NewColorableStderr() io.Writer {
|
||||
var csbi consoleScreenBufferInfo
|
||||
out := os.Stderr
|
||||
if !isatty.IsTerminal(out.Fd()) {
|
||||
return out
|
||||
}
|
||||
handle := syscall.Handle(out.Fd())
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
return &Writer{out: out, handle: handle, oldattr: csbi.attributes}
|
||||
}
|
||||
|
||||
var color256 = map[int]int{
|
||||
0: 0x000000,
|
||||
1: 0x800000,
|
||||
2: 0x008000,
|
||||
3: 0x808000,
|
||||
4: 0x000080,
|
||||
5: 0x800080,
|
||||
6: 0x008080,
|
||||
7: 0xc0c0c0,
|
||||
8: 0x808080,
|
||||
9: 0xff0000,
|
||||
10: 0x00ff00,
|
||||
11: 0xffff00,
|
||||
12: 0x0000ff,
|
||||
13: 0xff00ff,
|
||||
14: 0x00ffff,
|
||||
15: 0xffffff,
|
||||
16: 0x000000,
|
||||
17: 0x00005f,
|
||||
18: 0x000087,
|
||||
19: 0x0000af,
|
||||
20: 0x0000d7,
|
||||
21: 0x0000ff,
|
||||
22: 0x005f00,
|
||||
23: 0x005f5f,
|
||||
24: 0x005f87,
|
||||
25: 0x005faf,
|
||||
26: 0x005fd7,
|
||||
27: 0x005fff,
|
||||
28: 0x008700,
|
||||
29: 0x00875f,
|
||||
30: 0x008787,
|
||||
31: 0x0087af,
|
||||
32: 0x0087d7,
|
||||
33: 0x0087ff,
|
||||
34: 0x00af00,
|
||||
35: 0x00af5f,
|
||||
36: 0x00af87,
|
||||
37: 0x00afaf,
|
||||
38: 0x00afd7,
|
||||
39: 0x00afff,
|
||||
40: 0x00d700,
|
||||
41: 0x00d75f,
|
||||
42: 0x00d787,
|
||||
43: 0x00d7af,
|
||||
44: 0x00d7d7,
|
||||
45: 0x00d7ff,
|
||||
46: 0x00ff00,
|
||||
47: 0x00ff5f,
|
||||
48: 0x00ff87,
|
||||
49: 0x00ffaf,
|
||||
50: 0x00ffd7,
|
||||
51: 0x00ffff,
|
||||
52: 0x5f0000,
|
||||
53: 0x5f005f,
|
||||
54: 0x5f0087,
|
||||
55: 0x5f00af,
|
||||
56: 0x5f00d7,
|
||||
57: 0x5f00ff,
|
||||
58: 0x5f5f00,
|
||||
59: 0x5f5f5f,
|
||||
60: 0x5f5f87,
|
||||
61: 0x5f5faf,
|
||||
62: 0x5f5fd7,
|
||||
63: 0x5f5fff,
|
||||
64: 0x5f8700,
|
||||
65: 0x5f875f,
|
||||
66: 0x5f8787,
|
||||
67: 0x5f87af,
|
||||
68: 0x5f87d7,
|
||||
69: 0x5f87ff,
|
||||
70: 0x5faf00,
|
||||
71: 0x5faf5f,
|
||||
72: 0x5faf87,
|
||||
73: 0x5fafaf,
|
||||
74: 0x5fafd7,
|
||||
75: 0x5fafff,
|
||||
76: 0x5fd700,
|
||||
77: 0x5fd75f,
|
||||
78: 0x5fd787,
|
||||
79: 0x5fd7af,
|
||||
80: 0x5fd7d7,
|
||||
81: 0x5fd7ff,
|
||||
82: 0x5fff00,
|
||||
83: 0x5fff5f,
|
||||
84: 0x5fff87,
|
||||
85: 0x5fffaf,
|
||||
86: 0x5fffd7,
|
||||
87: 0x5fffff,
|
||||
88: 0x870000,
|
||||
89: 0x87005f,
|
||||
90: 0x870087,
|
||||
91: 0x8700af,
|
||||
92: 0x8700d7,
|
||||
93: 0x8700ff,
|
||||
94: 0x875f00,
|
||||
95: 0x875f5f,
|
||||
96: 0x875f87,
|
||||
97: 0x875faf,
|
||||
98: 0x875fd7,
|
||||
99: 0x875fff,
|
||||
100: 0x878700,
|
||||
101: 0x87875f,
|
||||
102: 0x878787,
|
||||
103: 0x8787af,
|
||||
104: 0x8787d7,
|
||||
105: 0x8787ff,
|
||||
106: 0x87af00,
|
||||
107: 0x87af5f,
|
||||
108: 0x87af87,
|
||||
109: 0x87afaf,
|
||||
110: 0x87afd7,
|
||||
111: 0x87afff,
|
||||
112: 0x87d700,
|
||||
113: 0x87d75f,
|
||||
114: 0x87d787,
|
||||
115: 0x87d7af,
|
||||
116: 0x87d7d7,
|
||||
117: 0x87d7ff,
|
||||
118: 0x87ff00,
|
||||
119: 0x87ff5f,
|
||||
120: 0x87ff87,
|
||||
121: 0x87ffaf,
|
||||
122: 0x87ffd7,
|
||||
123: 0x87ffff,
|
||||
124: 0xaf0000,
|
||||
125: 0xaf005f,
|
||||
126: 0xaf0087,
|
||||
127: 0xaf00af,
|
||||
128: 0xaf00d7,
|
||||
129: 0xaf00ff,
|
||||
130: 0xaf5f00,
|
||||
131: 0xaf5f5f,
|
||||
132: 0xaf5f87,
|
||||
133: 0xaf5faf,
|
||||
134: 0xaf5fd7,
|
||||
135: 0xaf5fff,
|
||||
136: 0xaf8700,
|
||||
137: 0xaf875f,
|
||||
138: 0xaf8787,
|
||||
139: 0xaf87af,
|
||||
140: 0xaf87d7,
|
||||
141: 0xaf87ff,
|
||||
142: 0xafaf00,
|
||||
143: 0xafaf5f,
|
||||
144: 0xafaf87,
|
||||
145: 0xafafaf,
|
||||
146: 0xafafd7,
|
||||
147: 0xafafff,
|
||||
148: 0xafd700,
|
||||
149: 0xafd75f,
|
||||
150: 0xafd787,
|
||||
151: 0xafd7af,
|
||||
152: 0xafd7d7,
|
||||
153: 0xafd7ff,
|
||||
154: 0xafff00,
|
||||
155: 0xafff5f,
|
||||
156: 0xafff87,
|
||||
157: 0xafffaf,
|
||||
158: 0xafffd7,
|
||||
159: 0xafffff,
|
||||
160: 0xd70000,
|
||||
161: 0xd7005f,
|
||||
162: 0xd70087,
|
||||
163: 0xd700af,
|
||||
164: 0xd700d7,
|
||||
165: 0xd700ff,
|
||||
166: 0xd75f00,
|
||||
167: 0xd75f5f,
|
||||
168: 0xd75f87,
|
||||
169: 0xd75faf,
|
||||
170: 0xd75fd7,
|
||||
171: 0xd75fff,
|
||||
172: 0xd78700,
|
||||
173: 0xd7875f,
|
||||
174: 0xd78787,
|
||||
175: 0xd787af,
|
||||
176: 0xd787d7,
|
||||
177: 0xd787ff,
|
||||
178: 0xd7af00,
|
||||
179: 0xd7af5f,
|
||||
180: 0xd7af87,
|
||||
181: 0xd7afaf,
|
||||
182: 0xd7afd7,
|
||||
183: 0xd7afff,
|
||||
184: 0xd7d700,
|
||||
185: 0xd7d75f,
|
||||
186: 0xd7d787,
|
||||
187: 0xd7d7af,
|
||||
188: 0xd7d7d7,
|
||||
189: 0xd7d7ff,
|
||||
190: 0xd7ff00,
|
||||
191: 0xd7ff5f,
|
||||
192: 0xd7ff87,
|
||||
193: 0xd7ffaf,
|
||||
194: 0xd7ffd7,
|
||||
195: 0xd7ffff,
|
||||
196: 0xff0000,
|
||||
197: 0xff005f,
|
||||
198: 0xff0087,
|
||||
199: 0xff00af,
|
||||
200: 0xff00d7,
|
||||
201: 0xff00ff,
|
||||
202: 0xff5f00,
|
||||
203: 0xff5f5f,
|
||||
204: 0xff5f87,
|
||||
205: 0xff5faf,
|
||||
206: 0xff5fd7,
|
||||
207: 0xff5fff,
|
||||
208: 0xff8700,
|
||||
209: 0xff875f,
|
||||
210: 0xff8787,
|
||||
211: 0xff87af,
|
||||
212: 0xff87d7,
|
||||
213: 0xff87ff,
|
||||
214: 0xffaf00,
|
||||
215: 0xffaf5f,
|
||||
216: 0xffaf87,
|
||||
217: 0xffafaf,
|
||||
218: 0xffafd7,
|
||||
219: 0xffafff,
|
||||
220: 0xffd700,
|
||||
221: 0xffd75f,
|
||||
222: 0xffd787,
|
||||
223: 0xffd7af,
|
||||
224: 0xffd7d7,
|
||||
225: 0xffd7ff,
|
||||
226: 0xffff00,
|
||||
227: 0xffff5f,
|
||||
228: 0xffff87,
|
||||
229: 0xffffaf,
|
||||
230: 0xffffd7,
|
||||
231: 0xffffff,
|
||||
232: 0x080808,
|
||||
233: 0x121212,
|
||||
234: 0x1c1c1c,
|
||||
235: 0x262626,
|
||||
236: 0x303030,
|
||||
237: 0x3a3a3a,
|
||||
238: 0x444444,
|
||||
239: 0x4e4e4e,
|
||||
240: 0x585858,
|
||||
241: 0x626262,
|
||||
242: 0x6c6c6c,
|
||||
243: 0x767676,
|
||||
244: 0x808080,
|
||||
245: 0x8a8a8a,
|
||||
246: 0x949494,
|
||||
247: 0x9e9e9e,
|
||||
248: 0xa8a8a8,
|
||||
249: 0xb2b2b2,
|
||||
250: 0xbcbcbc,
|
||||
251: 0xc6c6c6,
|
||||
252: 0xd0d0d0,
|
||||
253: 0xdadada,
|
||||
254: 0xe4e4e4,
|
||||
255: 0xeeeeee,
|
||||
}
|
||||
|
||||
func (w *Writer) Write(data []byte) (n int, err error) {
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
er := bytes.NewBuffer(data)
|
||||
loop:
|
||||
for {
|
||||
r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
if r1 == 0 {
|
||||
break loop
|
||||
}
|
||||
|
||||
c1, _, err := er.ReadRune()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if c1 != 0x1b {
|
||||
fmt.Fprint(w.out, string(c1))
|
||||
continue
|
||||
}
|
||||
c2, _, err := er.ReadRune()
|
||||
if err != nil {
|
||||
w.lastbuf.WriteRune(c1)
|
||||
break loop
|
||||
}
|
||||
if c2 != 0x5b {
|
||||
w.lastbuf.WriteRune(c1)
|
||||
w.lastbuf.WriteRune(c2)
|
||||
continue
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var m rune
|
||||
for {
|
||||
c, _, err := er.ReadRune()
|
||||
if err != nil {
|
||||
w.lastbuf.WriteRune(c1)
|
||||
w.lastbuf.WriteRune(c2)
|
||||
w.lastbuf.Write(buf.Bytes())
|
||||
break loop
|
||||
}
|
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
|
||||
m = c
|
||||
break
|
||||
}
|
||||
buf.Write([]byte(string(c)))
|
||||
}
|
||||
|
||||
switch m {
|
||||
case 'm':
|
||||
attr := csbi.attributes
|
||||
cs := buf.String()
|
||||
if cs == "" {
|
||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr))
|
||||
continue
|
||||
}
|
||||
token := strings.Split(cs, ";")
|
||||
for i, ns := range token {
|
||||
if n, err = strconv.Atoi(ns); err == nil {
|
||||
switch {
|
||||
case n == 0 || n == 100:
|
||||
attr = w.oldattr
|
||||
case 1 <= n && n <= 5:
|
||||
attr |= foregroundIntensity
|
||||
case n == 7:
|
||||
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
|
||||
case 22 == n || n == 25 || n == 25:
|
||||
attr |= foregroundIntensity
|
||||
case n == 27:
|
||||
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
|
||||
case 30 <= n && n <= 37:
|
||||
attr = (attr & backgroundMask)
|
||||
if (n-30)&1 != 0 {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if (n-30)&2 != 0 {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if (n-30)&4 != 0 {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
case n == 38: // set foreground color.
|
||||
if i < len(token)-2 && token[i+1] == "5" {
|
||||
if n256, err := strconv.Atoi(token[i+2]); err == nil {
|
||||
if n256foreAttr == nil {
|
||||
n256setup()
|
||||
}
|
||||
attr &= backgroundMask
|
||||
attr |= n256foreAttr[n256]
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
attr = attr & (w.oldattr & backgroundMask)
|
||||
}
|
||||
case n == 39: // reset foreground color.
|
||||
attr &= backgroundMask
|
||||
attr |= w.oldattr & foregroundMask
|
||||
case 40 <= n && n <= 47:
|
||||
attr = (attr & foregroundMask)
|
||||
if (n-40)&1 != 0 {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if (n-40)&2 != 0 {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if (n-40)&4 != 0 {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
case n == 48: // set background color.
|
||||
if i < len(token)-2 && token[i+1] == "5" {
|
||||
if n256, err := strconv.Atoi(token[i+2]); err == nil {
|
||||
if n256backAttr == nil {
|
||||
n256setup()
|
||||
}
|
||||
attr &= foregroundMask
|
||||
attr |= n256backAttr[n256]
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
attr = attr & (w.oldattr & foregroundMask)
|
||||
}
|
||||
case n == 49: // reset foreground color.
|
||||
attr &= foregroundMask
|
||||
attr |= w.oldattr & backgroundMask
|
||||
}
|
||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(data) - w.lastbuf.Len(), nil
|
||||
}
|
||||
|
||||
type consoleColor struct {
|
||||
red bool
|
||||
green bool
|
||||
blue bool
|
||||
intensity bool
|
||||
}
|
||||
|
||||
func minmax3(a, b, c int) (min, max int) {
|
||||
if a < b {
|
||||
if b < c {
|
||||
return a, c
|
||||
} else if a < c {
|
||||
return a, b
|
||||
} else {
|
||||
return c, b
|
||||
}
|
||||
} else {
|
||||
if a < c {
|
||||
return b, c
|
||||
} else if b < c {
|
||||
return b, a
|
||||
} else {
|
||||
return c, a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toConsoleColor(rgb int) (c consoleColor) {
|
||||
r, g, b := (rgb&0xFF0000)>>16, (rgb&0x00FF00)>>8, rgb&0x0000FF
|
||||
min, max := minmax3(r, g, b)
|
||||
a := (min + max) / 2
|
||||
if r < 128 && g < 128 && b < 128 {
|
||||
if r >= a {
|
||||
c.red = true
|
||||
}
|
||||
if g >= a {
|
||||
c.green = true
|
||||
}
|
||||
if b >= a {
|
||||
c.blue = true
|
||||
}
|
||||
// non-intensed white is lighter than intensed black, so swap those.
|
||||
if c.red && c.green && c.blue {
|
||||
c.red, c.green, c.blue = false, false, false
|
||||
c.intensity = true
|
||||
}
|
||||
} else {
|
||||
if min < 128 {
|
||||
min = 128
|
||||
a = (min + max) / 2
|
||||
}
|
||||
if r >= a {
|
||||
c.red = true
|
||||
}
|
||||
if g >= a {
|
||||
c.green = true
|
||||
}
|
||||
if b >= a {
|
||||
c.blue = true
|
||||
}
|
||||
c.intensity = true
|
||||
// intensed black is darker than non-intensed white, so swap those.
|
||||
if !c.red && !c.green && !c.blue {
|
||||
c.red, c.green, c.blue = true, true, true
|
||||
c.intensity = false
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c consoleColor) foregroundAttr() (attr word) {
|
||||
if c.red {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if c.green {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if c.blue {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
if c.intensity {
|
||||
attr |= foregroundIntensity
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c consoleColor) backgroundAttr() (attr word) {
|
||||
if c.red {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if c.green {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if c.blue {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
if c.intensity {
|
||||
attr |= backgroundIntensity
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var n256foreAttr []word
|
||||
var n256backAttr []word
|
||||
|
||||
func n256setup() {
|
||||
n256foreAttr = make([]word, 256)
|
||||
n256backAttr = make([]word, 256)
|
||||
for i, rgb := range color256 {
|
||||
c := toConsoleColor(rgb)
|
||||
n256foreAttr[i] = c.foregroundAttr()
|
||||
n256backAttr[i] = c.backgroundAttr()
|
||||
}
|
||||
}
|
|
@ -286,6 +286,9 @@ the API and exchange it for an OAuth token, which it saves in "~/.config/hub".
|
|||
To avoid being prompted, use **GITHUB_USER** and **GITHUB_PASSWORD** environment
|
||||
variables.
|
||||
|
||||
Alternatively, you may provide **GITHUB_TOKEN**, an access token with
|
||||
**repo** permissions. This will not be written to `~/.config/hub`.
|
||||
|
||||
### HTTPS instead of git protocol
|
||||
|
||||
If you prefer the HTTPS protocol for GitHub repositories, you can set
|
||||
|
|
128
commands/args.go
128
commands/args.go
|
@ -2,12 +2,9 @@ package commands
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/github/hub/cmd"
|
||||
|
||||
flag "github.com/github/hub/Godeps/_workspace/src/github.com/ogier/pflag"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
|
@ -24,7 +21,7 @@ type Args struct {
|
|||
func (a *Args) Words() []string {
|
||||
aa := make([]string, 0)
|
||||
for _, p := range a.Params {
|
||||
if !strings.HasPrefix(p, "-") {
|
||||
if !looksLikeFlag(p) {
|
||||
aa = append(aa, p)
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +168,8 @@ func NewArgs(args []string) *Args {
|
|||
globalFlags []string
|
||||
)
|
||||
|
||||
slurpGlobalFlags(&args, &globalFlags, &noop)
|
||||
slurpGlobalFlags(&args, &globalFlags)
|
||||
noop = removeValue(&globalFlags, noopFlag)
|
||||
|
||||
if len(args) == 0 {
|
||||
params = []string{}
|
||||
|
@ -191,90 +189,42 @@ func NewArgs(args []string) *Args {
|
|||
}
|
||||
}
|
||||
|
||||
func slurpGlobalFlags(args *[]string, globalFlags *[]string, noop *bool) {
|
||||
var (
|
||||
globalFlagSet flag.FlagSet
|
||||
const (
|
||||
noopFlag = "--noop"
|
||||
versionFlag = "--version"
|
||||
helpFlag = "--help"
|
||||
configFlag = "-c"
|
||||
chdirFlag = "-C"
|
||||
flagPrefix = "-"
|
||||
)
|
||||
|
||||
configParam mapValue = make(mapValue)
|
||||
paginate bool
|
||||
noPaginate bool
|
||||
noReplaceObjects bool
|
||||
bare bool
|
||||
version bool
|
||||
help bool
|
||||
func looksLikeFlag(value string) bool {
|
||||
return strings.HasPrefix(value, flagPrefix)
|
||||
}
|
||||
|
||||
execPath string
|
||||
gitDir string
|
||||
workTree string
|
||||
)
|
||||
func slurpGlobalFlags(args *[]string, globalFlags *[]string) {
|
||||
slurpNextValue := false
|
||||
commandIndex := 0
|
||||
|
||||
globalFlagSet.BoolVarP(noop, "noop", "", false, "")
|
||||
globalFlagSet.VarP(configParam, "", "c", "")
|
||||
globalFlagSet.BoolVarP(&paginate, "paginate", "p", false, "")
|
||||
globalFlagSet.BoolVarP(&noPaginate, "no-pager", "", false, "")
|
||||
globalFlagSet.BoolVarP(&noReplaceObjects, "no-replace-objects", "", false, "")
|
||||
globalFlagSet.BoolVarP(&bare, "bare", "", false, "")
|
||||
globalFlagSet.BoolVarP(&version, "version", "", false, "")
|
||||
globalFlagSet.BoolVarP(&help, "help", "", false, "")
|
||||
|
||||
globalFlagSet.StringVarP(&execPath, "exec-path", "", "", "")
|
||||
globalFlagSet.StringVarP(&gitDir, "git-dir", "", "", "")
|
||||
globalFlagSet.StringVarP(&workTree, "work-tree", "", "", "")
|
||||
|
||||
globalFlagSet.SetOutput(ioutil.Discard)
|
||||
globalFlagSet.Init("hub", flag.ContinueOnError)
|
||||
|
||||
aa := make([]string, 0)
|
||||
err := globalFlagSet.Parse(*args)
|
||||
if err == nil {
|
||||
aa = globalFlagSet.Args()
|
||||
} else {
|
||||
aa = *args
|
||||
for i, arg := range *args {
|
||||
if slurpNextValue {
|
||||
commandIndex = i + 1
|
||||
slurpNextValue = false
|
||||
} else if arg == versionFlag || arg == helpFlag || !looksLikeFlag(arg) {
|
||||
break
|
||||
} else {
|
||||
commandIndex = i + 1
|
||||
if arg == configFlag || arg == chdirFlag {
|
||||
slurpNextValue = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// manipulate global flags
|
||||
if version {
|
||||
aa = append([]string{"version"}, aa...)
|
||||
if commandIndex > 0 {
|
||||
aa := *args
|
||||
*globalFlags = aa[0:commandIndex]
|
||||
*args = aa[commandIndex:]
|
||||
}
|
||||
|
||||
if help {
|
||||
aa = append([]string{"help"}, aa...)
|
||||
}
|
||||
|
||||
for k, v := range configParam {
|
||||
*globalFlags = append(*globalFlags, "-c")
|
||||
*globalFlags = append(*globalFlags, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
if paginate {
|
||||
*globalFlags = append(*globalFlags, "--paginate")
|
||||
}
|
||||
|
||||
if noPaginate {
|
||||
*globalFlags = append(*globalFlags, "--no-pager")
|
||||
}
|
||||
|
||||
if noReplaceObjects {
|
||||
*globalFlags = append(*globalFlags, "--no-replace-objects")
|
||||
}
|
||||
|
||||
if bare {
|
||||
*globalFlags = append(*globalFlags, "--bare")
|
||||
}
|
||||
|
||||
if execPath != "" {
|
||||
*globalFlags = append(*globalFlags, "--exec-path", execPath)
|
||||
}
|
||||
|
||||
if gitDir != "" {
|
||||
*globalFlags = append(*globalFlags, "--git-dir", gitDir)
|
||||
}
|
||||
|
||||
if workTree != "" {
|
||||
*globalFlags = append(*globalFlags, "--work-tree", workTree)
|
||||
}
|
||||
|
||||
*args = aa
|
||||
}
|
||||
|
||||
func removeItem(slice []string, index int) (newSlice []string, item string) {
|
||||
|
@ -287,3 +237,15 @@ func removeItem(slice []string, index int) (newSlice []string, item string) {
|
|||
|
||||
return newSlice, item
|
||||
}
|
||||
|
||||
func removeValue(slice *[]string, value string) (found bool) {
|
||||
aa := *slice
|
||||
for i := len(aa) - 1; i >= 0; i-- {
|
||||
arg := aa[i]
|
||||
if arg == value {
|
||||
found = true
|
||||
*slice, _ = removeItem(*slice, i)
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/bmizerany/assert"
|
||||
|
@ -20,34 +19,22 @@ func TestNewArgs(t *testing.T) {
|
|||
assert.Equal(t, "command", args.Command)
|
||||
assert.Equal(t, 1, args.ParamsSize())
|
||||
|
||||
args = NewArgs([]string{"--noop", "command", "args"})
|
||||
assert.Equal(t, "command", args.Command)
|
||||
assert.Equal(t, 1, args.ParamsSize())
|
||||
assert.T(t, args.Noop)
|
||||
|
||||
args = NewArgs([]string{"--version"})
|
||||
assert.Equal(t, "version", args.Command)
|
||||
assert.Equal(t, "--version", args.Command)
|
||||
assert.Equal(t, 0, args.ParamsSize())
|
||||
|
||||
args = NewArgs([]string{"--help"})
|
||||
assert.Equal(t, "help", args.Command)
|
||||
assert.Equal(t, "--help", args.Command)
|
||||
assert.Equal(t, 0, args.ParamsSize())
|
||||
|
||||
args = NewArgs([]string{"--noop", "--version"})
|
||||
assert.T(t, args.Noop)
|
||||
assert.Equal(t, "version", args.Command)
|
||||
|
||||
args = NewArgs([]string{"-c", "foo=bar", "--git-dir=path", "--bare"})
|
||||
assert.Equal(t, 5, len(args.GlobalFlags))
|
||||
assert.Equal(t, "-c foo=bar --bare --git-dir path", strings.Join(args.GlobalFlags, " "))
|
||||
}
|
||||
|
||||
func TestArgs_Words(t *testing.T) {
|
||||
args := NewArgs([]string{"--no-ff", "master"})
|
||||
args := NewArgs([]string{"merge", "--no-ff", "master", "-m", "message"})
|
||||
a := args.Words()
|
||||
|
||||
assert.Equal(t, 1, len(a))
|
||||
assert.Equal(t, 2, len(a))
|
||||
assert.Equal(t, "master", a[0])
|
||||
assert.Equal(t, "message", a[1])
|
||||
}
|
||||
|
||||
func TestArgs_Insert(t *testing.T) {
|
||||
|
@ -86,3 +73,35 @@ func TestArgs_Remove(t *testing.T) {
|
|||
assert.Equal(t, "2", args.FirstParam())
|
||||
assert.Equal(t, "4", args.GetParam(1))
|
||||
}
|
||||
|
||||
func TestArgs_GlobalFlags(t *testing.T) {
|
||||
args := NewArgs([]string{"-c", "key=value", "status", "-s", "-b"})
|
||||
assert.Equal(t, "status", args.Command)
|
||||
assert.Equal(t, []string{"-c", "key=value"}, args.GlobalFlags)
|
||||
assert.Equal(t, []string{"-s", "-b"}, args.Params)
|
||||
assert.Equal(t, false, args.Noop)
|
||||
}
|
||||
|
||||
func TestArgs_GlobalFlags_Noop(t *testing.T) {
|
||||
args := NewArgs([]string{"-c", "key=value", "--noop", "--literal-pathspecs", "status", "-s", "-b"})
|
||||
assert.Equal(t, "status", args.Command)
|
||||
assert.Equal(t, []string{"-c", "key=value", "--literal-pathspecs"}, args.GlobalFlags)
|
||||
assert.Equal(t, []string{"-s", "-b"}, args.Params)
|
||||
assert.Equal(t, true, args.Noop)
|
||||
}
|
||||
|
||||
func TestArgs_GlobalFlags_NoopTwice(t *testing.T) {
|
||||
args := NewArgs([]string{"--noop", "--bare", "--noop", "status"})
|
||||
assert.Equal(t, "status", args.Command)
|
||||
assert.Equal(t, []string{"--bare"}, args.GlobalFlags)
|
||||
assert.Equal(t, 0, len(args.Params))
|
||||
assert.Equal(t, true, args.Noop)
|
||||
}
|
||||
|
||||
func TestArgs_GlobalFlags_Repeated(t *testing.T) {
|
||||
args := NewArgs([]string{"-C", "mydir", "-c", "a=b", "--bare", "-c", "c=d", "-c", "e=f", "status"})
|
||||
assert.Equal(t, "status", args.Command)
|
||||
assert.Equal(t, []string{"-C", "mydir", "-c", "a=b", "--bare", "-c", "c=d", "-c", "e=f"}, args.GlobalFlags)
|
||||
assert.Equal(t, 0, len(args.Params))
|
||||
assert.Equal(t, false, args.Noop)
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ package commands
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"net/url"
|
||||
|
||||
"github.com/github/hub/github"
|
||||
"github.com/github/hub/utils"
|
||||
|
@ -26,10 +26,12 @@ With "-u", outputs the URL rather than opening the browser.
|
|||
|
||||
var (
|
||||
flagCompareURLOnly bool
|
||||
flagCompareBase string
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdCompare.Flag.BoolVarP(&flagCompareURLOnly, "url-only", "u", false, "URL only")
|
||||
cmdCompare.Flag.StringVarP(&flagCompareBase, "base", "b", "", "BASE")
|
||||
|
||||
CmdRunner.Use(cmdCompare)
|
||||
}
|
||||
|
@ -57,20 +59,33 @@ func compare(command *Command, args *Args) {
|
|||
branch, project, err = localRepo.RemoteBranchAndProject("", false)
|
||||
utils.Check(err)
|
||||
|
||||
usageHelp := func() {
|
||||
utils.Check(fmt.Errorf("Usage: hub compare [-u] [-b <BASE>] [<USER>] [[<START>...]<END>]"))
|
||||
}
|
||||
|
||||
if args.IsParamsEmpty() {
|
||||
if branch != nil && !branch.IsMaster() {
|
||||
r = branch.ShortName()
|
||||
if branch == nil ||
|
||||
(branch.IsMaster() && flagCompareBase == "") ||
|
||||
(flagCompareBase == branch.ShortName()) {
|
||||
|
||||
usageHelp()
|
||||
} else {
|
||||
err = fmt.Errorf("Usage: hub compare [USER] [<START>...]<END>")
|
||||
utils.Check(err)
|
||||
r = branch.ShortName()
|
||||
if flagCompareBase != "" {
|
||||
r = parseCompareRange(flagCompareBase + "..." + r)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r = parseCompareRange(args.RemoveParam(args.ParamsSize() - 1))
|
||||
if args.IsParamsEmpty() {
|
||||
project, err = localRepo.CurrentProject()
|
||||
utils.Check(err)
|
||||
if flagCompareBase != "" {
|
||||
usageHelp()
|
||||
} else {
|
||||
project = github.NewProject(args.RemoveParam(args.ParamsSize()-1), "", "")
|
||||
r = parseCompareRange(args.RemoveParam(args.ParamsSize() - 1))
|
||||
if args.IsParamsEmpty() {
|
||||
project, err = localRepo.CurrentProject()
|
||||
utils.Check(err)
|
||||
} else {
|
||||
project = github.NewProject(args.RemoveParam(args.ParamsSize()-1), "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ var cmdHelp = &Command{
|
|||
func init() {
|
||||
cmdHelp.Run = runHelp
|
||||
|
||||
CmdRunner.Use(cmdHelp)
|
||||
CmdRunner.Use(cmdHelp, "--help")
|
||||
}
|
||||
|
||||
func runHelp(cmd *Command, args *Args) {
|
||||
|
|
|
@ -95,8 +95,8 @@ func writeIssueTitleAndBody(project *github.Project) (string, string, error) {
|
|||
message := `
|
||||
# Creating issue for %s.
|
||||
#
|
||||
# Write a message for this issue. The first block
|
||||
# of text is the title and the rest is description.
|
||||
# Write a message for this issue. The first block of
|
||||
# text is the title and the rest is the description.
|
||||
`
|
||||
message = fmt.Sprintf(message, project.Name)
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ func transformMergeArgs(args *Args) error {
|
|||
mergeMsg := fmt.Sprintf("Merge pull request #%v from %s\n\n%s", id, mergeHead, pullRequest.Title)
|
||||
args.AppendParams(mergeHead, "-m", mergeMsg)
|
||||
|
||||
if args.IndexOfParam("--ff-only") == -1 && args.IndexOfParam("--squash") == -1 {
|
||||
if args.IndexOfParam("--ff-only") == -1 && args.IndexOfParam("--squash") == -1 && args.IndexOfParam("--ff") == -1 {
|
||||
i := args.IndexOfParam("-m")
|
||||
args.InsertParam(i, "--no-ff")
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
var cmdPullRequest = &Command{
|
||||
Run: pullRequest,
|
||||
Usage: "pull-request [-f] [-m <MESSAGE>|-F <FILE>|-i <ISSUE>|<ISSUE-URL>] [-o] [-b <BASE>] [-h <HEAD>] [-a <USER>]",
|
||||
Usage: "pull-request [-f] [-m <MESSAGE>|-F <FILE>|-i <ISSUE>|<ISSUE-URL>] [-o] [-b <BASE>] [-h <HEAD>] [-a <USER>] [-M <MILESTONE>] [-l <LABELS>]",
|
||||
Short: "Open a pull request on GitHub",
|
||||
Long: `Opens a pull request on GitHub for the project that the "origin" remote
|
||||
points to. The default head of the pull request is the current branch.
|
||||
|
@ -31,7 +31,8 @@ If instead of normal <TITLE> an issue number is given with "-i", the pull
|
|||
request will be attached to an existing GitHub issue. Alternatively, instead
|
||||
of title you can paste a full URL to an issue on GitHub.
|
||||
|
||||
You can assign the new pull request to a user with "-a <USER>".
|
||||
You can assign the new pull request to a user with "-a <USER>", add it to a
|
||||
milestone with "-M <MILESTONE>" and apply labels with "-l <LABELS>".
|
||||
`,
|
||||
}
|
||||
|
||||
|
@ -41,9 +42,11 @@ var (
|
|||
flagPullRequestIssue,
|
||||
flagPullRequestMessage,
|
||||
flagPullRequestAssignee,
|
||||
flagPullRequestLabels,
|
||||
flagPullRequestFile string
|
||||
flagPullRequestBrowse,
|
||||
flagPullRequestForce bool
|
||||
flagPullRequestMilestone uint64
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -55,6 +58,8 @@ func init() {
|
|||
cmdPullRequest.Flag.BoolVarP(&flagPullRequestForce, "force", "f", false, "FORCE")
|
||||
cmdPullRequest.Flag.StringVarP(&flagPullRequestFile, "file", "F", "", "FILE")
|
||||
cmdPullRequest.Flag.StringVarP(&flagPullRequestAssignee, "assign", "a", "", "USER")
|
||||
cmdPullRequest.Flag.Uint64VarP(&flagPullRequestMilestone, "milestone", "M", 0, "MILESTONE")
|
||||
cmdPullRequest.Flag.StringVarP(&flagPullRequestLabels, "labels", "l", "", "LABELS")
|
||||
|
||||
CmdRunner.Use(cmdPullRequest)
|
||||
}
|
||||
|
@ -215,8 +220,16 @@ func pullRequest(cmd *Command, args *Args) {
|
|||
|
||||
pullRequestURL = pr.HTMLURL
|
||||
|
||||
if flagPullRequestAssignee != "" {
|
||||
err = client.UpdateIssueAssignee(baseProject, pr.Number, flagPullRequestAssignee)
|
||||
if flagPullRequestAssignee != "" || flagPullRequestMilestone > 0 ||
|
||||
flagPullRequestLabels != "" {
|
||||
|
||||
params := octokit.IssueParams{
|
||||
Assignee: flagPullRequestAssignee,
|
||||
Milestone: flagPullRequestMilestone,
|
||||
Labels: strings.Split(flagPullRequestLabels, ","),
|
||||
}
|
||||
|
||||
err = client.UpdateIssue(baseProject, pr.Number, params)
|
||||
utils.Check(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ const pullRequestTmpl = `{{if .InitMsg}}{{.InitMsg}}
|
|||
{{.CS}} Requesting a pull to {{.Base}} from {{.Head}}
|
||||
{{.CS}}
|
||||
{{.CS}} Write a message for this pull request. The first block
|
||||
{{.CS}} of text is the title and the rest is description.{{if .HasCommitLogs}}
|
||||
{{.CS}} of text is the title and the rest is the description.{{if .HasCommitLogs}}
|
||||
{{.CS}}
|
||||
{{.CS}} Changes:
|
||||
{{.CS}}{{if .HasCommitLogs}}
|
||||
|
|
|
@ -15,7 +15,7 @@ func TestRenderPullRequestTpl(t *testing.T) {
|
|||
# Requesting a pull to base from head
|
||||
#
|
||||
# Write a message for this pull request. The first block
|
||||
# of text is the title and the rest is description.
|
||||
# of text is the title and the rest is the description.
|
||||
#
|
||||
# Changes:
|
||||
#
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
|
||||
const releaseTmpl = `{{.CS}} Creating release {{.TagName}} for {{.ProjectName}} from {{.BranchName}}
|
||||
{{.CS}}
|
||||
{{.CS}} Write a message for this release. The first block
|
||||
{{.CS}} of text is the title and the rest is description.`
|
||||
{{.CS}} Write a message for this release. The first block of
|
||||
{{.CS}} text is the title and the rest is the description.`
|
||||
|
||||
type releaseMsg struct {
|
||||
CS string
|
||||
|
|
|
@ -12,7 +12,7 @@ func TestRenderReleaseTpl(t *testing.T) {
|
|||
|
||||
expMsg := `# Creating release 1.0 for hub from master
|
||||
#
|
||||
# Write a message for this release. The first block
|
||||
# of text is the title and the rest is description.`
|
||||
# Write a message for this release. The first block of
|
||||
# text is the title and the rest is the description.`
|
||||
assert.Equal(t, expMsg, msg)
|
||||
}
|
||||
|
|
|
@ -54,8 +54,11 @@ func (r *Runner) All() map[string]*Command {
|
|||
return r.commands
|
||||
}
|
||||
|
||||
func (r *Runner) Use(command *Command) {
|
||||
func (r *Runner) Use(command *Command, aliases ...string) {
|
||||
r.commands[command.Name()] = command
|
||||
if len(aliases) > 0 {
|
||||
r.commands[aliases[0]] = command
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) Lookup(name string) *Command {
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/github/hub/github"
|
||||
"github.com/github/hub/ui"
|
||||
"github.com/github/hub/utils"
|
||||
"github.com/github/hub/version"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -27,15 +28,15 @@ const (
|
|||
var EnableAutoUpdate = false
|
||||
|
||||
func NewUpdater() *Updater {
|
||||
version := os.Getenv("HUB_VERSION")
|
||||
if version == "" {
|
||||
version = Version
|
||||
ver := os.Getenv("HUB_VERSION")
|
||||
if ver == "" {
|
||||
ver = version.Version
|
||||
}
|
||||
|
||||
timestampPath := filepath.Join(os.Getenv("HOME"), ".config", "hub-update")
|
||||
return &Updater{
|
||||
Host: github.DefaultGitHubHost(),
|
||||
CurrentVersion: version,
|
||||
CurrentVersion: ver,
|
||||
timestampPath: timestampPath,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/github/hub/git"
|
||||
"github.com/github/hub/ui"
|
||||
"github.com/github/hub/utils"
|
||||
"github.com/github/hub/version"
|
||||
)
|
||||
|
||||
var Version = "2.2.1"
|
||||
|
||||
var cmdVersion = &Command{
|
||||
Run: runVersion,
|
||||
Usage: "version",
|
||||
|
@ -19,17 +15,10 @@ var cmdVersion = &Command{
|
|||
}
|
||||
|
||||
func init() {
|
||||
CmdRunner.Use(cmdVersion)
|
||||
CmdRunner.Use(cmdVersion, "--version")
|
||||
}
|
||||
|
||||
func runVersion(cmd *Command, args *Args) {
|
||||
gitVersion, err := git.Version()
|
||||
utils.Check(err)
|
||||
|
||||
ghVersion := fmt.Sprintf("hub version %s", Version)
|
||||
|
||||
ui.Println(gitVersion)
|
||||
ui.Println(ghVersion)
|
||||
|
||||
ui.Println(version.FullVersion())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
|
|
@ -225,13 +225,13 @@ EOF
|
|||
fi
|
||||
}
|
||||
|
||||
# hub pull-request [-f] [-m <MESSAGE>|-F <FILE>|-i <ISSUE>|<ISSUE-URL>] [-b <BASE>] [-h <HEAD>]
|
||||
# hub pull-request [-f] [-m <MESSAGE>|-F <FILE>|-i <ISSUE>|<ISSUE-URL>] [-b <BASE>] [-h <HEAD>] [-a <USER>] [-M <MILESTONE>] [-l <LABELS>]
|
||||
_git_pull_request() {
|
||||
local i c=2 flags="-f -m -F -i -b -h"
|
||||
local i c=2 flags="-f -m -F -i -b -h -a -M -l"
|
||||
while [ $c -lt $cword ]; do
|
||||
i="${words[c]}"
|
||||
case "$i" in
|
||||
-m|-F|-i|-b|-h)
|
||||
-m|-F|-i|-b|-h|-a|-M|-l)
|
||||
((c++))
|
||||
flags=${flags/$i/}
|
||||
;;
|
||||
|
@ -245,7 +245,7 @@ EOF
|
|||
-i)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
-b|-h)
|
||||
-b|-h|-a|-M|-l)
|
||||
# (Doesn't seem to need this...)
|
||||
# Uncomment the following line when 'owner/repo:[TAB]' misbehaved
|
||||
#_get_comp_words_by_ref -n : cur
|
||||
|
|
|
@ -58,6 +58,9 @@ __hub_setup_zsh_fns () {
|
|||
- set1 \
|
||||
'-m[message]' \
|
||||
'-F[file]' \
|
||||
'-a[user]' \
|
||||
'-M[milestone]' \
|
||||
'-l[labels]' \
|
||||
- set2 \
|
||||
'-i[issue]:issue number:' \
|
||||
- set3 \
|
||||
|
|
|
@ -117,6 +117,44 @@ Feature: OAuth authentication
|
|||
Then the output should not contain "github.com password for mislav"
|
||||
And the file "../home/.config/hub" should contain "oauth_token: OTOKEN"
|
||||
|
||||
Scenario: Credentials from GITHUB_TOKEN
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
get('/user') {
|
||||
halt 401 unless request.env["HTTP_AUTHORIZATION"] == "token OTOKEN"
|
||||
json :login => 'mislav'
|
||||
}
|
||||
post('/user/repos') {
|
||||
halt 401 unless request.env["HTTP_AUTHORIZATION"] == "token OTOKEN"
|
||||
json :full_name => 'mislav/dotfiles'
|
||||
}
|
||||
"""
|
||||
Given $GITHUB_TOKEN is "OTOKEN"
|
||||
When I successfully run `hub create`
|
||||
Then the output should not contain "github.com password"
|
||||
And the output should not contain "github.com username"
|
||||
And the file "../home/.config/hub" should not exist
|
||||
|
||||
Scenario: Credentials from GITHUB_TOKEN override those from config file
|
||||
Given I am "mislav" on github.com with OAuth token "OTOKEN"
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
get('/user') {
|
||||
halt 401 unless request.env["HTTP_AUTHORIZATION"] == "token PTOKEN"
|
||||
json :login => 'parkr'
|
||||
}
|
||||
get('/repos/parkr/dotfiles') {
|
||||
halt 401 unless request.env["HTTP_AUTHORIZATION"] == "token PTOKEN"
|
||||
json :private => false,
|
||||
:permissions => { :push => true }
|
||||
}
|
||||
"""
|
||||
Given $GITHUB_TOKEN is "PTOKEN"
|
||||
When I successfully run `hub clone dotfiles`
|
||||
Then it should clone "git@github.com:parkr/dotfiles.git"
|
||||
And the file "../home/.config/hub" should contain "user: mislav"
|
||||
And the file "../home/.config/hub" should contain "oauth_token: OTOKEN"
|
||||
|
||||
Scenario: Wrong password
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
|
|
|
@ -18,12 +18,12 @@ Feature: bash tab-completion
|
|||
Scenario: Offers pull-request flags
|
||||
When I type "git pull-request -" and press <Tab>
|
||||
When I press <Tab> again
|
||||
Then the completion menu should offer "-F -b -f -h -i -m" unsorted
|
||||
Then the completion menu should offer "-F -b -f -h -i -m -a -M -l" unsorted
|
||||
|
||||
Scenario: Doesn't offer already used pull-request flags
|
||||
When I type "git pull-request -F myfile -h mybranch -" and press <Tab>
|
||||
When I press <Tab> again
|
||||
Then the completion menu should offer "-b -f -i -m" unsorted
|
||||
Then the completion menu should offer "-b -f -i -m -a -M -l" unsorted
|
||||
|
||||
Scenario: Browse to issues
|
||||
When I type "git browse -- i" and press <Tab>
|
||||
|
|
|
@ -23,7 +23,7 @@ Feature: hub compare
|
|||
Then the exit status should be 1
|
||||
And the stderr should contain:
|
||||
"""
|
||||
hub compare [USER] [<START>...]<END>
|
||||
Usage: hub compare [-u] [-b <BASE>] [<USER>] [[<START>...]<END>]
|
||||
"""
|
||||
|
||||
Scenario: Can't compare default branch to self
|
||||
|
@ -33,7 +33,7 @@ Feature: hub compare
|
|||
Then the exit status should be 1
|
||||
And the stderr should contain:
|
||||
"""
|
||||
hub compare [USER] [<START>...]<END>
|
||||
Usage: hub compare [-u] [-b <BASE>] [<USER>] [[<START>...]<END>]
|
||||
"""
|
||||
|
||||
Scenario: No args, has upstream branch
|
||||
|
@ -63,6 +63,41 @@ Feature: hub compare
|
|||
https://github.com/mislav/dotfiles/compare/1.0...fix\n
|
||||
"""
|
||||
|
||||
Scenario: Compare base in branch that is not master
|
||||
Given I am on the "feature" branch with upstream "origin/experimental"
|
||||
And git "push.default" is set to "upstream"
|
||||
When I successfully run `hub compare -b master`
|
||||
Then there should be no output
|
||||
And "open https://github.com/mislav/dotfiles/compare/master...experimental" should be run
|
||||
|
||||
Scenario: Compare base in master branch
|
||||
Given I am on the "master" branch with upstream "origin/master"
|
||||
And git "push.default" is set to "upstream"
|
||||
When I successfully run `hub compare -b experimental`
|
||||
Then there should be no output
|
||||
And "open https://github.com/mislav/dotfiles/compare/experimental...master" should be run
|
||||
|
||||
Scenario: Compare base with same branch as the current branch
|
||||
Given I am on the "feature" branch with upstream "origin/experimental"
|
||||
And git "push.default" is set to "upstream"
|
||||
When I run `hub compare -b experimental`
|
||||
Then "open https://github.com/mislav/dotfiles/compare/experimental...experimental" should not be run
|
||||
And the exit status should be 1
|
||||
And the stderr should contain:
|
||||
"""
|
||||
Usage: hub compare [-u] [-b <BASE>] [<USER>] [[<START>...]<END>]
|
||||
"""
|
||||
|
||||
Scenario: Compare base with parameters
|
||||
Given I am on the "master" branch with upstream "origin/master"
|
||||
When I run `hub compare -b master experimental..master`
|
||||
Then "open https://github.com/mislav/dotfiles/compare/experimental...master" should not be run
|
||||
And the exit status should be 1
|
||||
And the stderr should contain:
|
||||
"""
|
||||
Usage: hub compare [-u] [-b <BASE>] [<USER>] [[<START>...]<END>]
|
||||
"""
|
||||
|
||||
Scenario: Compare 2-dots range for tags
|
||||
When I successfully run `hub compare 1.0..fix`
|
||||
Then there should be no output
|
||||
|
|
|
@ -83,6 +83,32 @@ Feature: hub merge
|
|||
Fast-forward (no commit created; -m option ignored)
|
||||
"""
|
||||
|
||||
Scenario: Merge pull request with --ff option
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
require 'json'
|
||||
get('/repos/defunkt/hub/pulls/164') { json \
|
||||
:head => {
|
||||
:ref => "hub_merge",
|
||||
:repo => {
|
||||
:owner => { :login => "jfirebaugh" },
|
||||
:name => "hub",
|
||||
:private => false
|
||||
}
|
||||
},
|
||||
:title => "Add `hub merge` command"
|
||||
}
|
||||
"""
|
||||
And there is a commit named "jfirebaugh/hub_merge"
|
||||
When I successfully run `hub merge --ff https://github.com/defunkt/hub/pull/164`
|
||||
Then "git fetch git://github.com/jfirebaugh/hub.git +refs/heads/hub_merge:refs/remotes/jfirebaugh/hub_merge" should be run
|
||||
And "git merge --ff jfirebaugh/hub_merge -m Merge pull request #164 from jfirebaugh/hub_merge" should be run
|
||||
When I successfully run `git show -s --format=%B`
|
||||
Then the output should contain:
|
||||
"""
|
||||
Fast-forward (no commit created; -m option ignored)
|
||||
"""
|
||||
|
||||
Scenario: Merge private pull request
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
|
|
|
@ -120,7 +120,7 @@ Feature: hub pull-request
|
|||
# Requesting a pull to mislav:master from mislav:topic
|
||||
#
|
||||
# Write a message for this pull request. The first block
|
||||
# of text is the title and the rest is description.
|
||||
# of text is the title and the rest is the description.
|
||||
#
|
||||
# Changes:
|
||||
#
|
||||
|
@ -611,3 +611,35 @@ Feature: hub pull-request
|
|||
"""
|
||||
When I successfully run `hub pull-request -m hereyougo -a mislav`
|
||||
Then the output should contain exactly "the://url\n"
|
||||
|
||||
Scenario: Pull request with milestone
|
||||
Given I am on the "feature" branch with upstream "origin/feature"
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
post('/repos/mislav/coral/pulls') {
|
||||
assert :head => "mislav:feature"
|
||||
json :html_url => "the://url", :number => 1234
|
||||
}
|
||||
patch('/repos/mislav/coral/issues/1234') {
|
||||
assert :milestone => 1234
|
||||
json :html_url => "the://url"
|
||||
}
|
||||
"""
|
||||
When I successfully run `hub pull-request -m hereyougo -M 1234`
|
||||
Then the output should contain exactly "the://url\n"
|
||||
|
||||
Scenario: Pull request with labels
|
||||
Given I am on the "feature" branch with upstream "origin/feature"
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
post('/repos/mislav/coral/pulls') {
|
||||
assert :head => "mislav:feature"
|
||||
json :html_url => "the://url", :number => 1234
|
||||
}
|
||||
patch('/repos/mislav/coral/issues/1234') {
|
||||
assert :labels => ["feature", "release"]
|
||||
json :html_url => "the://url"
|
||||
}
|
||||
"""
|
||||
When I successfully run `hub pull-request -m hereyougo -l feature,release`
|
||||
Then the output should contain exactly "the://url\n"
|
||||
|
|
|
@ -46,6 +46,8 @@ Before do
|
|||
set_env 'GIT_EDITOR', 'false'
|
||||
# reset current localization settings
|
||||
set_env 'LANG', nil
|
||||
# ignore current user's token
|
||||
set_env 'GITHUB_TOKEN', nil
|
||||
|
||||
author_name = "Hub"
|
||||
author_email = "hub@test.local"
|
||||
|
|
|
@ -26,6 +26,9 @@ Feature: zsh tab-completion
|
|||
| -F | file |
|
||||
| -i | issue |
|
||||
| -f | force (skip check for local commits) |
|
||||
| -a | user |
|
||||
| -M | milestone |
|
||||
| -l | labels |
|
||||
|
||||
Scenario: Completion of fork arguments
|
||||
When I type "git fork -" and press <Tab>
|
||||
|
|
|
@ -37,6 +37,15 @@ func Dir() (string, error) {
|
|||
}
|
||||
|
||||
func HasFile(segments ...string) bool {
|
||||
// The blessed way to resolve paths within git dir since Git 2.5.0
|
||||
output, err := gitOutput("rev-parse", "-q", "--git-path", filepath.Join(segments...))
|
||||
if err == nil && output[0] != "--git-path" {
|
||||
if _, err := os.Stat(output[0]); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for older git versions
|
||||
dir, err := Dir()
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -18,7 +19,16 @@ func TestGitDir(t *testing.T) {
|
|||
|
||||
func TestGitEditor(t *testing.T) {
|
||||
repo := fixtures.SetupTestRepo()
|
||||
defer repo.TearDown()
|
||||
editor := os.Getenv("GIT_EDITOR")
|
||||
if err := os.Unsetenv("GIT_EDITOR"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
repo.TearDown()
|
||||
if err := os.Setenv("GIT_EDITOR", editor); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
SetGlobalConfig("core.editor", "foo")
|
||||
gitEditor, err := Editor()
|
||||
|
|
|
@ -428,7 +428,7 @@ func (client *Client) CreateIssue(project *Project, title, body string, labels [
|
|||
return
|
||||
}
|
||||
|
||||
func (client *Client) UpdateIssueAssignee(project *Project, issueNumber int, assignee string) (err error) {
|
||||
func (client *Client) UpdateIssue(project *Project, issueNumber int, params octokit.IssueParams) (err error) {
|
||||
url, err := octokit.RepoIssuesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name, "number": issueNumber})
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -440,9 +440,6 @@ func (client *Client) UpdateIssueAssignee(project *Project, issueNumber int, ass
|
|||
return
|
||||
}
|
||||
|
||||
params := octokit.IssueParams{
|
||||
Assignee: assignee,
|
||||
}
|
||||
_, result := api.Issues(client.requestURL(url)).Update(params)
|
||||
if result.HasError() {
|
||||
err = FormatError("updating issue", result.Err)
|
||||
|
|
|
@ -48,19 +48,55 @@ type Host struct {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
Hosts []Host `toml:"hosts"`
|
||||
Hosts []*Host `toml:"hosts"`
|
||||
}
|
||||
|
||||
func (c *Config) PromptForHost(host string) (h *Host, err error) {
|
||||
token := c.DetectToken()
|
||||
tokenFromEnv := token != ""
|
||||
|
||||
h = c.Find(host)
|
||||
if h != nil {
|
||||
return
|
||||
if tokenFromEnv {
|
||||
h.AccessToken = token
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
h = &Host{
|
||||
Host: host,
|
||||
AccessToken: token,
|
||||
Protocol: "https",
|
||||
}
|
||||
c.Hosts = append(c.Hosts, h)
|
||||
}
|
||||
|
||||
client := NewClientWithHost(h)
|
||||
|
||||
if !tokenFromEnv {
|
||||
err = c.authorizeClient(client, host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
currentUser, err := client.CurrentUser()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.User = currentUser.Login
|
||||
|
||||
if !tokenFromEnv {
|
||||
err = newConfigService().Save(configsFile(), c)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Config) authorizeClient(client *Client, host string) (err error) {
|
||||
user := c.PromptForUser(host)
|
||||
pass := c.PromptForPassword(host, user)
|
||||
|
||||
client := NewClient(host)
|
||||
var code, token string
|
||||
for {
|
||||
token, err = client.FindOrCreateToken(user, pass, code)
|
||||
|
@ -78,28 +114,17 @@ func (c *Config) PromptForHost(host string) (h *Host, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
if err == nil {
|
||||
client.Host.AccessToken = token
|
||||
}
|
||||
|
||||
client.Host.AccessToken = token
|
||||
currentUser, err := client.CurrentUser()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h = &Host{
|
||||
Host: host,
|
||||
User: currentUser.Login,
|
||||
AccessToken: token,
|
||||
Protocol: "https",
|
||||
}
|
||||
c.Hosts = append(c.Hosts, *h)
|
||||
err = newConfigService().Save(configsFile(), c)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Config) DetectToken() string {
|
||||
return os.Getenv("GITHUB_TOKEN")
|
||||
}
|
||||
|
||||
func (c *Config) PromptForUser(host string) (user string) {
|
||||
user = os.Getenv("GITHUB_USER")
|
||||
if user != "" {
|
||||
|
@ -147,7 +172,7 @@ func (c *Config) scanLine() string {
|
|||
func (c *Config) Find(host string) *Host {
|
||||
for _, h := range c.Hosts {
|
||||
if h.Host == host {
|
||||
return &h
|
||||
return h
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +183,7 @@ func (c *Config) selectHost() *Host {
|
|||
options := len(c.Hosts)
|
||||
|
||||
if options == 1 {
|
||||
return &c.Hosts[0]
|
||||
return c.Hosts[0]
|
||||
}
|
||||
|
||||
prompt := "Select host:\n"
|
||||
|
@ -174,7 +199,7 @@ func (c *Config) selectHost() *Host {
|
|||
utils.Check(fmt.Errorf("Error: must enter a number [1-%d]", options))
|
||||
}
|
||||
|
||||
return &c.Hosts[i-1]
|
||||
return c.Hosts[i-1]
|
||||
}
|
||||
|
||||
func configsFile() string {
|
||||
|
@ -186,11 +211,18 @@ func configsFile() string {
|
|||
return configsFile
|
||||
}
|
||||
|
||||
func CurrentConfig() *Config {
|
||||
c := &Config{}
|
||||
newConfigService().Load(configsFile(), c)
|
||||
var currentConfig *Config
|
||||
var configLoadedFrom = ""
|
||||
|
||||
return c
|
||||
func CurrentConfig() *Config {
|
||||
filename := configsFile()
|
||||
if configLoadedFrom != filename {
|
||||
currentConfig = &Config{}
|
||||
newConfigService().Load(filename, currentConfig)
|
||||
configLoadedFrom = filename
|
||||
}
|
||||
|
||||
return currentConfig
|
||||
}
|
||||
|
||||
func (c *Config) DefaultHost() (host *Host, err error) {
|
||||
|
@ -198,6 +230,8 @@ func (c *Config) DefaultHost() (host *Host, err error) {
|
|||
host, err = c.PromptForHost(GitHubHostEnv)
|
||||
} else if len(c.Hosts) > 0 {
|
||||
host = c.selectHost()
|
||||
// HACK: forces host to inherit GITHUB_TOKEN if applicable
|
||||
host, err = c.PromptForHost(host.Host)
|
||||
} else {
|
||||
host, err = c.PromptForHost(DefaultGitHubHost())
|
||||
}
|
||||
|
@ -210,13 +244,13 @@ func CreateTestConfigs(user, token string) *Config {
|
|||
f, _ := ioutil.TempFile("", "test-config")
|
||||
defaultConfigsFile = f.Name()
|
||||
|
||||
host := Host{
|
||||
host := &Host{
|
||||
User: "jingweno",
|
||||
AccessToken: "123",
|
||||
Host: GitHubHost,
|
||||
}
|
||||
|
||||
c := &Config{Hosts: []Host{host}}
|
||||
c := &Config{Hosts: []*Host{host}}
|
||||
err := newConfigService().Save(f.Name(), c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -38,7 +38,7 @@ func (y *yamlConfigDecoder) Decode(r io.Reader, c *Config) error {
|
|||
|
||||
for h, v := range yc {
|
||||
vv := v[0]
|
||||
host := Host{
|
||||
host := &Host{
|
||||
Host: h,
|
||||
User: vv.User,
|
||||
AccessToken: vv.OAuthToken,
|
||||
|
|
|
@ -54,19 +54,19 @@ func TestConfigService_TomlSave(t *testing.T) {
|
|||
file, _ := ioutil.TempFile("", "test-gh-config-")
|
||||
defer os.RemoveAll(file.Name())
|
||||
|
||||
host := Host{
|
||||
host := &Host{
|
||||
Host: "github.com",
|
||||
User: "jingweno",
|
||||
AccessToken: "123",
|
||||
Protocol: "https",
|
||||
}
|
||||
c := Config{Hosts: []Host{host}}
|
||||
c := &Config{Hosts: []*Host{host}}
|
||||
|
||||
cs := &configService{
|
||||
Encoder: &tomlConfigEncoder{},
|
||||
Decoder: &tomlConfigDecoder{},
|
||||
}
|
||||
err := cs.Save(file.Name(), &c)
|
||||
err := cs.Save(file.Name(), c)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
b, _ := ioutil.ReadFile(file.Name())
|
||||
|
@ -82,19 +82,19 @@ func TestConfigService_YamlSave(t *testing.T) {
|
|||
file, _ := ioutil.TempFile("", "test-gh-config-")
|
||||
defer os.RemoveAll(file.Name())
|
||||
|
||||
host := Host{
|
||||
host := &Host{
|
||||
Host: "github.com",
|
||||
User: "jingweno",
|
||||
AccessToken: "123",
|
||||
Protocol: "https",
|
||||
}
|
||||
c := Config{Hosts: []Host{host}}
|
||||
c := &Config{Hosts: []*Host{host}}
|
||||
|
||||
cs := &configService{
|
||||
Encoder: &yamlConfigEncoder{},
|
||||
Decoder: &yamlConfigDecoder{},
|
||||
}
|
||||
err := cs.Save(file.Name(), &c)
|
||||
err := cs.Save(file.Name(), c)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
b, _ := ioutil.ReadFile(file.Name())
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/github/hub/git"
|
||||
"github.com/github/hub/ui"
|
||||
"github.com/github/hub/utils"
|
||||
"github.com/github/hub/version"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -74,19 +75,31 @@ func report(reportedError error, stack string) {
|
|||
ui.Println(issue.HTMLURL)
|
||||
}
|
||||
|
||||
func reportTitleAndBody(reportedError error, stack string) (title, body string, err error) {
|
||||
message := "Crash report - %v\n\nError (%s): `%v`\n\nStack:\n\n```\n%s\n```\n\nRuntime:\n\n```\n%s\n```\n\n"
|
||||
message += `
|
||||
const crashReportTmpl = "Crash report - %v\n\n" +
|
||||
"Error (%s): `%v`\n\n" +
|
||||
"Stack:\n\n```\n%s\n```\n\n" +
|
||||
"Runtime:\n\n```\n%s\n```\n\n" +
|
||||
"Version:\n\n```\n%s\n```\n" +
|
||||
`
|
||||
# Creating crash report:
|
||||
#
|
||||
# This information will be posted as a new issue under jingweno/gh.
|
||||
# This information will be posted as a new issue under github/hub.
|
||||
# We're NOT including any information about the command that you were executing,
|
||||
# but knowing a little bit more about it would really help us to solve this problem.
|
||||
# Feel free to modify the title and the description for this issue.
|
||||
`
|
||||
|
||||
func reportTitleAndBody(reportedError error, stack string) (title, body string, err error) {
|
||||
errType := reflect.TypeOf(reportedError).String()
|
||||
message = fmt.Sprintf(message, reportedError, errType, reportedError, stack, runtimeInfo())
|
||||
message := fmt.Sprintf(
|
||||
crashReportTmpl,
|
||||
reportedError,
|
||||
errType,
|
||||
reportedError,
|
||||
stack,
|
||||
runtimeInfo(),
|
||||
version.FullVersion(),
|
||||
)
|
||||
|
||||
editor, err := NewEditor("CRASH_REPORT", "crash report", message)
|
||||
if err != nil {
|
||||
|
|
|
@ -26,7 +26,7 @@ func (h GitHubHosts) Include(host string) bool {
|
|||
func knownGitHubHosts() (hosts GitHubHosts) {
|
||||
defaultHost := DefaultGitHubHost()
|
||||
hosts = append(hosts, defaultHost)
|
||||
hosts = append(hosts, "ssh." + GitHubHost)
|
||||
hosts = append(hosts, "ssh."+GitHubHost)
|
||||
|
||||
ghHosts, _ := git.Config("hub.host")
|
||||
for _, ghHost := range strings.Split(ghHosts, "\n") {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/github/hub/ui"
|
||||
"github.com/github/hub/utils"
|
||||
)
|
||||
|
||||
|
@ -134,7 +135,7 @@ func newHttpClient(testHost string, verbose bool) *http.Client {
|
|||
},
|
||||
Verbose: verbose,
|
||||
OverrideURL: testURL,
|
||||
Out: os.Stderr,
|
||||
Out: ui.Stderr,
|
||||
Colorized: isTerminal(os.Stderr.Fd()),
|
||||
}
|
||||
return &http.Client{Transport: tr}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"github.com/github/hub/git"
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/mattn/go-isatty"
|
||||
"github.com/github/hub/git"
|
||||
)
|
||||
|
||||
func IsHttpsProtocol() bool {
|
||||
|
|
17
man/hub.1
17
man/hub.1
|
@ -1,7 +1,7 @@
|
|||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "HUB" "1" "May 2015" "GITHUB" "Hub Manual"
|
||||
.TH "HUB" "1" "September 2015" "GITHUB" "Hub Manual"
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBhub\fR \- git + hub = github
|
||||
|
@ -55,13 +55,13 @@
|
|||
\fBgit browse\fR [\fB\-u\fR] [[\fIUSER\fR\fB/\fR]\fIREPOSITORY\fR] [SUBPAGE]
|
||||
.
|
||||
.br
|
||||
\fBgit compare\fR [\fB\-u\fR] [\fIUSER\fR] [[\fISTART\fR\.\.\.]\fIEND\fR]
|
||||
\fBgit compare\fR [\fB\-u\fR] [\fB\-b\fR \fIBASE\fR] [\fIUSER\fR] [[\fISTART\fR\.\.\.]\fIEND\fR]
|
||||
.
|
||||
.br
|
||||
\fBgit fork\fR [\fB\-\-no\-remote\fR]
|
||||
.
|
||||
.br
|
||||
\fBgit pull\-request\fR [\fB\-o\fR|\fB\-\-browse\fR] [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR] [\fB\-a\fR \fIUSER\fR]:
|
||||
\fBgit pull\-request\fR [\fB\-o\fR|\fB\-\-browse\fR] [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR] [\fB\-a\fR \fIUSER\fR] [\fB\-M\fR \fIMILESTONE\fR] [\fB\-l\fR \fILABELS\fR]:
|
||||
.
|
||||
.br
|
||||
\fBgit ci\-status\fR [\fB\-v\fR] [\fICOMMIT\fR]
|
||||
|
@ -140,15 +140,15 @@ Create a new public GitHub repository from the current git repository and add re
|
|||
Open repository\'s GitHub page in the system\'s default web browser using \fBopen(1)\fR or the \fBBROWSER\fR env variable\. If the repository isn\'t specified, \fBbrowse\fR opens the page of the repository found in the current directory\. If SUBPAGE is specified, the browser will open on the specified subpage: one of "wiki", "commits", "issues" or other (the default is "tree")\. A specific commit in the default repository can be opened with \fBgit browse \-\- commit/<COMMIT>\fR\. With \fB\-u\fR, outputs the URL rather than opening the browser\.
|
||||
.
|
||||
.TP
|
||||
\fBgit compare\fR [\fB\-u\fR] [\fIUSER\fR] [[\fISTART\fR\.\.\.]\fIEND\fR]
|
||||
Open a GitHub compare view page in the system\'s default web browser\. \fISTART\fR to \fIEND\fR are branch names, tag names, or commit SHA1s specifying the range of history to compare\. If a range with two dots (\fBa\.\.b\fR) is given, it will be transformed into one with three dots\. If \fISTART\fR is omitted, GitHub will compare against the base branch (the default is "master")\. If \fIEND\fR is omitted, GitHub compare view is opened for the current branch\. With \fB\-u\fR, outputs the URL rather than opening the browser\.
|
||||
\fBgit compare\fR [\fB\-u\fR] [\fB\-b\fR \fIBASE\fR] [\fIUSER\fR] [[\fISTART\fR\.\.\.]\fIEND\fR]
|
||||
Open a GitHub compare view page in the system\'s default web browser\. \fISTART\fR to \fIEND\fR are branch names, tag names, or commit SHA1s specifying the range of history to compare\. If a range with two dots (\fBa\.\.b\fR) is given, it will be transformed into one with three dots\. If \fISTART\fR is omitted, GitHub will compare against the base branch (the default is "master")\. If \fIEND\fR is omitted, GitHub compare view is opened for the current branch\. With \fB\-u\fR, outputs the URL rather than opening the browser\. With \fB\-b\fR, uses \fIBASE\fR as base branch rather than the default branch (master)\.
|
||||
.
|
||||
.TP
|
||||
\fBgit fork\fR [\fB\-\-no\-remote\fR]
|
||||
Forks the original project (referenced by "origin" remote) on GitHub and adds a new remote for it under your username\.
|
||||
.
|
||||
.TP
|
||||
\fBgit pull\-request\fR [\fB\-o\fR|\fB\-\-browse\fR] [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR] [\fB\-a\fR \fIUSER\fR]
|
||||
\fBgit pull\-request\fR [\fB\-o\fR|\fB\-\-browse\fR] [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR] [\fB\-a\fR \fIUSER\fR] [\fB\-M\fR \fIMILESTONE\fR] [\fB\-l\fR \fILABELS\fR]
|
||||
Opens a pull request on GitHub for the project that the "origin" remote points to\. The default head of the pull request is the current branch\. Both base and head of the pull request can be explicitly given in one of the following formats: "branch", "owner:branch", "owner/repo:branch"\. This command will abort operation if it detects that the current topic branch has local commits that are not yet pushed to its upstream branch on the remote\. To skip this check, use \fB\-f\fR\.
|
||||
.
|
||||
.IP
|
||||
|
@ -158,7 +158,7 @@ Without \fIMESSAGE\fR or \fIFILE\fR, a text editor will open in which title and
|
|||
With \fB\-o\fR or \fB\-\-browse\fR, the new pull request will open in the web browser\.
|
||||
.
|
||||
.IP
|
||||
With \fB\-a\fR or \fB\-\-assign\fR, the new pull request will be assigned to \fIUSER\fR\.
|
||||
With \fB\-a\fR or \fB\-\-assign\fR, the new pull request will be assigned to \fIUSER\fR\. With \fB\-M\fR or \fB\-\-milestone\fR, it will be added to milestone with ID \fIMILESTONE\fR\. With \fB\-l\fR or \fB\-\-labels\fR, the comma\-separated list of labels will be applied\.
|
||||
.
|
||||
.IP
|
||||
Issue to pull request conversion via \fB\-i <ISSUE>\fR or \fIISSUE\-URL\fR arguments is deprecated and will likely be removed from the future versions of both hub and GitHub API\.
|
||||
|
@ -181,6 +181,9 @@ Hub will prompt for GitHub username & password the first time it needs to access
|
|||
.P
|
||||
To avoid being prompted, use \fIGITHUB_USER\fR and \fIGITHUB_PASSWORD\fR environment variables\.
|
||||
.
|
||||
.P
|
||||
Alternatively, you may provide \fIGITHUB_TOKEN\fR, an access token with \fIrepo\fR permissions\. This will not be written to \fB~/\.config/hub\fR\.
|
||||
.
|
||||
.SS "HTTPS instead of git protocol"
|
||||
If you prefer the HTTPS protocol for GitHub repositories, you can set "hub\.protocol" to "https"\. This will affect \fBclone\fR, \fBfork\fR, \fBremote add\fR and other operations that expand references to GitHub repositories as full URLs that otherwise use git and ssh protocols\.
|
||||
.
|
||||
|
|
|
@ -98,9 +98,9 @@
|
|||
|
||||
<p><code>git create</code> [<var>NAME</var>] [<code>-p</code>] [<code>-d</code> <var>DESCRIPTION</var>] [<code>-h</code> <var>HOMEPAGE</var>]<br />
|
||||
<code>git browse</code> [<code>-u</code>] [[<var>USER</var><code>/</code>]<var>REPOSITORY</var>] [SUBPAGE]<br />
|
||||
<code>git compare</code> [<code>-u</code>] [<var>USER</var>] [[<var>START</var>...]<var>END</var>]<br />
|
||||
<code>git compare</code> [<code>-u</code>] [<code>-b</code> <var>BASE</var>] [<var>USER</var>] [[<var>START</var>...]<var>END</var>]<br />
|
||||
<code>git fork</code> [<code>--no-remote</code>]<br />
|
||||
<code>git pull-request</code> [<code>-o</code>|<code>--browse</code>] [<code>-f</code>] [<code>-m</code> <var>MESSAGE</var>|<code>-F</code> <var>FILE</var>|<code>-i</code> <var>ISSUE</var>|<var>ISSUE-URL</var>] [<code>-b</code> <var>BASE</var>] [<code>-h</code> <var>HEAD</var>] [<code>-a</code> <var>USER</var>]:<br />
|
||||
<code>git pull-request</code> [<code>-o</code>|<code>--browse</code>] [<code>-f</code>] [<code>-m</code> <var>MESSAGE</var>|<code>-F</code> <var>FILE</var>|<code>-i</code> <var>ISSUE</var>|<var>ISSUE-URL</var>] [<code>-b</code> <var>BASE</var>] [<code>-h</code> <var>HEAD</var>] [<code>-a</code> <var>USER</var>] [<code>-M</code> <var>MILESTONE</var>] [<code>-l</code> <var>LABELS</var>]:<br />
|
||||
<code>git ci-status</code> [<code>-v</code>] [<var>COMMIT</var>]</p>
|
||||
|
||||
<h2 id="DESCRIPTION">DESCRIPTION</h2>
|
||||
|
@ -178,16 +178,17 @@ subpage: one of "wiki", "commits", "issues" or other (the default is
|
|||
"tree"). A specific commit in the default repository can be opened with
|
||||
<code>git browse -- commit/<COMMIT></code>.
|
||||
With <code>-u</code>, outputs the URL rather than opening the browser.</p></dd>
|
||||
<dt><code>git compare</code> [<code>-u</code>] [<var>USER</var>] [[<var>START</var>...]<var>END</var>]</dt><dd><p>Open a GitHub compare view page in the system's default web browser.
|
||||
<dt><code>git compare</code> [<code>-u</code>] [<code>-b</code> <var>BASE</var>] [<var>USER</var>] [[<var>START</var>...]<var>END</var>]</dt><dd><p>Open a GitHub compare view page in the system's default web browser.
|
||||
<var>START</var> to <var>END</var> are branch names, tag names, or commit SHA1s specifying
|
||||
the range of history to compare. If a range with two dots (<code>a..b</code>) is given,
|
||||
it will be transformed into one with three dots. If <var>START</var> is omitted,
|
||||
GitHub will compare against the base branch (the default is "master").
|
||||
If <var>END</var> is omitted, GitHub compare view is opened for the current branch.
|
||||
With <code>-u</code>, outputs the URL rather than opening the browser.</p></dd>
|
||||
With <code>-u</code>, outputs the URL rather than opening the browser. With <code>-b</code>, uses
|
||||
<var>BASE</var> as base branch rather than the default branch (master).</p></dd>
|
||||
<dt><code>git fork</code> [<code>--no-remote</code>]</dt><dd><p>Forks the original project (referenced by "origin" remote) on GitHub and
|
||||
adds a new remote for it under your username.</p></dd>
|
||||
<dt><code>git pull-request</code> [<code>-o</code>|<code>--browse</code>] [<code>-f</code>] [<code>-m</code> <var>MESSAGE</var>|<code>-F</code> <var>FILE</var>|<code>-i</code> <var>ISSUE</var>|<var>ISSUE-URL</var>] [<code>-b</code> <var>BASE</var>] [<code>-h</code> <var>HEAD</var>] [<code>-a</code> <var>USER</var>]</dt><dd><p>Opens a pull request on GitHub for the project that the "origin" remote
|
||||
<dt><code>git pull-request</code> [<code>-o</code>|<code>--browse</code>] [<code>-f</code>] [<code>-m</code> <var>MESSAGE</var>|<code>-F</code> <var>FILE</var>|<code>-i</code> <var>ISSUE</var>|<var>ISSUE-URL</var>] [<code>-b</code> <var>BASE</var>] [<code>-h</code> <var>HEAD</var>] [<code>-a</code> <var>USER</var>] [<code>-M</code> <var>MILESTONE</var>] [<code>-l</code> <var>LABELS</var>]</dt><dd><p>Opens a pull request on GitHub for the project that the "origin" remote
|
||||
points to. The default head of the pull request is the current branch.
|
||||
Both base and head of the pull request can be explicitly given in one of
|
||||
the following formats: "branch", "owner:branch", "owner/repo:branch".
|
||||
|
@ -201,7 +202,9 @@ Pull request message can also be passed via stdin with <code>-F -</code>.</p>
|
|||
|
||||
<p>With <code>-o</code> or <code>--browse</code>, the new pull request will open in the web browser.</p>
|
||||
|
||||
<p>With <code>-a</code> or <code>--assign</code>, the new pull request will be assigned to <var>USER</var>.</p>
|
||||
<p>With <code>-a</code> or <code>--assign</code>, the new pull request will be assigned to <var>USER</var>.
|
||||
With <code>-M</code> or <code>--milestone</code>, it will be added to milestone with ID <var>MILESTONE</var>.
|
||||
With <code>-l</code> or <code>--labels</code>, the comma-separated list of labels will be applied.</p>
|
||||
|
||||
<p>Issue to pull request conversion via <code>-i <ISSUE></code> or <var>ISSUE-URL</var>
|
||||
arguments is deprecated and will likely be removed from the future versions
|
||||
|
@ -224,6 +227,9 @@ the API and exchange it for an OAuth token, which it saves in "~/.config/hub".</
|
|||
<p>To avoid being prompted, use <var>GITHUB_USER</var> and <var>GITHUB_PASSWORD</var> environment
|
||||
variables.</p>
|
||||
|
||||
<p>Alternatively, you may provide <var>GITHUB_TOKEN</var>, an access token with
|
||||
<var>repo</var> permissions. This will not be written to <code>~/.config/hub</code>.</p>
|
||||
|
||||
<h3 id="HTTPS-instead-of-git-protocol">HTTPS instead of git protocol</h3>
|
||||
|
||||
<p>If you prefer the HTTPS protocol for GitHub repositories, you can set
|
||||
|
@ -481,7 +487,7 @@ $ git help hub
|
|||
|
||||
<ol class='man-decor man-foot man foot'>
|
||||
<li class='tl'>GITHUB</li>
|
||||
<li class='tc'>May 2015</li>
|
||||
<li class='tc'>September 2015</li>
|
||||
<li class='tr'>hub(1)</li>
|
||||
</ol>
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ hub(1) -- git + hub = github
|
|||
|
||||
`git create` [<NAME>] [`-p`] [`-d` <DESCRIPTION>] [`-h` <HOMEPAGE>]
|
||||
`git browse` [`-u`] [[<USER>`/`]<REPOSITORY>] [SUBPAGE]
|
||||
`git compare` [`-u`] [<USER>] [[<START>...]<END>]
|
||||
`git compare` [`-u`] [`-b` <BASE>] [<USER>] [[<START>...]<END>]
|
||||
`git fork` [`--no-remote`]
|
||||
`git pull-request` [`-o`|`--browse`] [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>] [`-a` <USER>]:
|
||||
`git pull-request` [`-o`|`--browse`] [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>] [`-a` <USER>] [`-M` <MILESTONE>] [`-l` <LABELS>]:
|
||||
`git ci-status` [`-v`] [<COMMIT>]
|
||||
|
||||
## DESCRIPTION
|
||||
|
@ -132,20 +132,21 @@ hub also adds some custom commands that are otherwise not present in git:
|
|||
`git browse -- commit/<COMMIT>`.
|
||||
With `-u`, outputs the URL rather than opening the browser.
|
||||
|
||||
* `git compare` [`-u`] [<USER>] [[<START>...]<END>]:
|
||||
* `git compare` [`-u`] [`-b` <BASE>] [<USER>] [[<START>...]<END>]:
|
||||
Open a GitHub compare view page in the system's default web browser.
|
||||
<START> to <END> are branch names, tag names, or commit SHA1s specifying
|
||||
the range of history to compare. If a range with two dots (`a..b`) is given,
|
||||
it will be transformed into one with three dots. If <START> is omitted,
|
||||
GitHub will compare against the base branch (the default is "master").
|
||||
If <END> is omitted, GitHub compare view is opened for the current branch.
|
||||
With `-u`, outputs the URL rather than opening the browser.
|
||||
With `-u`, outputs the URL rather than opening the browser. With `-b`, uses
|
||||
<BASE> as base branch rather than the default branch (master).
|
||||
|
||||
* `git fork` [`--no-remote`]:
|
||||
Forks the original project (referenced by "origin" remote) on GitHub and
|
||||
adds a new remote for it under your username.
|
||||
|
||||
* `git pull-request` [`-o`|`--browse`] [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>] [`-a` <USER>]:
|
||||
* `git pull-request` [`-o`|`--browse`] [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>] [`-a` <USER>] [`-M` <MILESTONE>] [`-l` <LABELS>]:
|
||||
Opens a pull request on GitHub for the project that the "origin" remote
|
||||
points to. The default head of the pull request is the current branch.
|
||||
Both base and head of the pull request can be explicitly given in one of
|
||||
|
@ -161,6 +162,8 @@ hub also adds some custom commands that are otherwise not present in git:
|
|||
With `-o` or `--browse`, the new pull request will open in the web browser.
|
||||
|
||||
With `-a` or `--assign`, the new pull request will be assigned to <USER>.
|
||||
With `-M` or `--milestone`, it will be added to milestone with ID <MILESTONE>.
|
||||
With `-l` or `--labels`, the comma-separated list of labels will be applied.
|
||||
|
||||
Issue to pull request conversion via `-i <ISSUE>` or <ISSUE-URL>
|
||||
arguments is deprecated and will likely be removed from the future versions
|
||||
|
|
|
@ -18,14 +18,14 @@ fi
|
|||
STATUS=0
|
||||
|
||||
if ! go version; then
|
||||
echo "You need to install Go 1.2 to build hub" >&2
|
||||
echo "You need to install Go 1.4.1 or higher to build hub" >&2
|
||||
STATUS=1
|
||||
fi
|
||||
|
||||
{ ruby --version
|
||||
bundle install --path vendor/bundle -j 4
|
||||
} || {
|
||||
echo "You need Ruby 2.1 and Bundler to run hub tests" >&2
|
||||
echo "You need Ruby 1.9 or higher and Bundler to run hub tests" >&2
|
||||
STATUS=1
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ up_to_date() {
|
|||
|
||||
build_hub() {
|
||||
setup_gopath
|
||||
[ -n "$1" ] && (up_to_date "$1" || go build -ldflags "-X github.com/github/hub/commands.Version=`./script/version`" -o "$1")
|
||||
[ -n "$1" ] && (up_to_date "$1" || go build -ldflags "-X github.com/github/hub/version.Version=`./script/version`" -o "$1")
|
||||
}
|
||||
|
||||
test_hub() {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
# Usage: script/ctags [<tags-file=.git/tags>]
|
||||
#
|
||||
# Generates tags for Go runtime files. Requires universal-ctags.
|
||||
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
-h | --help )
|
||||
sed -ne '/^#/!q;s/.\{1,2\}//;1d;p' < "$0"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
ctags -R --tag-relative -f "${1:-.git/tags}" \
|
||||
--exclude=Godeps \
|
||||
--exclude=etc \
|
||||
--exclude=script \
|
||||
--exclude=tmp \
|
||||
--exclude=vendor \
|
||||
--exclude='*_test.go' \
|
||||
.
|
9
ui/ui.go
9
ui/ui.go
|
@ -3,7 +3,8 @@ package ui
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/github/hub/Godeps/_workspace/src/github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
type UI interface {
|
||||
|
@ -13,7 +14,11 @@ type UI interface {
|
|||
Errorln(a ...interface{}) (n int, err error)
|
||||
}
|
||||
|
||||
var Default UI = Console{Stdout: os.Stdout, Stderr: os.Stderr}
|
||||
var (
|
||||
Stdout = colorable.NewColorableStdout()
|
||||
Stderr = colorable.NewColorableStderr()
|
||||
Default UI = Console{Stdout: Stdout, Stderr: Stderr}
|
||||
)
|
||||
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return Default.Printf(format, a...)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/github/hub/git"
|
||||
"github.com/github/hub/utils"
|
||||
)
|
||||
|
||||
var Version = "2.2.0"
|
||||
|
||||
func FullVersion() string {
|
||||
gitVersion, err := git.Version()
|
||||
utils.Check(err)
|
||||
return fmt.Sprintf("%s\nhub version %s", gitVersion, Version)
|
||||
}
|
Загрузка…
Ссылка в новой задаче