aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--LICENSE4
-rw-r--r--README.md14
-rw-r--r--_examples/README.md4
-rw-r--r--_examples/checkout/main.go4
-rw-r--r--_examples/clone/auth/basic/access_token/main.go40
-rw-r--r--_examples/clone/auth/basic/username_password/main.go37
-rw-r--r--_examples/commit/main.go6
-rw-r--r--_examples/log/main.go2
-rw-r--r--_examples/open/main.go2
-rw-r--r--_examples/pull/main.go2
-rw-r--r--_examples/showcase/main.go2
-rw-r--r--_examples/storage/README.md2
-rw-r--r--_examples/tag/main.go2
-rw-r--r--example_test.go47
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--plumbing/format/packfile/packfile.go56
-rw-r--r--plumbing/reference.go30
-rw-r--r--plumbing/reference_test.go25
-rw-r--r--plumbing/transport/http/common.go22
-rw-r--r--plumbing/transport/http/common_test.go37
-rw-r--r--plumbing/transport/ssh/upload_pack_test.go14
-rw-r--r--remote.go9
-rw-r--r--remote_test.go23
-rw-r--r--repository.go106
-rw-r--r--repository_test.go116
-rw-r--r--worktree_bsd.go2
-rw-r--r--worktree_unix_other.go26
29 files changed, 535 insertions, 106 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 92b7b8c..bdb5f73 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,7 +21,8 @@ This can be done easily using the [`-s`](https://github.com/git/git/blob/b2c150d
The official support channels, for both users and contributors, are:
-- GitHub [issues](https://github.com/src-d/go-git/issues)*
+- [StackOverflow go-git tag](https://stackoverflow.com/questions/tagged/go-git) for user questions.
+- GitHub [Issues](https://github.com/src-d/go-git/issues)* for bug reports and feature requests.
- Slack: #go-git room in the [source{d} Slack](https://join.slack.com/t/sourced-community/shared_invite/enQtMjc4Njk5MzEyNzM2LTFjNzY4NjEwZGEwMzRiNTM4MzRlMzQ4MmIzZjkwZmZlM2NjODUxZmJjNDI1OTcxNDAyMmZlNmFjODZlNTg0YWM)
*Before opening a new issue or submitting a new pull request, it's helpful to
diff --git a/LICENSE b/LICENSE
index 6d972e2..8aa3d85 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2017 Sourced Technologies, S.L.
+ Copyright 2018 Sourced Technologies, S.L.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
- limitations under the License. \ No newline at end of file
+ limitations under the License.
diff --git a/README.md b/README.md
index 8cdfef8..ed9306c 100644
--- a/README.md
+++ b/README.md
@@ -3,16 +3,16 @@
*go-git* is a highly extensible git implementation library written in **pure Go**.
-It can be used to manipulate git repositories at low level *(plumbing)* or high level *(porcelain)*, through an idiomatic Go API. It also supports several type of storage, such as in-memory filesystems, or custom implementations thanks to the [`Storer`](https://godoc.org/gopkg.in/src-d/go-git.v4/plumbing/storer) interface.
+It can be used to manipulate git repositories at low level *(plumbing)* or high level *(porcelain)*, through an idiomatic Go API. It also supports several types of storage, such as in-memory filesystems, or custom implementations thanks to the [`Storer`](https://godoc.org/gopkg.in/src-d/go-git.v4/plumbing/storer) interface.
-It's being actively develop since 2015 and is being use extensively by [source{d}](https://sourced.tech/) and [Keybase](https://keybase.io/blog/encrypted-git-for-everyone), and by many other libraries and tools.
+It's being actively developed since 2015 and is being used extensively by [source{d}](https://sourced.tech/) and [Keybase](https://keybase.io/blog/encrypted-git-for-everyone), and by many other libraries and tools.
Comparison with git
-------------------
*go-git* aims to be fully compatible with [git](https://github.com/git/git), all the *porcelain* operations are implemented to work exactly as *git* does.
-*git* is a humongous project with years of development by thousands of contributors, making it challenging for *go-git* implement all the features. You can find a comparison of *go-git* vs *git* in the [compatibility documentation](COMPATIBILITY.md).
+*git* is a humongous project with years of development by thousands of contributors, making it challenging for *go-git* to implement all the features. You can find a comparison of *go-git* vs *git* in the [compatibility documentation](COMPATIBILITY.md).
Installation
@@ -24,12 +24,12 @@ The recommended way to install *go-git* is:
go get -u gopkg.in/src-d/go-git.v4/...
```
-> We use [gopkg.in](http://labix.org/gopkg.in) for having a versioned API, this means that when `go get` clones the package, is the latest tag matching `v4.*` cloned and not the master branch.
+> We use [gopkg.in](http://labix.org/gopkg.in) to version the API, this means that when `go get` clones the package, it's the latest tag matching `v4.*` that is cloned and not the master branch.
Examples
--------
-> Please note that the functions `CheckIfError` and `Info` used in the examples are from the [examples package](https://github.com/src-d/go-git/blob/master/_examples/common.go#L17) just to be used in the examples.
+> Please note that the `CheckIfError` and `Info` functions used in the examples are from the [examples package](https://github.com/src-d/go-git/blob/master/_examples/common.go#L17) just to be used in the examples.
### Basic example
@@ -71,7 +71,7 @@ r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
CheckIfError(err)
-// Gets the HEAD history from HEAD, just like does:
+// Gets the HEAD history from HEAD, just like this command:
Info("git log")
// ... retrieves the branch pointed by HEAD
@@ -110,7 +110,7 @@ Date: Fri Nov 11 13:23:22 2016 +0100
...
```
-You can find this [example](_examples/log/main.go) and many others at the [examples](_examples) folder
+You can find this [example](_examples/log/main.go) and many others in the [examples](_examples) folder.
Contribute
----------
diff --git a/_examples/README.md b/_examples/README.md
index 26639b1..cf9c2d3 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -6,6 +6,10 @@ Here you can find a list of annotated _go-git_ examples:
- [showcase](showcase/main.go) - A small showcase of the capabilities of _go-git_
- [open](open/main.go) - Opening a existing repository cloned by _git_
- [clone](clone/main.go) - Cloning a repository
+ - [username and password](clone/auth/basic/username_password/main.go) - Cloning a repository
+ using a username and password
+ - [personal access token](clone/auth/basic/access_token/main.go) - Cloning
+ a repository using a GitHub personal access token
- [commit](commit/main.go) - Commit changes to the current branch to an existent repository
- [push](push/main.go) - Push repository to default remote (origin)
- [pull](pull/main.go) - Pull changes from a remote repository
diff --git a/_examples/checkout/main.go b/_examples/checkout/main.go
index 2c54550..5969eb4 100644
--- a/_examples/checkout/main.go
+++ b/_examples/checkout/main.go
@@ -38,8 +38,8 @@ func main() {
})
CheckIfError(err)
- // ... retrieving the commit being pointed by HEAD, it's shows that the
- // repository is poiting to the giving commit in detached mode
+ // ... retrieving the commit being pointed by HEAD, it shows that the
+ // repository is pointing to the giving commit in detached mode
Info("git show-ref --head HEAD")
ref, err = r.Head()
CheckIfError(err)
diff --git a/_examples/clone/auth/basic/access_token/main.go b/_examples/clone/auth/basic/access_token/main.go
new file mode 100644
index 0000000..7f6d121
--- /dev/null
+++ b/_examples/clone/auth/basic/access_token/main.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ git "gopkg.in/src-d/go-git.v4"
+ . "gopkg.in/src-d/go-git.v4/_examples"
+ "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
+)
+
+func main() {
+ CheckArgs("<url>", "<directory>", "<github_access_token>")
+ url, directory, token := os.Args[1], os.Args[2], os.Args[3]
+
+ // Clone the given repository to the given directory
+ Info("git clone %s %s", url, directory)
+
+ r, err := git.PlainClone(directory, false, &git.CloneOptions{
+ // The intended use of a GitHub personal access token is in replace of your password
+ // because access tokens can easily be revoked.
+ // https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
+ Auth: &http.BasicAuth{
+ Username: "abc123", // yes, this can be anything except an empty string
+ Password: token,
+ },
+ URL: url,
+ Progress: os.Stdout,
+ })
+ CheckIfError(err)
+
+ // ... retrieving the branch being pointed by HEAD
+ ref, err := r.Head()
+ CheckIfError(err)
+ // ... retrieving the commit object
+ commit, err := r.CommitObject(ref.Hash())
+ CheckIfError(err)
+
+ fmt.Println(commit)
+}
diff --git a/_examples/clone/auth/basic/username_password/main.go b/_examples/clone/auth/basic/username_password/main.go
new file mode 100644
index 0000000..754558c
--- /dev/null
+++ b/_examples/clone/auth/basic/username_password/main.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ git "gopkg.in/src-d/go-git.v4"
+ . "gopkg.in/src-d/go-git.v4/_examples"
+ "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
+)
+
+func main() {
+ CheckArgs("<url>", "<directory>", "<github_username>", "<github_password>")
+ url, directory, username, password := os.Args[1], os.Args[2], os.Args[3], os.Args[4]
+
+ // Clone the given repository to the given directory
+ Info("git clone %s %s", url, directory)
+
+ r, err := git.PlainClone(directory, false, &git.CloneOptions{
+ Auth: &http.BasicAuth{
+ Username: username,
+ Password: password,
+ },
+ URL: url,
+ Progress: os.Stdout,
+ })
+ CheckIfError(err)
+
+ // ... retrieving the branch being pointed by HEAD
+ ref, err := r.Head()
+ CheckIfError(err)
+ // ... retrieving the commit object
+ commit, err := r.CommitObject(ref.Hash())
+ CheckIfError(err)
+
+ fmt.Println(commit)
+}
diff --git a/_examples/commit/main.go b/_examples/commit/main.go
index 556cb9c..ec296b9 100644
--- a/_examples/commit/main.go
+++ b/_examples/commit/main.go
@@ -12,13 +12,13 @@ import (
"gopkg.in/src-d/go-git.v4/plumbing/object"
)
-// Basic example of how to commit changes to the current branch to an existent
+// Basic example of how to commit changes to the current branch to an existing
// repository.
func main() {
CheckArgs("<directory>")
directory := os.Args[1]
- // Opens an already existent repository.
+ // Opens an already existing repository.
r, err := git.PlainOpen(directory)
CheckIfError(err)
@@ -44,7 +44,7 @@ func main() {
fmt.Println(status)
- // Commits the current staging are to the repository, with the new file
+ // Commits the current staging area to the repository, with the new file
// just created. We should provide the object.Signature of Author of the
// commit.
Info("git commit -m \"example go-git commit\"")
diff --git a/_examples/log/main.go b/_examples/log/main.go
index 714d58f..ba0597a 100644
--- a/_examples/log/main.go
+++ b/_examples/log/main.go
@@ -23,7 +23,7 @@ func main() {
})
CheckIfError(err)
- // Gets the HEAD history from HEAD, just like does:
+ // Gets the HEAD history from HEAD, just like this command:
Info("git log")
// ... retrieves the branch pointed by HEAD
diff --git a/_examples/open/main.go b/_examples/open/main.go
index b890423..dec183e 100644
--- a/_examples/open/main.go
+++ b/_examples/open/main.go
@@ -14,7 +14,7 @@ func main() {
CheckArgs("<path>")
path := os.Args[1]
- // We instance a new repository targeting the given path (the .git folder)
+ // We instanciate a new repository targeting the given path (the .git folder)
r, err := git.PlainOpen(path)
CheckIfError(err)
diff --git a/_examples/pull/main.go b/_examples/pull/main.go
index ae751d2..06369fa 100644
--- a/_examples/pull/main.go
+++ b/_examples/pull/main.go
@@ -13,7 +13,7 @@ func main() {
CheckArgs("<path>")
path := os.Args[1]
- // We instance a new repository targeting the given path (the .git folder)
+ // We instance\iate a new repository targeting the given path (the .git folder)
r, err := git.PlainOpen(path)
CheckIfError(err)
diff --git a/_examples/showcase/main.go b/_examples/showcase/main.go
index aeeddb8..85f2b58 100644
--- a/_examples/showcase/main.go
+++ b/_examples/showcase/main.go
@@ -16,7 +16,7 @@ import (
// - Get the HEAD reference
// - Using the HEAD reference, obtain the commit this reference is pointing to
// - Print the commit content
-// - Using the commit, iterate all its files and print them
+// - Using the commit, iterate over all its files and print them
// - Print all the commit history with commit messages, short hash and the
// first line of the commit message
func main() {
diff --git a/_examples/storage/README.md b/_examples/storage/README.md
index fc72e6f..b002515 100644
--- a/_examples/storage/README.md
+++ b/_examples/storage/README.md
@@ -6,7 +6,7 @@
### and what this means ...
-*git* has as very well defined storage system, the `.git` directory, present on any repository. This is the place where `git` stores al the [`objects`](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects), [`references`](https://git-scm.com/book/es/v2/Git-Internals-Git-References) and [`configuration`](https://git-scm.com/docs/git-config#_configuration_file). This information is stored in plain files.
+*git* has a very well defined storage system, the `.git` directory, present on any repository. This is the place where `git` stores all the [`objects`](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects), [`references`](https://git-scm.com/book/es/v2/Git-Internals-Git-References) and [`configuration`](https://git-scm.com/docs/git-config#_configuration_file). This information is stored in plain files.
Our original **go-git** version was designed to work in memory, some time after we added support to read the `.git`, and now we have added support for fully customized [storages](https://godoc.org/gopkg.in/src-d/go-git.v4/storage#Storer).
diff --git a/_examples/tag/main.go b/_examples/tag/main.go
index 190c3ad..1e6212b 100644
--- a/_examples/tag/main.go
+++ b/_examples/tag/main.go
@@ -15,7 +15,7 @@ func main() {
CheckArgs("<path>")
path := os.Args[1]
- // We instance a new repository targeting the given path (the .git folder)
+ // We instanciate a new repository targeting the given path (the .git folder)
r, err := git.PlainOpen(path)
CheckIfError(err)
diff --git a/example_test.go b/example_test.go
index ef7e3d3..691b4ac 100644
--- a/example_test.go
+++ b/example_test.go
@@ -11,6 +11,7 @@ import (
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/config"
"gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
"gopkg.in/src-d/go-git.v4/storage/memory"
"gopkg.in/src-d/go-billy.v4/memfs"
@@ -69,6 +70,52 @@ func ExamplePlainClone() {
// Output: Initial changelog
}
+func ExamplePlainClone_usernamePassword() {
+ // Tempdir to clone the repository
+ dir, err := ioutil.TempDir("", "clone-example")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ defer os.RemoveAll(dir) // clean up
+
+ // Clones the repository into the given dir, just as a normal git clone does
+ _, err = git.PlainClone(dir, false, &git.CloneOptions{
+ URL: "https://github.com/git-fixtures/basic.git",
+ Auth: &http.BasicAuth{
+ Username: "username",
+ Password: "password",
+ },
+ })
+
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExamplePlainClone_accessToken() {
+ // Tempdir to clone the repository
+ dir, err := ioutil.TempDir("", "clone-example")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ defer os.RemoveAll(dir) // clean up
+
+ // Clones the repository into the given dir, just as a normal git clone does
+ _, err = git.PlainClone(dir, false, &git.CloneOptions{
+ URL: "https://github.com/git-fixtures/basic.git",
+ Auth: &http.BasicAuth{
+ Username: "abc123", // anything except an empty string
+ Password: "github_access_token",
+ },
+ })
+
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
func ExampleRepository_References() {
r, _ := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
URL: "https://github.com/git-fixtures/basic.git",
diff --git a/go.mod b/go.mod
index e269350..36a1bed 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
github.com/pkg/errors v0.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.0.0
- github.com/src-d/gcfg v1.3.0
+ github.com/src-d/gcfg v1.4.0
github.com/stretchr/testify v1.2.2 // indirect
github.com/xanzy/ssh-agent v0.2.0
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
diff --git a/go.sum b/go.sum
index e262a66..98ba1d4 100644
--- a/go.sum
+++ b/go.sum
@@ -35,6 +35,8 @@ github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/src-d/gcfg v1.3.0 h1:2BEDr8r0I0b8h/fOqwtxCEiq2HJu8n2JGZJQFGXWLjg=
github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
+github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
+github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
diff --git a/plumbing/format/packfile/packfile.go b/plumbing/format/packfile/packfile.go
index 0d13066..2166e0a 100644
--- a/plumbing/format/packfile/packfile.go
+++ b/plumbing/format/packfile/packfile.go
@@ -114,62 +114,6 @@ func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) {
return h, err
}
-func (p *Packfile) getObjectData(
- h *ObjectHeader,
-) (typ plumbing.ObjectType, size int64, err error) {
- switch h.Type {
- case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject:
- typ = h.Type
- size = h.Length
- case plumbing.REFDeltaObject, plumbing.OFSDeltaObject:
- buf := bufPool.Get().(*bytes.Buffer)
- buf.Reset()
- defer bufPool.Put(buf)
-
- _, _, err = p.s.NextObject(buf)
- if err != nil {
- return
- }
-
- delta := buf.Bytes()
- _, delta = decodeLEB128(delta) // skip src size
- sz, _ := decodeLEB128(delta)
- size = int64(sz)
-
- var offset int64
- if h.Type == plumbing.REFDeltaObject {
- offset, err = p.FindOffset(h.Reference)
- if err != nil {
- return
- }
- } else {
- offset = h.OffsetReference
- }
-
- if baseType, ok := p.offsetToType[offset]; ok {
- typ = baseType
- } else {
- if _, err = p.s.SeekFromStart(offset); err != nil {
- return
- }
-
- h, err = p.nextObjectHeader()
- if err != nil {
- return
- }
-
- typ, _, err = p.getObjectData(h)
- if err != nil {
- return
- }
- }
- default:
- err = ErrInvalidObject.AddDetails("type %q", h.Type)
- }
-
- return
-}
-
func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) {
switch h.Type {
case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject:
diff --git a/plumbing/reference.go b/plumbing/reference.go
index 2f53d4e..08e908f 100644
--- a/plumbing/reference.go
+++ b/plumbing/reference.go
@@ -55,6 +55,36 @@ func (r ReferenceType) String() string {
// ReferenceName reference name's
type ReferenceName string
+// NewBranchReferenceName returns a reference name describing a branch based on
+// his short name.
+func NewBranchReferenceName(name string) ReferenceName {
+ return ReferenceName(refHeadPrefix + name)
+}
+
+// NewNoteReferenceName returns a reference name describing a note based on his
+// short name.
+func NewNoteReferenceName(name string) ReferenceName {
+ return ReferenceName(refNotePrefix + name)
+}
+
+// NewRemoteReferenceName returns a reference name describing a remote branch
+// based on his short name and the remote name.
+func NewRemoteReferenceName(remote, name string) ReferenceName {
+ return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, name))
+}
+
+// NewRemoteHEADReferenceName returns a reference name describing a the HEAD
+// branch of a remote.
+func NewRemoteHEADReferenceName(remote string) ReferenceName {
+ return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, HEAD))
+}
+
+// NewTagReferenceName returns a reference name describing a tag based on short
+// his name.
+func NewTagReferenceName(name string) ReferenceName {
+ return ReferenceName(refTagPrefix + name)
+}
+
// IsBranch check if a reference is a branch
func (r ReferenceName) IsBranch() bool {
return strings.HasPrefix(string(r), refHeadPrefix)
diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go
index 47919ef..b3ccf53 100644
--- a/plumbing/reference_test.go
+++ b/plumbing/reference_test.go
@@ -54,6 +54,31 @@ func (s *ReferenceSuite) TestNewHashReference(c *C) {
c.Assert(r.Hash(), Equals, NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
}
+func (s *ReferenceSuite) TestNewBranchReferenceName(c *C) {
+ r := NewBranchReferenceName("foo")
+ c.Assert(r.String(), Equals, "refs/heads/foo")
+}
+
+func (s *ReferenceSuite) TestNewNoteReferenceName(c *C) {
+ r := NewNoteReferenceName("foo")
+ c.Assert(r.String(), Equals, "refs/notes/foo")
+}
+
+func (s *ReferenceSuite) TestNewRemoteReferenceName(c *C) {
+ r := NewRemoteReferenceName("bar", "foo")
+ c.Assert(r.String(), Equals, "refs/remotes/bar/foo")
+}
+
+func (s *ReferenceSuite) TestNewRemoteHEADReferenceName(c *C) {
+ r := NewRemoteHEADReferenceName("foo")
+ c.Assert(r.String(), Equals, "refs/remotes/foo/HEAD")
+}
+
+func (s *ReferenceSuite) TestNewTagReferenceName(c *C) {
+ r := NewTagReferenceName("foo")
+ c.Assert(r.String(), Equals, "refs/tags/foo")
+}
+
func (s *ReferenceSuite) TestIsBranch(c *C) {
r := ExampleReferenceName
c.Assert(r.IsBranch(), Equals, true)
diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go
index c034846..5d3535e 100644
--- a/plumbing/transport/http/common.go
+++ b/plumbing/transport/http/common.go
@@ -4,6 +4,7 @@ package http
import (
"bytes"
"fmt"
+ "net"
"net/http"
"strconv"
"strings"
@@ -151,6 +152,18 @@ func (s *session) ModifyEndpointIfRedirect(res *http.Response) {
return
}
+ h, p, err := net.SplitHostPort(r.URL.Host)
+ if err != nil {
+ h = r.URL.Host
+ }
+ if p != "" {
+ port, err := strconv.Atoi(p)
+ if err == nil {
+ s.endpoint.Port = port
+ }
+ }
+ s.endpoint.Host = h
+
s.endpoint.Protocol = r.URL.Scheme
s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)]
}
@@ -201,7 +214,14 @@ func (a *BasicAuth) String() string {
return fmt.Sprintf("%s - %s:%s", a.Name(), a.Username, masked)
}
-// TokenAuth implements the go-git http.AuthMethod and transport.AuthMethod interfaces
+// TokenAuth implements an http.AuthMethod that can be used with http transport
+// to authenticate with HTTP token authentication (also known as bearer
+// authentication).
+//
+// IMPORTANT: If you are looking to use OAuth tokens with popular servers (e.g.
+// GitHub, Bitbucket, GitLab) you should use BasicAuth instead. These servers
+// use basic HTTP authentication, with the OAuth token as user or password.
+// Check the documentation of your git server for details.
type TokenAuth struct {
Token string
}
diff --git a/plumbing/transport/http/common_test.go b/plumbing/transport/http/common_test.go
index 71eede4..8b300e8 100644
--- a/plumbing/transport/http/common_test.go
+++ b/plumbing/transport/http/common_test.go
@@ -8,6 +8,7 @@ import (
"net"
"net/http"
"net/http/cgi"
+ "net/url"
"os"
"os/exec"
"path/filepath"
@@ -119,6 +120,42 @@ func (s *ClientSuite) TestSetAuthWrongType(c *C) {
c.Assert(err, Equals, transport.ErrInvalidAuthMethod)
}
+func (s *ClientSuite) TestModifyEndpointIfRedirect(c *C) {
+ sess := &session{endpoint: nil}
+ u, _ := url.Parse("https://example.com/info/refs")
+ res := &http.Response{Request: &http.Request{URL: u}}
+ c.Assert(func() {
+ sess.ModifyEndpointIfRedirect(res)
+ }, PanicMatches, ".*nil pointer dereference.*")
+
+ sess = &session{endpoint: nil}
+ // no-op - should return and not panic
+ sess.ModifyEndpointIfRedirect(&http.Response{})
+
+ data := []struct {
+ url string
+ endpoint *transport.Endpoint
+ expected *transport.Endpoint
+ }{
+ {"https://example.com/foo/bar", nil, nil},
+ {"https://example.com/foo.git/info/refs",
+ &transport.Endpoint{},
+ &transport.Endpoint{Protocol: "https", Host: "example.com", Path: "/foo.git"}},
+ {"https://example.com:8080/foo.git/info/refs",
+ &transport.Endpoint{},
+ &transport.Endpoint{Protocol: "https", Host: "example.com", Port: 8080, Path: "/foo.git"}},
+ }
+
+ for _, d := range data {
+ u, _ := url.Parse(d.url)
+ sess := &session{endpoint: d.endpoint}
+ sess.ModifyEndpointIfRedirect(&http.Response{
+ Request: &http.Request{URL: u},
+ })
+ c.Assert(d.endpoint, DeepEquals, d.expected)
+ }
+}
+
type BaseSuite struct {
fixtures.Suite
diff --git a/plumbing/transport/ssh/upload_pack_test.go b/plumbing/transport/ssh/upload_pack_test.go
index 87fd4f5..2685ff0 100644
--- a/plumbing/transport/ssh/upload_pack_test.go
+++ b/plumbing/transport/ssh/upload_pack_test.go
@@ -10,6 +10,7 @@ import (
"os/exec"
"path/filepath"
"strings"
+ "sync"
"gopkg.in/src-d/go-git.v4/plumbing/transport"
"gopkg.in/src-d/go-git.v4/plumbing/transport/test"
@@ -97,13 +98,20 @@ func handlerSSH(s ssh.Session) {
io.Copy(stdin, s)
}()
+ var wg sync.WaitGroup
+ wg.Add(2)
+
go func() {
- defer stderr.Close()
+ defer wg.Done()
io.Copy(s.Stderr(), stderr)
}()
- defer stdout.Close()
- io.Copy(s, stdout)
+ go func() {
+ defer wg.Done()
+ io.Copy(s, stdout)
+ }()
+
+ wg.Wait()
if err := cmd.Wait(); err != nil {
return
diff --git a/remote.go b/remote.go
index 0556b98..8f4e41d 100644
--- a/remote.go
+++ b/remote.go
@@ -155,7 +155,7 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
}
}
- rs, err := pushHashes(ctx, s, r.s, req, hashesToPush)
+ rs, err := pushHashes(ctx, s, r.s, req, hashesToPush, r.useRefDeltas(ar))
if err != nil {
return err
}
@@ -167,6 +167,10 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
return r.updateRemoteReferenceStorage(req, rs)
}
+func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool {
+ return !ar.Capabilities.Supports(capability.OFSDelta)
+}
+
func (r *Remote) newReferenceUpdateRequest(
o *PushOptions,
localRefs []*plumbing.Reference,
@@ -994,6 +998,7 @@ func pushHashes(
s storage.Storer,
req *packp.ReferenceUpdateRequest,
hs []plumbing.Hash,
+ useRefDeltas bool,
) (*packp.ReportStatus, error) {
rd, wr := io.Pipe()
@@ -1004,7 +1009,7 @@ func pushHashes(
}
done := make(chan error)
go func() {
- e := packfile.NewEncoder(wr, s, false)
+ e := packfile.NewEncoder(wr, s, useRefDeltas)
if _, err := e.Encode(hs, config.Pack.Window); err != nil {
done <- wr.CloseWithError(err)
return
diff --git a/remote_test.go b/remote_test.go
index 175faed..28b0a3a 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -11,6 +11,7 @@ import (
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/cache"
"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp"
+ "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
"gopkg.in/src-d/go-git.v4/plumbing/storer"
"gopkg.in/src-d/go-git.v4/storage"
"gopkg.in/src-d/go-git.v4/storage/filesystem"
@@ -798,3 +799,25 @@ func (s *RemoteSuite) TestUpdateShallows(c *C) {
c.Assert(shallow, DeepEquals, t.result)
}
}
+
+func (s *RemoteSuite) TestUseRefDeltas(c *C) {
+ url := c.MkDir()
+ _, err := PlainInit(url, true)
+ c.Assert(err, IsNil)
+
+ fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit()
+ sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
+
+ r := newRemote(sto, &config.RemoteConfig{
+ Name: DefaultRemoteName,
+ URLs: []string{url},
+ })
+
+ ar := packp.NewAdvRefs()
+
+ ar.Capabilities.Add(capability.OFSDelta)
+ c.Assert(r.useRefDeltas(ar), Equals, false)
+
+ ar.Capabilities.Delete(capability.OFSDelta)
+ c.Assert(r.useRefDeltas(ar), Equals, true)
+}
diff --git a/repository.go b/repository.go
index 507ff44..f548e9a 100644
--- a/repository.go
+++ b/repository.go
@@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
+ "io"
stdioutil "io/ioutil"
"os"
"path"
@@ -342,12 +343,22 @@ func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error)
//
// TODO(mcuadros): move isBare to CloneOptions in v5
func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOptions) (*Repository, error) {
+ dirExists, err := checkExistsAndIsEmptyDir(path)
+ if err != nil {
+ return nil, err
+ }
+
r, err := PlainInit(path, isBare)
if err != nil {
return nil, err
}
- return r, r.clone(ctx, o)
+ err = r.clone(ctx, o)
+ if err != nil && err != ErrRepositoryAlreadyExists {
+ cleanUpDir(path, !dirExists)
+ }
+
+ return r, err
}
func newRepository(s storage.Storer, worktree billy.Filesystem) *Repository {
@@ -358,6 +369,65 @@ func newRepository(s storage.Storer, worktree billy.Filesystem) *Repository {
}
}
+func checkExistsAndIsEmptyDir(path string) (exists bool, err error) {
+ fi, err := os.Stat(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+
+ return false, err
+ }
+
+ if !fi.IsDir() {
+ return false, fmt.Errorf("path is not a directory: %s", path)
+ }
+
+ f, err := os.Open(path)
+ if err != nil {
+ return false, err
+ }
+
+ defer ioutil.CheckClose(f, &err)
+
+ _, err = f.Readdirnames(1)
+ if err == io.EOF {
+ return true, nil
+ }
+
+ if err != nil {
+ return true, err
+ }
+
+ return true, fmt.Errorf("directory is not empty: %s", path)
+}
+
+func cleanUpDir(path string, all bool) error {
+ if all {
+ return os.RemoveAll(path)
+ }
+
+ f, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+
+ defer ioutil.CheckClose(f, &err)
+
+ names, err := f.Readdirnames(-1)
+ if err != nil {
+ return err
+ }
+
+ for _, name := range names {
+ if err := os.RemoveAll(filepath.Join(path, name)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
// Config return the repository config
func (r *Repository) Config() (*config.Config, error) {
return r.Storer.Config()
@@ -640,8 +710,9 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
}
c := &config.RemoteConfig{
- Name: o.RemoteName,
- URLs: []string{o.URL},
+ Name: o.RemoteName,
+ URLs: []string{o.URL},
+ Fetch: r.cloneRefSpec(o),
}
if _, err := r.CreateRemote(c); err != nil {
@@ -649,7 +720,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
}
ref, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{
- RefSpecs: r.cloneRefSpec(o, c),
+ RefSpecs: c.Fetch,
Depth: o.Depth,
Auth: o.Auth,
Progress: o.Progress,
@@ -719,21 +790,26 @@ const (
refspecSingleBranchHEAD = "+HEAD:refs/remotes/%s/HEAD"
)
-func (r *Repository) cloneRefSpec(o *CloneOptions, c *config.RemoteConfig) []config.RefSpec {
- var rs string
-
+func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec {
switch {
case o.ReferenceName.IsTag():
- rs = fmt.Sprintf(refspecTag, o.ReferenceName.Short())
+ return []config.RefSpec{
+ config.RefSpec(fmt.Sprintf(refspecTag, o.ReferenceName.Short())),
+ }
case o.SingleBranch && o.ReferenceName == plumbing.HEAD:
- rs = fmt.Sprintf(refspecSingleBranchHEAD, c.Name)
+ return []config.RefSpec{
+ config.RefSpec(fmt.Sprintf(refspecSingleBranchHEAD, o.RemoteName)),
+ config.RefSpec(fmt.Sprintf(refspecSingleBranch, plumbing.Master.Short(), o.RemoteName)),
+ }
case o.SingleBranch:
- rs = fmt.Sprintf(refspecSingleBranch, o.ReferenceName.Short(), c.Name)
+ return []config.RefSpec{
+ config.RefSpec(fmt.Sprintf(refspecSingleBranch, o.ReferenceName.Short(), o.RemoteName)),
+ }
default:
- return c.Fetch
+ return []config.RefSpec{
+ config.RefSpec(fmt.Sprintf(config.DefaultFetchRefSpec, o.RemoteName)),
+ }
}
-
- return []config.RefSpec{config.RefSpec(rs)}
}
func (r *Repository) setIsBare(isBare bool) error {
@@ -751,9 +827,7 @@ func (r *Repository) updateRemoteConfigIfNeeded(o *CloneOptions, c *config.Remot
return nil
}
- c.Fetch = []config.RefSpec{config.RefSpec(fmt.Sprintf(
- refspecSingleBranch, head.Name().Short(), c.Name,
- ))}
+ c.Fetch = r.cloneRefSpec(o)
cfg, err := r.Storer.Config()
if err != nil {
diff --git a/repository_test.go b/repository_test.go
index 07c3570..59ab89e 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -581,14 +581,94 @@ func (s *RepositorySuite) TestPlainCloneWithRemoteName(c *C) {
c.Assert(remote, NotNil)
}
-func (s *RepositorySuite) TestPlainCloneContext(c *C) {
+func (s *RepositorySuite) TestPlainCloneContextWithProperParameters(c *C) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
- _, err := PlainCloneContext(ctx, c.MkDir(), false, &CloneOptions{
+ r, err := PlainCloneContext(ctx, c.MkDir(), false, &CloneOptions{
URL: s.GetBasicLocalRepositoryURL(),
})
+ c.Assert(r, NotNil)
+ c.Assert(err, NotNil)
+}
+
+func (s *RepositorySuite) TestPlainCloneContextNonExistentWithExistentDir(c *C) {
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ tmpDir := c.MkDir()
+ repoDir := tmpDir
+
+ r, err := PlainCloneContext(ctx, repoDir, false, &CloneOptions{
+ URL: "incorrectOnPurpose",
+ })
+ c.Assert(r, NotNil)
+ c.Assert(err, NotNil)
+
+ _, err = os.Stat(repoDir)
+ c.Assert(os.IsNotExist(err), Equals, false)
+
+ names, err := ioutil.ReadDir(repoDir)
+ c.Assert(err, IsNil)
+ c.Assert(names, HasLen, 0)
+}
+
+func (s *RepositorySuite) TestPlainCloneContextNonExistentWithNonExistentDir(c *C) {
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ tmpDir := c.MkDir()
+ repoDir := filepath.Join(tmpDir, "repoDir")
+
+ r, err := PlainCloneContext(ctx, repoDir, false, &CloneOptions{
+ URL: "incorrectOnPurpose",
+ })
+ c.Assert(r, NotNil)
+ c.Assert(err, NotNil)
+
+ _, err = os.Stat(repoDir)
+ c.Assert(os.IsNotExist(err), Equals, true)
+}
+
+func (s *RepositorySuite) TestPlainCloneContextNonExistentWithNotDir(c *C) {
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ tmpDir := c.MkDir()
+ repoDir := filepath.Join(tmpDir, "repoDir")
+ f, err := os.Create(repoDir)
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ r, err := PlainCloneContext(ctx, repoDir, false, &CloneOptions{
+ URL: "incorrectOnPurpose",
+ })
+ c.Assert(r, IsNil)
+ c.Assert(err, NotNil)
+
+ fi, err := os.Stat(repoDir)
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *RepositorySuite) TestPlainCloneContextNonExistentWithNotEmptyDir(c *C) {
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ tmpDir := c.MkDir()
+ repoDirPath := filepath.Join(tmpDir, "repoDir")
+ err := os.Mkdir(repoDirPath, 0777)
+ c.Assert(err, IsNil)
+
+ dummyFile := filepath.Join(repoDirPath, "dummyFile")
+ err = ioutil.WriteFile(dummyFile, []byte(fmt.Sprint("dummyContent")), 0644)
+ c.Assert(err, IsNil)
+
+ r, err := PlainCloneContext(ctx, repoDirPath, false, &CloneOptions{
+ URL: "incorrectOnPurpose",
+ })
+ c.Assert(r, IsNil)
c.Assert(err, NotNil)
}
@@ -839,6 +919,32 @@ func (s *RepositorySuite) TestCloneSingleBranch(c *C) {
c.Assert(branch.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
}
+func (s *RepositorySuite) TestCloneSingleTag(c *C) {
+ r, _ := Init(memory.NewStorage(), nil)
+
+ url := s.GetLocalRepositoryURL(
+ fixtures.ByURL("https://github.com/git-fixtures/tags.git").One(),
+ )
+
+ err := r.clone(context.Background(), &CloneOptions{
+ URL: url,
+ SingleBranch: true,
+ ReferenceName: plumbing.ReferenceName("refs/tags/commit-tag"),
+ })
+ c.Assert(err, IsNil)
+
+ branch, err := r.Reference("refs/tags/commit-tag", false)
+ c.Assert(err, IsNil)
+ c.Assert(branch, NotNil)
+
+ conf, err := r.Config()
+ c.Assert(err, IsNil)
+ originRemote := conf.Remotes["origin"]
+ c.Assert(originRemote, NotNil)
+ c.Assert(originRemote.Fetch, HasLen, 1)
+ c.Assert(originRemote.Fetch[0].String(), Equals, "+refs/tags/commit-tag:refs/tags/commit-tag")
+}
+
func (s *RepositorySuite) TestCloneDetachedHEAD(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
@@ -2104,9 +2210,9 @@ func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) {
c.Assert(err, IsNil)
datas := map[string]string{
- "efs/heads/master~": "reference not found",
- "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`,
- "HEAD^{/whatever}": `No commit message match regexp : "whatever"`,
+ "efs/heads/master~": "reference not found",
+ "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`,
+ "HEAD^{/whatever}": `No commit message match regexp : "whatever"`,
"4e1243bd22c66e76c2ba9eddc1f91394e57f9f83": "reference not found",
"918c48b83bd081e863dbe1b80f8998f058cd8294": `refname "918c48b83bd081e863dbe1b80f8998f058cd8294" is ambiguous`,
}
diff --git a/worktree_bsd.go b/worktree_bsd.go
index 3b374c7..9ff670e 100644
--- a/worktree_bsd.go
+++ b/worktree_bsd.go
@@ -1,4 +1,4 @@
-// +build darwin freebsd netbsd openbsd
+// +build darwin freebsd netbsd
package git
diff --git a/worktree_unix_other.go b/worktree_unix_other.go
new file mode 100644
index 0000000..d632767
--- /dev/null
+++ b/worktree_unix_other.go
@@ -0,0 +1,26 @@
+// +build openbsd dragonfly solaris
+
+package git
+
+import (
+ "syscall"
+ "time"
+
+ "gopkg.in/src-d/go-git.v4/plumbing/format/index"
+)
+
+func init() {
+ fillSystemInfo = func(e *index.Entry, sys interface{}) {
+ if os, ok := sys.(*syscall.Stat_t); ok {
+ e.CreatedAt = time.Unix(int64(os.Atim.Sec), int64(os.Atim.Nsec))
+ e.Dev = uint32(os.Dev)
+ e.Inode = uint32(os.Ino)
+ e.GID = os.Gid
+ e.UID = os.Uid
+ }
+ }
+}
+
+func isSymlinkWindowsNonAdmin(err error) bool {
+ return false
+}