Skip to content

Commit

Permalink
Merge pull request #325 from tjamet/push/sha
Browse files Browse the repository at this point in the history
Remote: Push, add support to push commits per hashes
  • Loading branch information
mcuadros committed Nov 1, 2021
2 parents 934e99b + 21d8d7e commit ed3b10c
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 2 deletions.
10 changes: 8 additions & 2 deletions options.go
Expand Up @@ -198,8 +198,14 @@ type PushOptions struct {
RemoteName string
// RemoteURL overrides the remote repo address with a custom URL
RemoteURL string
// RefSpecs specify what destination ref to update with what source
// object. A refspec with empty src can be used to delete a reference.
// RefSpecs specify what destination ref to update with what source object.
//
// The format of a <refspec> parameter is an optional plus +, followed by
// the source object <src>, followed by a colon :, followed by the destination ref <dst>.
// The <src> is often the name of the branch you would want to push, but it can be a SHA-1.
// The <dst> tells which ref on the remote side is updated with this push.
//
// A refspec with empty src can be used to delete a reference.
RefSpecs []config.RefSpec
// Auth credentials, if required, to use with the remote repository.
Auth transport.AuthMethod
Expand Down
41 changes: 41 additions & 0 deletions remote.go
Expand Up @@ -602,6 +602,10 @@ func (r *Remote) addOrUpdateReferences(
if !rs.IsWildcard() {
ref, ok := refsDict[rs.Src()]
if !ok {
commit, err := object.GetCommit(r.s, plumbing.NewHash(rs.Src()))
if err == nil {
return r.addCommit(rs, remoteRefs, commit.Hash, req)
}
return nil
}

Expand Down Expand Up @@ -656,6 +660,43 @@ func (r *Remote) deleteReferences(rs config.RefSpec,
})
}

func (r *Remote) addCommit(rs config.RefSpec,
remoteRefs storer.ReferenceStorer, localCommit plumbing.Hash,
req *packp.ReferenceUpdateRequest) error {

if rs.IsWildcard() {
return errors.New("can't use wildcard together with hash refspecs")
}

cmd := &packp.Command{
Name: rs.Dst(""),
Old: plumbing.ZeroHash,
New: localCommit,
}
remoteRef, err := remoteRefs.Reference(cmd.Name)
if err == nil {
if remoteRef.Type() != plumbing.HashReference {
//TODO: check actual git behavior here
return nil
}

cmd.Old = remoteRef.Hash()
} else if err != plumbing.ErrReferenceNotFound {
return err
}
if cmd.Old == cmd.New {
return nil
}
if !rs.IsForceUpdate() {
if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
return err
}
}

req.Commands = append(req.Commands, cmd)
return nil
}

func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference,
req *packp.ReferenceUpdateRequest) error {
Expand Down
92 changes: 92 additions & 0 deletions remote_test.go
Expand Up @@ -5,12 +5,16 @@ import (
"context"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"time"

"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
"github.com/go-git/go-git/v5/plumbing/storer"
Expand Down Expand Up @@ -1206,3 +1210,91 @@ func (s *RemoteSuite) TestPushRequireRemoteRefs(c *C) {
c.Assert(err, IsNil)
c.Assert(newRef, Not(DeepEquals), oldRef)
}

func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
d, err := ioutil.TempDir("", "TestCanPushShasToReference")
c.Assert(err, IsNil)
if err != nil {
return
}
defer os.RemoveAll(d)

// remote currently forces a plain path for path based remotes inside the PushContext function.
// This makes it impossible, in the current state to use memfs.
// For the sake of readability, use the same osFS everywhere and use plain git repositories on temporary files
remote, err := PlainInit(filepath.Join(d, "remote"), true)
c.Assert(err, IsNil)
c.Assert(remote, NotNil)

repo, err := PlainInit(filepath.Join(d, "repo"), false)
c.Assert(err, IsNil)
c.Assert(repo, NotNil)

fd, err := os.Create(filepath.Join(d, "repo", "README.md"))
c.Assert(err, IsNil)
if err != nil {
return
}
_, err = fd.WriteString("# test repo")
c.Assert(err, IsNil)
if err != nil {
return
}
err = fd.Close()
c.Assert(err, IsNil)
if err != nil {
return
}

wt, err := repo.Worktree()
c.Assert(err, IsNil)
if err != nil {
return
}

wt.Add("README.md")
sha, err := wt.Commit("test commit", &CommitOptions{
Author: &object.Signature{
Name: "test",
Email: "test@example.com",
When: time.Now(),
},
Committer: &object.Signature{
Name: "test",
Email: "test@example.com",
When: time.Now(),
},
})
c.Assert(err, IsNil)
if err != nil {
return
}

gitremote, err := repo.CreateRemote(&config.RemoteConfig{
Name: "local",
URLs: []string{filepath.Join(d, "remote")},
})
c.Assert(err, IsNil)
if err != nil {
return
}

err = gitremote.Push(&PushOptions{
RemoteName: "local",
RefSpecs: []config.RefSpec{
// TODO: check with short hashes that this is still respected
config.RefSpec(sha.String() + ":refs/heads/branch"),
},
})
c.Assert(err, IsNil)
if err != nil {
return
}

ref, err := remote.Reference(plumbing.ReferenceName("refs/heads/branch"), false)
c.Assert(err, IsNil)
if err != nil {
return
}
c.Assert(ref.Hash().String(), Equals, sha.String())
}

0 comments on commit ed3b10c

Please sign in to comment.