Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote: Push, add support to push commits per hashes #325

Merged
merged 2 commits into from Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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())
}