Skip to content

Commit 617ae9f

Browse files
committed
Add support to push commits per hashes
Using plain git, the command `git push ${sha}:refs/heads/some-branch` actually ensures that the remote branch `some-branch` points to the commit `${sha}`. In the current version of go-git, this results in an "everything is up to date" error. When a source reference is not found, check the object storage to find the sha. If it is found, consider pushing this exact commit. fixes: go-git#105
1 parent 99457e5 commit 617ae9f

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

Diff for: remote.go

+41
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,10 @@ func (r *Remote) addOrUpdateReferences(
602602
if !rs.IsWildcard() {
603603
ref, ok := refsDict[rs.Src()]
604604
if !ok {
605+
commit, err := object.GetCommit(r.s, plumbing.NewHash(rs.Src()))
606+
if err == nil {
607+
return r.addCommit(rs, remoteRefs, commit.Hash, req)
608+
}
605609
return nil
606610
}
607611

@@ -656,6 +660,43 @@ func (r *Remote) deleteReferences(rs config.RefSpec,
656660
})
657661
}
658662

663+
func (r *Remote) addCommit(rs config.RefSpec,
664+
remoteRefs storer.ReferenceStorer, localCommit plumbing.Hash,
665+
req *packp.ReferenceUpdateRequest) error {
666+
667+
if rs.IsWildcard() {
668+
return errors.New("can't use wildcard together with hash refspecs")
669+
}
670+
671+
cmd := &packp.Command{
672+
Name: rs.Dst(""),
673+
Old: plumbing.ZeroHash,
674+
New: localCommit,
675+
}
676+
remoteRef, err := remoteRefs.Reference(cmd.Name)
677+
if err == nil {
678+
if remoteRef.Type() != plumbing.HashReference {
679+
//TODO: check actual git behavior here
680+
return nil
681+
}
682+
683+
cmd.Old = remoteRef.Hash()
684+
} else if err != plumbing.ErrReferenceNotFound {
685+
return err
686+
}
687+
if cmd.Old == cmd.New {
688+
return nil
689+
}
690+
if !rs.IsForceUpdate() {
691+
if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
692+
return err
693+
}
694+
}
695+
696+
req.Commands = append(req.Commands, cmd)
697+
return nil
698+
}
699+
659700
func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
660701
remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference,
661702
req *packp.ReferenceUpdateRequest) error {

Diff for: remote_test.go

+92
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import (
55
"context"
66
"errors"
77
"io"
8+
"io/ioutil"
9+
"os"
10+
"path/filepath"
811
"runtime"
912
"time"
1013

1114
"github.com/go-git/go-git/v5/config"
1215
"github.com/go-git/go-git/v5/plumbing"
1316
"github.com/go-git/go-git/v5/plumbing/cache"
17+
"github.com/go-git/go-git/v5/plumbing/object"
1418
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
1519
"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
1620
"github.com/go-git/go-git/v5/plumbing/storer"
@@ -1206,3 +1210,91 @@ func (s *RemoteSuite) TestPushRequireRemoteRefs(c *C) {
12061210
c.Assert(err, IsNil)
12071211
c.Assert(newRef, Not(DeepEquals), oldRef)
12081212
}
1213+
1214+
func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
1215+
d, err := ioutil.TempDir("", "TestCanPushShasToReference")
1216+
c.Assert(err, IsNil)
1217+
if err != nil {
1218+
return
1219+
}
1220+
defer os.RemoveAll(d)
1221+
1222+
// remote currently forces a plain path for path based remotes inside the PushContext function.
1223+
// This makes it impossible, in the current state to use memfs.
1224+
// For the sake of readability, use the same osFS everywhere and use plain git repositories on temporary files
1225+
remote, err := PlainInit(filepath.Join(d, "remote"), true)
1226+
c.Assert(err, IsNil)
1227+
c.Assert(remote, NotNil)
1228+
1229+
repo, err := PlainInit(filepath.Join(d, "repo"), false)
1230+
c.Assert(err, IsNil)
1231+
c.Assert(repo, NotNil)
1232+
1233+
fd, err := os.Create(filepath.Join(d, "repo", "README.md"))
1234+
c.Assert(err, IsNil)
1235+
if err != nil {
1236+
return
1237+
}
1238+
_, err = fd.WriteString("# test repo")
1239+
c.Assert(err, IsNil)
1240+
if err != nil {
1241+
return
1242+
}
1243+
err = fd.Close()
1244+
c.Assert(err, IsNil)
1245+
if err != nil {
1246+
return
1247+
}
1248+
1249+
wt, err := repo.Worktree()
1250+
c.Assert(err, IsNil)
1251+
if err != nil {
1252+
return
1253+
}
1254+
1255+
wt.Add("README.md")
1256+
sha, err := wt.Commit("test commit", &CommitOptions{
1257+
Author: &object.Signature{
1258+
Name: "test",
1259+
1260+
When: time.Now(),
1261+
},
1262+
Committer: &object.Signature{
1263+
Name: "test",
1264+
1265+
When: time.Now(),
1266+
},
1267+
})
1268+
c.Assert(err, IsNil)
1269+
if err != nil {
1270+
return
1271+
}
1272+
1273+
gitremote, err := repo.CreateRemote(&config.RemoteConfig{
1274+
Name: "local",
1275+
URLs: []string{filepath.Join(d, "remote")},
1276+
})
1277+
c.Assert(err, IsNil)
1278+
if err != nil {
1279+
return
1280+
}
1281+
1282+
err = gitremote.Push(&PushOptions{
1283+
RemoteName: "local",
1284+
RefSpecs: []config.RefSpec{
1285+
// TODO: check with short hashes that this is still respected
1286+
config.RefSpec(sha.String() + ":refs/heads/branch"),
1287+
},
1288+
})
1289+
c.Assert(err, IsNil)
1290+
if err != nil {
1291+
return
1292+
}
1293+
1294+
ref, err := remote.Reference(plumbing.ReferenceName("refs/heads/branch"), false)
1295+
c.Assert(err, IsNil)
1296+
if err != nil {
1297+
return
1298+
}
1299+
c.Assert(ref.Hash().String(), Equals, sha.String())
1300+
}

0 commit comments

Comments
 (0)