aboutsummaryrefslogtreecommitdiffstats
path: root/remote_test.go
diff options
context:
space:
mode:
authorAditya Sirish <aditya@saky.in>2023-10-12 00:15:14 -0400
committerAditya Sirish <aditya@saky.in>2023-10-26 07:05:45 -0400
commit4862643548f115dff8d402faf610e50408458cdf (patch)
tree28a0f2150107c215c288f7d2126c16005c34f6db /remote_test.go
parentf4b881ce59edb35afb6a375187953db5ea7cbd64 (diff)
downloadgo-git-4862643548f115dff8d402faf610e50408458cdf.tar.gz
git: remote, flip branch check to not-tag check
In remote.updateLocalReferenceStorage, this commit updates a check to see if the reference is for a branch (in refs/heads) to checking the reference is not a tag. This change ensures that the subsequent fast-forward only handling clauses apply to references that are not standard branches stored in refs/heads. Signed-off-by: Aditya Sirish <aditya@saky.in>
Diffstat (limited to 'remote_test.go')
-rw-r--r--remote_test.go141
1 files changed, 141 insertions, 0 deletions
diff --git a/remote_test.go b/remote_test.go
index e0c3332..81c60bc 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -4,16 +4,20 @@ import (
"bytes"
"context"
"errors"
+ "fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
+ "testing"
"time"
+ "github.com/go-git/go-billy/v5/memfs"
"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"
@@ -1555,3 +1559,140 @@ func (s *RemoteSuite) TestFetchAfterShallowClone(c *C) {
plumbing.NewSymbolicReference("HEAD", "refs/heads/master"),
})
}
+
+func TestFetchFastForwardForCustomRef(t *testing.T) {
+ customRef := "refs/custom/branch"
+ // 1. Set up a remote with a URL
+ remoteURL := t.TempDir()
+ remoteRepo, err := PlainInit(remoteURL, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // 2. Add a commit with an empty tree to master and custom ref, also set HEAD
+ emptyTreeID := writeEmptyTree(t, remoteRepo)
+ writeCommitToRef(t, remoteRepo, "refs/heads/master", emptyTreeID, time.Now())
+ writeCommitToRef(t, remoteRepo, customRef, emptyTreeID, time.Now())
+ if err := remoteRepo.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, "refs/heads/master")); err != nil {
+ t.Fatal(err)
+ }
+
+ // 3. Clone repo, then fetch the custom ref
+ // Note that using custom ref in ReferenceName has an IsBranch issue
+ localRepo, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
+ URL: remoteURL,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := localRepo.Fetch(&FetchOptions{
+ RefSpecs: []config.RefSpec{
+ config.RefSpec(fmt.Sprintf("%s:%s", customRef, customRef)),
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ // 4. Make divergent changes
+ remoteCommitID := writeCommitToRef(t, remoteRepo, customRef, emptyTreeID, time.Now())
+ // Consecutive calls to writeCommitToRef with time.Now() might have the same
+ // time value, explicitly set distinct ones to ensure the commit hashes
+ // differ
+ writeCommitToRef(t, localRepo, customRef, emptyTreeID, time.Now().Add(time.Second))
+
+ // 5. Try to fetch with fast-forward only mode
+ remote, err := localRepo.Remote(DefaultRemoteName)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = remote.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{
+ config.RefSpec(fmt.Sprintf("%s:%s", customRef, customRef)),
+ }})
+ if !errors.Is(err, ErrForceNeeded) {
+ t.Errorf("expected %v, got %v", ErrForceNeeded, err)
+ }
+
+ // 6. Fetch with force
+ err = remote.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{
+ config.RefSpec(fmt.Sprintf("+%s:%s", customRef, customRef)),
+ }})
+ if err != nil {
+ t.Errorf("unexpected error %v", err)
+ }
+
+ // 7. Assert commit ID matches
+ ref, err := localRepo.Reference(plumbing.ReferenceName(customRef), true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if remoteCommitID != ref.Hash() {
+ t.Errorf("expected %s, got %s", remoteCommitID.String(), ref.Hash().String())
+ }
+}
+
+func writeEmptyTree(t *testing.T, repo *Repository) plumbing.Hash {
+ t.Helper()
+
+ obj := repo.Storer.NewEncodedObject()
+ obj.SetType(plumbing.TreeObject)
+
+ tree := object.Tree{Entries: nil}
+ if err := tree.Encode(obj); err != nil {
+ t.Fatal(err)
+ }
+
+ treeID, err := repo.Storer.SetEncodedObject(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return treeID
+}
+
+func writeCommitToRef(t *testing.T, repo *Repository, refName string, treeID plumbing.Hash, when time.Time) plumbing.Hash {
+ t.Helper()
+
+ ref, err := repo.Reference(plumbing.ReferenceName(refName), true)
+ if err != nil {
+ if errors.Is(err, plumbing.ErrReferenceNotFound) {
+ if err := repo.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(refName), plumbing.ZeroHash)); err != nil {
+ t.Fatal(err)
+ }
+
+ ref, err = repo.Reference(plumbing.ReferenceName(refName), true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ t.Fatal(err)
+ }
+ }
+
+ commit := &object.Commit{
+ TreeHash: treeID,
+ Author: object.Signature{
+ When: when,
+ },
+ }
+ if !ref.Hash().IsZero() {
+ commit.ParentHashes = []plumbing.Hash{ref.Hash()}
+ }
+
+ obj := repo.Storer.NewEncodedObject()
+ if err := commit.Encode(obj); err != nil {
+ t.Fatal(err)
+ }
+
+ commitID, err := repo.Storer.SetEncodedObject(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ newRef := plumbing.NewHashReference(plumbing.ReferenceName(refName), commitID)
+ if err := repo.Storer.CheckAndSetReference(newRef, ref); err != nil {
+ t.Fatal(err)
+ }
+
+ return commitID
+}