зеркало из https://github.com/mislav/hub.git
Merge branch 'update'
This commit is contained in:
Коммит
958169bfac
|
@ -5,6 +5,11 @@
|
|||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "bitbucket.org/kardianos/osext",
|
||||
"Comment": "null-9",
|
||||
"Rev": "364fb577de68fb646c4cb39cc0e09c887ee16376"
|
||||
},
|
||||
{
|
||||
"ImportPath": "code.google.com/p/go-netrc/netrc",
|
||||
"Comment": "null-12",
|
||||
|
@ -24,10 +29,14 @@
|
|||
"ImportPath": "github.com/howeyc/gopass",
|
||||
"Rev": "4cf66881dcc3d6f0071eb6473d95c39e460de696"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/inconshreveable/go-update",
|
||||
"Rev": "3f0466666779bd2143f368a207b0641f0ed536e8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jingweno/go-octokit/octokit",
|
||||
"Comment": "v0.4.0-27-g1dbe02b",
|
||||
"Rev": "1dbe02be6d8bd53eaf3869d97b1746ff82e2a807"
|
||||
"Comment": "v0.4.0-41-g85bc6b5",
|
||||
"Rev": "85bc6b536f8e8cb24d4db262e4dfea3d7d2d8138"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jtacoma/uritemplates",
|
||||
|
|
20
Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE
сгенерированный
поставляемый
Normal file
20
Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2012 Daniel Theophanes
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
32
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go
сгенерированный
поставляемый
Normal file
32
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,32 @@
|
|||
// 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.
|
||||
|
||||
// Extensions to the standard "os" package.
|
||||
package osext
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
// Executable returns an absolute path that can be used to
|
||||
// re-invoke the current program.
|
||||
// It may not be valid after the current program exits.
|
||||
func Executable() (string, error) {
|
||||
p, err := executable()
|
||||
return filepath.Clean(p), err
|
||||
}
|
||||
|
||||
// Returns same path as Executable, returns just the folder
|
||||
// path. Excludes the executable name.
|
||||
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()
|
||||
}
|
16
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go
сгенерированный
поставляемый
Normal file
16
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,16 @@
|
|||
// 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.
|
||||
|
||||
package osext
|
||||
|
||||
import "syscall"
|
||||
|
||||
func executable() (string, error) {
|
||||
f, err := Open("/proc/" + itoa(Getpid()) + "/text")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
return syscall.Fd2path(int(f.Fd()))
|
||||
}
|
25
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go
сгенерированный
поставляемый
Normal file
25
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,25 @@
|
|||
// 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 linux netbsd openbsd
|
||||
|
||||
package osext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func executable() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return os.Readlink("/proc/self/exe")
|
||||
case "netbsd":
|
||||
return os.Readlink("/proc/curproc/exe")
|
||||
case "openbsd":
|
||||
return os.Readlink("/proc/curproc/file")
|
||||
}
|
||||
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
||||
}
|
82
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go
сгенерированный
поставляемый
Normal file
82
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,82 @@
|
|||
// 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 freebsd
|
||||
|
||||
package osext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var startUpcwd, getwdError = os.Getwd()
|
||||
|
||||
func executable() (string, error) {
|
||||
var mib [4]int32
|
||||
switch runtime.GOOS {
|
||||
case "freebsd":
|
||||
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
|
||||
case "darwin":
|
||||
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
|
||||
}
|
||||
|
||||
n := uintptr(0)
|
||||
// get length
|
||||
_, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||
if err != 0 {
|
||||
return "", err
|
||||
}
|
||||
if n == 0 { // shouldn't happen
|
||||
return "", nil
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
_, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||
if err != 0 {
|
||||
return "", err
|
||||
}
|
||||
if n == 0 { // shouldn't happen
|
||||
return "", nil
|
||||
}
|
||||
for i, v := range buf {
|
||||
if v == 0 {
|
||||
buf = buf[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
var strpath string
|
||||
if buf[0] != '/' {
|
||||
var e error
|
||||
if strpath, e = getAbs(buf); e != nil {
|
||||
return strpath, e
|
||||
}
|
||||
} else {
|
||||
strpath = string(buf)
|
||||
}
|
||||
// darwin KERN_PROCARGS may return the path to a symlink rather than the
|
||||
// actual executable
|
||||
if runtime.GOOS == "darwin" {
|
||||
if strpath, err := filepath.EvalSymlinks(strpath); err != nil {
|
||||
return strpath, err
|
||||
}
|
||||
}
|
||||
return strpath, nil
|
||||
}
|
||||
|
||||
func getAbs(buf []byte) (string, error) {
|
||||
if getwdError != nil {
|
||||
return string(buf), getwdError
|
||||
} else {
|
||||
if buf[0] == '.' {
|
||||
buf = buf[1:]
|
||||
}
|
||||
if startUpcwd[len(startUpcwd)-1] != '/' && buf[0] != '/' {
|
||||
return startUpcwd + "/" + string(buf), nil
|
||||
}
|
||||
return startUpcwd + string(buf), nil
|
||||
}
|
||||
}
|
79
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go
сгенерированный
поставляемый
Normal file
79
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,79 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
34
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go
сгенерированный
поставляемый
Normal file
34
Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,34 @@
|
|||
// 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.
|
||||
|
||||
package osext
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel = syscall.MustLoadDLL("kernel32.dll")
|
||||
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
|
||||
)
|
||||
|
||||
// GetModuleFileName() with hModule = NULL
|
||||
func executable() (exePath string, err error) {
|
||||
return getModuleFileName()
|
||||
}
|
||||
|
||||
func getModuleFileName() (string, error) {
|
||||
var n uint32
|
||||
b := make([]uint16, syscall.MAX_PATH)
|
||||
size := uint32(len(b))
|
||||
|
||||
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
|
||||
n = uint32(r0)
|
||||
if n == 0 {
|
||||
return "", e1
|
||||
}
|
||||
return string(utf16.Decode(b[0:n])), nil
|
||||
}
|
137
Godeps/_workspace/src/github.com/inconshreveable/go-update/README
сгенерированный
поставляемый
Normal file
137
Godeps/_workspace/src/github.com/inconshreveable/go-update/README
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,137 @@
|
|||
PACKAGE DOCUMENTATION
|
||||
|
||||
package update
|
||||
import "github.com/inconshreveable/go-update"
|
||||
|
||||
Package update allows a program to "self-update", replacing its
|
||||
executable file with new bytes.
|
||||
|
||||
Package update provides the facility to create user experiences like
|
||||
auto-updating or user-approved updates which manifest as user prompts in
|
||||
commercial applications with copy similar to "Restart to being using the
|
||||
new version of X".
|
||||
|
||||
Updating your program to a new version is as easy as:
|
||||
|
||||
err := update.FromUrl("http://release.example.com/2.0/myprogram")
|
||||
if err != nil {
|
||||
fmt.Printf("Update failed: %v", err)
|
||||
}
|
||||
|
||||
The most low-level API is FromStream() which updates the current
|
||||
executable with the bytes read from an io.Reader.
|
||||
|
||||
Additional APIs are provided for common update strategies which include
|
||||
updating from a file with FromFile() and updating from the internet with
|
||||
FromUrl().
|
||||
|
||||
Using the more advaced Download.UpdateFromUrl() API gives you the
|
||||
ability to resume an interrupted download to enable large updates to
|
||||
complete even over intermittent or slow connections. This API also
|
||||
enables more fine-grained control over how the update is downloaded from
|
||||
the internet as well as access to download progress,
|
||||
|
||||
|
||||
VARIABLES
|
||||
|
||||
var (
|
||||
// Returned when the remote server indicates that no download is available
|
||||
UpdateUnavailable error
|
||||
)
|
||||
|
||||
|
||||
FUNCTIONS
|
||||
|
||||
func FromFile(filepath string) (err error)
|
||||
FromFile reads the contents of the given file and uses them to update
|
||||
the current program's executable file by calling FromStream().
|
||||
|
||||
func FromStream(newBinary io.Reader) (err error)
|
||||
FromStream reads the contents of the supplied io.Reader newBinary and
|
||||
uses them to update the current program's executable file.
|
||||
|
||||
FromStream performs the following actions to ensure a cross-platform
|
||||
safe update:
|
||||
|
||||
- Renames the current program's executable file from
|
||||
/path/to/program-name to /path/to/.program-name.old
|
||||
|
||||
- Opens the now-empty path /path/to/program-name with mode 0755 and
|
||||
copies the contents of newBinary into the file.
|
||||
|
||||
- If the copy is successful, it erases /path/to/.program.old. If this
|
||||
operation fails, no error is reported.
|
||||
|
||||
- If the copy is unsuccessful, it attempts to rename
|
||||
/path/to/.program-name.old back to /path/to/program-name. If this
|
||||
operation fails, the error is not reported in order to not mask the
|
||||
error that caused the rename recovery attempt.
|
||||
|
||||
func FromUrl(url string) error
|
||||
FromUrl downloads the contents of the given url and uses them to update
|
||||
the current program's executable file. It is a convenience function
|
||||
which is equivalent to
|
||||
|
||||
NewDownload().UpdateFromUrl(url)
|
||||
|
||||
See Download.UpdateFromUrl for more details.
|
||||
|
||||
|
||||
TYPES
|
||||
|
||||
type Download struct {
|
||||
// net/http.Client to use when downloading the update.
|
||||
// If nil, a default http.Client is used
|
||||
HttpClient *http.Client
|
||||
|
||||
// Path on the file system to dowload the update to
|
||||
// If empty, a temporary file is used.
|
||||
// After the download begins, this path will be set
|
||||
// so that the client can use it to resume aborted
|
||||
// downloads
|
||||
Path string
|
||||
|
||||
// Progress returns the percentage of the download
|
||||
// completed as an integer between 0 and 100
|
||||
Progress chan (int)
|
||||
|
||||
// HTTP Method to use in the download request. Default is "GET"
|
||||
Method string
|
||||
}
|
||||
Type Download encapsulates the necessary parameters and state needed to
|
||||
download an update from the internet. Create an instance with the
|
||||
NewDownload() factory function.
|
||||
|
||||
|
||||
func NewDownload() *Download
|
||||
NewDownload initializes a new Download object
|
||||
|
||||
|
||||
func (d *Download) UpdateFromUrl(url string) (err error)
|
||||
UpdateFromUrl downloads the given url from the internet to a file on
|
||||
disk and then calls FromStream() to update the current program's
|
||||
executable file with the contents of that file.
|
||||
|
||||
If the update is successful, the downloaded file will be erased from
|
||||
disk. Otherwise, it will remain in d.Path to allow the download to
|
||||
resume later or be skipped entirely.
|
||||
|
||||
Only HTTP/1.1 servers that implement the Range header are supported.
|
||||
|
||||
UpdateFromUrl() uses HTTP status codes to determine what action to take.
|
||||
|
||||
- The HTTP server should return 200 or 206 for the update to be
|
||||
downloaded.
|
||||
|
||||
- The HTTP server should return 204 if no update is available at this
|
||||
time. This will cause UpdateFromUrl to return the error
|
||||
UpdateUnavailable.
|
||||
|
||||
- If the HTTP server returns a 3XX redirect, it will be followed
|
||||
according to d.HttpClient's redirect policy.
|
||||
|
||||
- Any other HTTP status code will cause UpdateFromUrl to return an
|
||||
error.
|
||||
|
||||
|
||||
|
455
Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go
сгенерированный
поставляемый
Normal file
455
Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,455 @@
|
|||
/*
|
||||
Package update allows a program to "self-update", replacing its executable file
|
||||
with new bytes.
|
||||
|
||||
Package update provides the facility to create user experiences like auto-updating
|
||||
or user-approved updates which manifest as user prompts in commercial applications
|
||||
with copy similar to "Restart to being using the new version of X".
|
||||
|
||||
Updating your program to a new version is as easy as:
|
||||
|
||||
err := update.FromUrl("http://release.example.com/2.0/myprogram")
|
||||
if err != nil {
|
||||
fmt.Printf("Update failed: %v", err)
|
||||
}
|
||||
|
||||
The most low-level API is FromStream() which updates the current executable
|
||||
with the bytes read from an io.Reader.
|
||||
|
||||
Additional APIs are provided for common update strategies which include
|
||||
updating from a file with FromFile() and updating from the internet with
|
||||
FromUrl().
|
||||
|
||||
Using the more advaced Download.UpdateFromUrl() API gives you the ability
|
||||
to resume an interrupted download to enable large updates to complete even
|
||||
over intermittent or slow connections. This API also enables more fine-grained
|
||||
control over how the update is downloaded from the internet as well as access to
|
||||
download progress,
|
||||
*/
|
||||
package update
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"bitbucket.org/kardianos/osext"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type MeteredReader struct {
|
||||
rd io.ReadCloser
|
||||
totalSize int64
|
||||
progress chan int
|
||||
totalRead int64
|
||||
ticks int64
|
||||
}
|
||||
|
||||
func (m *MeteredReader) Close() error {
|
||||
return m.rd.Close()
|
||||
}
|
||||
|
||||
func (m *MeteredReader) Read(b []byte) (n int, err error) {
|
||||
chunkSize := (m.totalSize / 100) + 1
|
||||
lenB := int64(len(b))
|
||||
|
||||
var nChunk int
|
||||
for start := int64(0); start < lenB; start += int64(nChunk) {
|
||||
end := start + chunkSize
|
||||
if end > lenB {
|
||||
end = lenB
|
||||
}
|
||||
|
||||
nChunk, err = m.rd.Read(b[start:end])
|
||||
|
||||
n += nChunk
|
||||
m.totalRead += int64(nChunk)
|
||||
|
||||
if m.totalRead > (m.ticks * chunkSize) {
|
||||
m.ticks += 1
|
||||
// try to send on channel, but don't block if it's full
|
||||
select {
|
||||
case m.progress <- int(m.ticks + 1):
|
||||
default:
|
||||
}
|
||||
|
||||
// give the progress channel consumer a chance to run
|
||||
runtime.Gosched()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// We wrap the round tripper when making requests
|
||||
// because we need to add headers to the requests we make
|
||||
// even when they are requests made after a redirect
|
||||
type RoundTripper struct {
|
||||
RoundTripFn func(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return rt.RoundTripFn(r)
|
||||
}
|
||||
|
||||
// Type Download encapsulates the necessary parameters and state
|
||||
// needed to download an update from the internet. Create an instance
|
||||
// with the NewDownload() factory function.
|
||||
//
|
||||
// You may only use a Download once,
|
||||
type Download struct {
|
||||
// net/http.Client to use when downloading the update.
|
||||
// If nil, a default http.Client is used
|
||||
HttpClient *http.Client
|
||||
|
||||
// Path on the file system to dowload the update to
|
||||
// If empty, a temporary file is used.
|
||||
// After the download begins, this path will be set
|
||||
// so that the client can use it to resume aborted
|
||||
// downloads
|
||||
Path string
|
||||
|
||||
// Progress returns the percentage of the download
|
||||
// completed as an integer between 0 and 100
|
||||
Progress chan (int)
|
||||
|
||||
// HTTP Method to use in the download request. Default is "GET"
|
||||
Method string
|
||||
|
||||
// HTTP URL to issue the download request to
|
||||
Url string
|
||||
|
||||
// Set to true when the server confirms a new version is available
|
||||
// even if the updating process encounters an error later on
|
||||
Available bool
|
||||
}
|
||||
|
||||
// NewDownload initializes a new Download object
|
||||
func NewDownload(url string) *Download {
|
||||
return &Download{
|
||||
HttpClient: new(http.Client),
|
||||
Progress: make(chan int),
|
||||
Method: "GET",
|
||||
Url: url,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) {
|
||||
// create the download request
|
||||
req, err := http.NewRequest(d.Method, d.Url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// we have to add headers like this so they get used across redirects
|
||||
trans := d.HttpClient.Transport
|
||||
if trans == nil {
|
||||
trans = http.DefaultTransport
|
||||
}
|
||||
|
||||
d.HttpClient.Transport = &RoundTripper{
|
||||
RoundTripFn: func(r *http.Request) (*http.Response, error) {
|
||||
// add header for download continuation
|
||||
if offset > 0 {
|
||||
r.Header.Add("Range", fmt.Sprintf("%d-", offset))
|
||||
}
|
||||
|
||||
// ask for gzipped content so that net/http won't unzip it for us
|
||||
// and destroy the content length header we need for progress calculations
|
||||
r.Header.Add("Accept-Encoding", "gzip")
|
||||
|
||||
return trans.RoundTrip(r)
|
||||
},
|
||||
}
|
||||
|
||||
// issue the download request
|
||||
return d.HttpClient.Do(req)
|
||||
}
|
||||
|
||||
func (d *Download) Check() (available bool, err error) {
|
||||
resp, err := d.sharedHttp(0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
// ok
|
||||
case 200, 206:
|
||||
available = true
|
||||
|
||||
// no update available
|
||||
case 204:
|
||||
available = false
|
||||
|
||||
// server error
|
||||
default:
|
||||
err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get() downloads the given url from the internet to a file on disk
|
||||
// and then calls FromStream() to update the current program's executable file
|
||||
// with the contents of that file.
|
||||
//
|
||||
// If the update is successful, the downloaded file will be erased from disk.
|
||||
// Otherwise, it will remain in d.Path to allow the download to resume later
|
||||
// or be skipped entirely.
|
||||
//
|
||||
// Only HTTP/1.1 servers that implement the Range header support resuming a
|
||||
// partially completed download.
|
||||
//
|
||||
// UpdateFromUrl() uses HTTP status codes to determine what action to take.
|
||||
//
|
||||
// - The HTTP server should return 200 or 206 for the update to be downloaded.
|
||||
//
|
||||
// - The HTTP server should return 204 if no update is available at this time.
|
||||
//
|
||||
// - If the HTTP server returns a 3XX redirect, it will be followed
|
||||
// according to d.HttpClient's redirect policy.
|
||||
//
|
||||
// - Any other HTTP status code will cause UpdateFromUrl to return an error.
|
||||
func (d *Download) Get() (err error) {
|
||||
var offset int64 = 0
|
||||
var fp *os.File
|
||||
|
||||
// Close the progress channel whenever this function completes
|
||||
defer close(d.Progress)
|
||||
|
||||
// open a file where we will stream the downloaded update to
|
||||
// we do this first because if the caller specified a non-empty dlpath
|
||||
// we need to determine how large it is in order to resume the download
|
||||
if d.Path == "" {
|
||||
// no dlpath specified, use a random tempfile
|
||||
fp, err = ioutil.TempFile("", "update")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
// remember the path
|
||||
d.Path = fp.Name()
|
||||
} else {
|
||||
fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
// determine the file size so we can resume the download, if possible
|
||||
var fi os.FileInfo
|
||||
fi, err = fp.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
offset = fi.Size()
|
||||
}
|
||||
|
||||
// start downloading the file
|
||||
resp, err := d.sharedHttp(offset)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
// ok
|
||||
case 200, 206:
|
||||
d.Available = true
|
||||
|
||||
// no update available
|
||||
case 204:
|
||||
return
|
||||
|
||||
// server error
|
||||
default:
|
||||
err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine how much we have to download
|
||||
// net/http sets this to -1 when it is unknown
|
||||
clength := resp.ContentLength
|
||||
|
||||
// Read the content from the response body
|
||||
rd := resp.Body
|
||||
|
||||
// meter the rate at which we download content for
|
||||
// progress reporting if we know how much to expect
|
||||
if clength > 0 {
|
||||
rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress}
|
||||
}
|
||||
|
||||
// Decompress the content if necessary
|
||||
if resp.Header.Get("Content-Encoding") == "gzip" {
|
||||
rd, err = gzip.NewReader(rd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Download the update
|
||||
_, err = io.Copy(fp, rd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Download) GetAndUpdate() (err error, errRecover error) {
|
||||
// check before we download if this will work
|
||||
if err = SanityCheck(); err != nil {
|
||||
// keep the contract that d.Progress will close whenever Get() terminates
|
||||
close(d.Progress)
|
||||
return
|
||||
}
|
||||
|
||||
// download the update
|
||||
if err = d.Get(); err != nil || !d.Available {
|
||||
return
|
||||
}
|
||||
|
||||
// apply the update
|
||||
if err, errRecover = FromFile(d.Path); err != nil || errRecover != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// remove the temporary file
|
||||
os.Remove(d.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// FromUrl downloads the contents of the given url and uses them to update
|
||||
// the current program's executable file. It is a convenience function which is equivalent to
|
||||
//
|
||||
// NewDownload(url).GetAndUpdate()
|
||||
//
|
||||
// See Download.Get() for more details.
|
||||
func FromUrl(url string) (err error, errRecover error) {
|
||||
return NewDownload(url).GetAndUpdate()
|
||||
}
|
||||
|
||||
// FromFile reads the contents of the given file and uses them
|
||||
// to update the current program's executable file by calling FromStream().
|
||||
func FromFile(filepath string) (err error, errRecover error) {
|
||||
// open the new binary
|
||||
fp, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
// do the update
|
||||
return FromStream(fp)
|
||||
}
|
||||
|
||||
// FromStream reads the contents of the supplied io.Reader newBinary
|
||||
// and uses them to update the current program's executable file.
|
||||
//
|
||||
// FromStream performs the following actions to ensure a cross-platform safe
|
||||
// update:
|
||||
//
|
||||
// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies
|
||||
// the contents of newBinary into the file
|
||||
//
|
||||
// - Renames the current program's executable file from /path/to/program-name
|
||||
// to /path/to/.program-name.old
|
||||
//
|
||||
// - Renames /path/to/.program-name.new to /path/to/program-name
|
||||
//
|
||||
// - If the rename is successful, it erases /path/to/.program.old. If this operation
|
||||
// fails, no error is reported.
|
||||
//
|
||||
// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old
|
||||
// back to /path/to/program-name. If this operation fails, the error is not reported
|
||||
// in order to not mask the error that caused the rename recovery attempt.
|
||||
func FromStream(newBinary io.Reader) (err error, errRecover error) {
|
||||
// get the path to the executable
|
||||
thisExecPath, err := osext.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// get the directory the executable exists in
|
||||
execDir := filepath.Dir(thisExecPath)
|
||||
execName := filepath.Base(thisExecPath)
|
||||
|
||||
// Copy the contents of of newbinary to a the new executable file
|
||||
newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
|
||||
fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
_, err = io.Copy(fp, newBinary)
|
||||
|
||||
// if we don't call fp.Close(), windows won't let us move the new executable
|
||||
// because the file will still be "in use"
|
||||
fp.Close()
|
||||
|
||||
// this is where we'll move the executable to so that we can swap in the updated replacement
|
||||
oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName))
|
||||
|
||||
// delete any existing old exec file - this is necessary on Windows for two reasons:
|
||||
// 1. after a successful update, windows can't remove the .old file because the process is still running
|
||||
// 2. windows rename operations fail if the destination file already exists
|
||||
_ = os.Remove(oldExecPath)
|
||||
|
||||
// move the existing executable to a new file in the same directory
|
||||
err = os.Rename(thisExecPath, oldExecPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// move the new exectuable in to become the new program
|
||||
err = os.Rename(newExecPath, thisExecPath)
|
||||
|
||||
if err != nil {
|
||||
// copy unsuccessful
|
||||
errRecover = os.Rename(oldExecPath, thisExecPath)
|
||||
} else {
|
||||
// copy successful, remove the old binary
|
||||
_ = os.Remove(oldExecPath)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SanityCheck() attempts to determine whether an in-place executable update could
|
||||
// succeed by performing preliminary checks (to establish valid permissions, etc).
|
||||
// This helps avoid downloading updates when we know the update can't be successfully
|
||||
// applied later.
|
||||
func SanityCheck() (err error) {
|
||||
// get the path to the executable
|
||||
thisExecPath, err := osext.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// get the directory the executable exists in
|
||||
execDir := filepath.Dir(thisExecPath)
|
||||
execName := filepath.Base(thisExecPath)
|
||||
|
||||
// attempt to open a file in the executable's directory
|
||||
newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
|
||||
fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fp.Close()
|
||||
|
||||
_ = os.Remove(newExecPath)
|
||||
return
|
||||
}
|
68
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/client.go
сгенерированный
поставляемый
68
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/client.go
сгенерированный
поставляемый
|
@ -44,65 +44,49 @@ func (c *Client) NewRequest(urlStr string) (req *Request, err error) {
|
|||
}
|
||||
|
||||
func (c *Client) head(url *url.URL, output interface{}) (result *Result) {
|
||||
req, err := c.NewRequest(url.String())
|
||||
if err != nil {
|
||||
result = newResult(nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := req.Head(output)
|
||||
result = newResult(resp, err)
|
||||
|
||||
return
|
||||
return sendRequest(c, url, func(req *Request) (*Response, error) {
|
||||
return req.Head(output)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) get(url *url.URL, output interface{}) (result *Result) {
|
||||
req, err := c.NewRequest(url.String())
|
||||
if err != nil {
|
||||
result = newResult(nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := req.Get(output)
|
||||
result = newResult(resp, err)
|
||||
|
||||
return
|
||||
return sendRequest(c, url, func(req *Request) (*Response, error) {
|
||||
return req.Get(output)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) post(url *url.URL, input interface{}, output interface{}) (result *Result) {
|
||||
req, err := c.NewRequest(url.String())
|
||||
if err != nil {
|
||||
result = newResult(nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := req.Post(input, output)
|
||||
result = newResult(resp, err)
|
||||
|
||||
return
|
||||
return sendRequest(c, url, func(req *Request) (*Response, error) {
|
||||
return req.Post(input, output)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) put(url *url.URL, input interface{}, output interface{}) (result *Result) {
|
||||
req, err := c.NewRequest(url.String())
|
||||
if err != nil {
|
||||
result = newResult(nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := req.Put(input, output)
|
||||
result = newResult(resp, err)
|
||||
|
||||
return
|
||||
func (c *Client) put(url *url.URL, input interface{}, output interface{}) *Result {
|
||||
return sendRequest(c, url, func(req *Request) (*Response, error) {
|
||||
return req.Put(input, output)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) delete(url *url.URL, output interface{}) (result *Result) {
|
||||
return sendRequest(c, url, func(req *Request) (*Response, error) {
|
||||
return req.Delete(output)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) patch(url *url.URL, input interface{}, output interface{}) (result *Result) {
|
||||
return sendRequest(c, url, func(req *Request) (*Response, error) {
|
||||
return req.Patch(input, output)
|
||||
})
|
||||
}
|
||||
|
||||
func sendRequest(c *Client, url *url.URL, fn func(r *Request) (*Response, error)) (result *Result) {
|
||||
req, err := c.NewRequest(url.String())
|
||||
if err != nil {
|
||||
result = newResult(nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := req.Delete(output)
|
||||
resp, err := fn(req)
|
||||
result = newResult(resp, err)
|
||||
|
||||
return
|
||||
|
|
19
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/issues.go
сгенерированный
поставляемый
19
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/issues.go
сгенерированный
поставляемый
|
@ -31,6 +31,16 @@ func (i *IssuesService) All() (issues []Issue, result *Result) {
|
|||
return
|
||||
}
|
||||
|
||||
func (i *IssuesService) Create(params interface{}) (issue *Issue, result *Result) {
|
||||
result = i.client.post(i.URL, params, &issue)
|
||||
return
|
||||
}
|
||||
|
||||
func (i *IssuesService) Update(params interface{}) (issue *Issue, result *Result) {
|
||||
result = i.client.patch(i.URL, params, &issue)
|
||||
return
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
*hypermedia.HALResource
|
||||
|
||||
|
@ -69,3 +79,12 @@ type Issue struct {
|
|||
ClosedAt *time.Time `json:"closed_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
type IssueParams struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Milestone uint64 `json:"milestone,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
}
|
||||
|
|
46
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/issues_test.go
сгенерированный
поставляемый
46
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/issues_test.go
сгенерированный
поставляемый
|
@ -45,6 +45,52 @@ func TestIssuesService_One(t *testing.T) {
|
|||
validateIssue(t, *issue)
|
||||
}
|
||||
|
||||
func TestIssuesService_Create(t *testing.T) {
|
||||
setup()
|
||||
defer tearDown()
|
||||
|
||||
mux.HandleFunc("/repos/octocat/Hello-World/issues", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "POST")
|
||||
testBody(t, r, "{\"title\":\"title\",\"body\":\"body\"}\n")
|
||||
respondWithJSON(w, loadFixture("issue.json"))
|
||||
})
|
||||
|
||||
url, err := RepoIssuesURL.Expand(M{"owner": "octocat", "repo": "Hello-World"})
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
params := IssueParams{
|
||||
Title: "title",
|
||||
Body: "body",
|
||||
}
|
||||
issue, result := client.Issues(url).Create(params)
|
||||
|
||||
assert.T(t, !result.HasError())
|
||||
validateIssue(t, *issue)
|
||||
}
|
||||
|
||||
func TestIssuesService_Update(t *testing.T) {
|
||||
setup()
|
||||
defer tearDown()
|
||||
|
||||
mux.HandleFunc("/repos/octocat/Hello-World/issues/1347", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PATCH")
|
||||
testBody(t, r, "{\"title\":\"title\",\"body\":\"body\"}\n")
|
||||
respondWithJSON(w, loadFixture("issue.json"))
|
||||
})
|
||||
|
||||
url, err := RepoIssuesURL.Expand(M{"owner": "octocat", "repo": "Hello-World", "number": 1347})
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
params := IssueParams{
|
||||
Title: "title",
|
||||
Body: "body",
|
||||
}
|
||||
issue, result := client.Issues(url).Update(params)
|
||||
|
||||
assert.T(t, !result.HasError())
|
||||
validateIssue(t, *issue)
|
||||
}
|
||||
|
||||
func validateIssue(t *testing.T, issue Issue) {
|
||||
|
||||
assert.Equal(t, "https://api.github.com/repos/octocat/Hello-World/issues/1347", issue.URL)
|
||||
|
|
5
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/releases.go
сгенерированный
поставляемый
5
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/releases.go
сгенерированный
поставляемый
|
@ -63,6 +63,11 @@ func (r *ReleasesService) Create(params interface{}) (release *Release, result *
|
|||
return
|
||||
}
|
||||
|
||||
func (r *ReleasesService) Update(params interface{}) (release *Release, result *Result) {
|
||||
result = r.client.patch(r.URL, params, &release)
|
||||
return
|
||||
}
|
||||
|
||||
type ReleaseParams struct {
|
||||
TagName string `json:"tag_name,omitempty"`
|
||||
TargetCommitish string `json:"target_commitish,omitempty"`
|
||||
|
|
23
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/releases_test.go
сгенерированный
поставляемый
23
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/releases_test.go
сгенерированный
поставляемый
|
@ -75,3 +75,26 @@ func TestCreateRelease(t *testing.T) {
|
|||
assert.T(t, !result.HasError())
|
||||
assert.Equal(t, "v1.0.0", release.TagName)
|
||||
}
|
||||
|
||||
func TestUpdateRelease(t *testing.T) {
|
||||
setup()
|
||||
defer tearDown()
|
||||
|
||||
mux.HandleFunc("/repos/octokit/Hello-World/releases/123", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PATCH")
|
||||
testBody(t, r, "{\"tag_name\":\"v1.0.0\",\"target_commitish\":\"master\"}\n")
|
||||
respondWithJSON(w, loadFixture("create_release.json"))
|
||||
})
|
||||
|
||||
url, err := ReleasesURL.Expand(M{"owner": "octokit", "repo": "Hello-World", "id": "123"})
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
params := Release{
|
||||
TagName: "v1.0.0",
|
||||
TargetCommitish: "master",
|
||||
}
|
||||
release, result := client.Releases(url).Update(params)
|
||||
|
||||
assert.T(t, !result.HasError())
|
||||
assert.Equal(t, "v1.0.0", release.TagName)
|
||||
}
|
||||
|
|
17
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/request.go
сгенерированный
поставляемый
17
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/request.go
сгенерированный
поставляемый
|
@ -10,27 +10,32 @@ type Request struct {
|
|||
}
|
||||
|
||||
func (r *Request) Head(output interface{}) (resp *Response, err error) {
|
||||
resp, err = r.do("HEAD", nil, output)
|
||||
resp, err = r.do(sawyer.HeadMethod, nil, output)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Request) Get(output interface{}) (resp *Response, err error) {
|
||||
resp, err = r.do("GET", nil, output)
|
||||
resp, err = r.do(sawyer.GetMethod, nil, output)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Request) Post(input interface{}, output interface{}) (resp *Response, err error) {
|
||||
resp, err = r.do("POST", input, output)
|
||||
resp, err = r.do(sawyer.PostMethod, input, output)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Request) Put(input interface{}, output interface{}) (resp *Response, err error) {
|
||||
resp, err = r.do("PUT", input, output)
|
||||
resp, err = r.do(sawyer.PutMethod, input, output)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Request) Delete(output interface{}) (resp *Response, err error) {
|
||||
resp, err = r.do("DELETE", nil, output)
|
||||
resp, err = r.do(sawyer.DeleteMethod, nil, output)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Request) Patch(input interface{}, output interface{}) (resp *Response, err error) {
|
||||
resp, err = r.do(sawyer.PatchMethod, input, output)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -50,6 +55,8 @@ func (r *Request) do(method string, input interface{}, output interface{}) (resp
|
|||
r.sawyerReq.SetBody(mtype, input)
|
||||
sawyerResp = r.sawyerReq.Put()
|
||||
case sawyer.PatchMethod:
|
||||
mtype, _ := mediatype.Parse(defaultMediaType)
|
||||
r.sawyerReq.SetBody(mtype, input)
|
||||
sawyerResp = r.sawyerReq.Patch()
|
||||
case sawyer.DeleteMethod:
|
||||
sawyerResp = r.sawyerReq.Delete()
|
||||
|
|
2
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/users.go
сгенерированный
поставляемый
2
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/users.go
сгенерированный
поставляемый
|
@ -44,6 +44,7 @@ func (u *UsersService) All() (users []User, result *Result) {
|
|||
type User struct {
|
||||
*hypermedia.HALResource
|
||||
|
||||
SiteAdmin bool `json:"site_admin,omitempty"`
|
||||
Login string `json:"login,omitempty"`
|
||||
ID int `json:"id,omitempty"`
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
|
@ -57,7 +58,6 @@ type User struct {
|
|||
Hireable bool `json:"hireable,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
PublicRepos int `json:"public_repos,omitempty"`
|
||||
PublicGists int `json:"public_gists,omitempty"`
|
||||
Followers int `json:"followers,omitempty"`
|
||||
Following int `json:"following,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
|
|
8
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/users_test.go
сгенерированный
поставляемый
8
Godeps/_workspace/src/github.com/jingweno/go-octokit/octokit/users_test.go
сгенерированный
поставляемый
|
@ -25,10 +25,10 @@ func TestUsersService_GetCurrentUser(t *testing.T) {
|
|||
assert.Equal(t, "jingweno", user.Login)
|
||||
assert.Equal(t, "jingweno@gmail.com", user.Email)
|
||||
assert.Equal(t, "User", user.Type)
|
||||
assert.Equal(t, 3, user.Following)
|
||||
assert.Equal(t, 19, user.Followers)
|
||||
assert.Equal(t, 80, user.PublicRepos)
|
||||
assert.Equal(t, 25, user.PublicGists)
|
||||
assert.Equal(t, 17, user.Following)
|
||||
assert.Equal(t, 28, user.Followers)
|
||||
assert.Equal(t, 90, user.PublicRepos)
|
||||
assert.Equal(t, false, user.SiteAdmin)
|
||||
assert.Equal(t, "https://api.github.com/users/jingweno/repos", string(user.ReposURL))
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ func All() []*Command {
|
|||
all = append(all, cmdAlias)
|
||||
all = append(all, cmdVersion)
|
||||
all = append(all, cmdHelp)
|
||||
all = append(all, cmdUpdate)
|
||||
|
||||
return all
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ GitHub Commands:
|
|||
issue Manipulate issues (beta)
|
||||
|
||||
See 'git help <command>' for more information on a specific command.
|
||||
Run 'git update' to update to the latest version of gh.
|
||||
`
|
||||
|
||||
func printUsage() {
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
updater "github.com/inconshreveable/go-update"
|
||||
"github.com/jingweno/gh/github"
|
||||
"github.com/jingweno/gh/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var cmdUpdate = &Command{
|
||||
Run: update,
|
||||
Usage: "update",
|
||||
Short: "Update gh",
|
||||
Long: `Update gh to the latest version.
|
||||
|
||||
Examples:
|
||||
git update
|
||||
`,
|
||||
}
|
||||
|
||||
func update(cmd *Command, args *Args) {
|
||||
err := doUpdate()
|
||||
utils.Check(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func doUpdate() (err error) {
|
||||
client := github.NewClient(github.GitHubHost)
|
||||
releases, err := client.Releases(github.NewProject("jingweno", "gh", github.GitHubHost))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error fetching releases: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
latestRelease := releases[0]
|
||||
tagName := latestRelease.TagName
|
||||
version := strings.TrimPrefix(tagName, "v")
|
||||
if version == Version {
|
||||
err = fmt.Errorf("You're already on the latest version: %s", Version)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Updating gh to release %s...\n", version)
|
||||
downloadURL := fmt.Sprintf("https://github.com/jingweno/gh/releases/download/%s/gh_%s-snapshot_%s_%s.zip", tagName, version, runtime.GOOS, runtime.GOARCH)
|
||||
path, err := downloadFile(downloadURL)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Can't download update from %s to %s", downloadURL, path)
|
||||
return
|
||||
}
|
||||
|
||||
exec, err := unzipExecutable(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Can't unzip gh executable: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err, _ = updater.FromFile(exec)
|
||||
if err == nil {
|
||||
fmt.Println("Done!")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func unzipExecutable(path string) (exec string, err error) {
|
||||
rc, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Can't open zip file %s: %s", path, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
for _, file := range rc.File {
|
||||
if !strings.HasPrefix(file.Name, "gh") {
|
||||
continue
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
exec, err = unzipFile(file, dir)
|
||||
break
|
||||
}
|
||||
|
||||
if exec == "" && err == nil {
|
||||
err = fmt.Errorf("No gh executable is found in %s", path)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func unzipFile(file *zip.File, to string) (exec string, err error) {
|
||||
frc, err := file.Open()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Can't open zip entry %s when reading: %s", file.Name, err)
|
||||
return
|
||||
}
|
||||
defer frc.Close()
|
||||
|
||||
dest := filepath.Join(to, filepath.Base(file.Name))
|
||||
f, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
copied, err := io.Copy(f, frc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if uint32(copied) != file.UncompressedSize {
|
||||
err = fmt.Errorf("Zip entry %s is corrupted", file.Name)
|
||||
return
|
||||
}
|
||||
|
||||
exec = f.Name()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func downloadFile(url string) (path string, err error) {
|
||||
dir, err := ioutil.TempDir("", "gh-update")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Join(dir, filepath.Base(url)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
path = file.Name()
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"ConfigVersion": "0.9",
|
||||
"BuildConstraints": "linux",
|
||||
"TaskSettings": {
|
||||
"archive-tar-gz": {
|
||||
"platforms": "!linux"
|
||||
},
|
||||
"archive-zip": {
|
||||
"platforms": "linux"
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче