aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--_examples/remotes/main.go2
-rw-r--r--options.go15
-rw-r--r--plumbing/format/index/encoder.go34
-rw-r--r--plumbing/format/index/encoder_test.go26
-rw-r--r--plumbing/protocol/packp/updreq.go6
-rw-r--r--remote.go43
-rw-r--r--remote_test.go127
-rw-r--r--storage/filesystem/dotgit/dotgit_test.go7
-rw-r--r--storage/filesystem/object_test.go4
9 files changed, 235 insertions, 29 deletions
diff --git a/_examples/remotes/main.go b/_examples/remotes/main.go
index b1a91a9..d09957e 100644
--- a/_examples/remotes/main.go
+++ b/_examples/remotes/main.go
@@ -33,7 +33,7 @@ func main() {
CheckIfError(err)
// List remotes from a repository
- Info("git remotes -v")
+ Info("git remote -v")
list, err := r.Remotes()
CheckIfError(err)
diff --git a/options.go b/options.go
index e54889f..fee2e16 100644
--- a/options.go
+++ b/options.go
@@ -228,10 +228,25 @@ type PushOptions struct {
// FollowTags will send any annotated tags with a commit target reachable from
// the refs already being pushed
FollowTags bool
+ // ForceWithLease allows a force push as long as the remote ref adheres to a "lease"
+ ForceWithLease *ForceWithLease
// PushOptions sets options to be transferred to the server during push.
Options map[string]string
}
+// ForceWithLease sets fields on the lease
+// If neither RefName nor Hash are set, ForceWithLease protects
+// all refs in the refspec by ensuring the ref of the remote in the local repsitory
+// matches the one in the ref advertisement.
+type ForceWithLease struct {
+ // RefName, when set will protect the ref by ensuring it matches the
+ // hash in the ref advertisement.
+ RefName plumbing.ReferenceName
+ // Hash is the expected object id of RefName. The push will be rejected unless this
+ // matches the corresponding object id of RefName in the refs advertisement.
+ Hash plumbing.Hash
+}
+
// Validate validates the fields and sets the default values.
func (o *PushOptions) Validate() error {
if o.RemoteName == "" {
diff --git a/plumbing/format/index/encoder.go b/plumbing/format/index/encoder.go
index 00d4e7a..2c94d93 100644
--- a/plumbing/format/index/encoder.go
+++ b/plumbing/format/index/encoder.go
@@ -14,7 +14,7 @@ import (
var (
// EncodeVersionSupported is the range of supported index versions
- EncodeVersionSupported uint32 = 2
+ EncodeVersionSupported uint32 = 3
// ErrInvalidTimestamp is returned by Encode if a Index with a Entry with
// negative timestamp values
@@ -36,9 +36,9 @@ func NewEncoder(w io.Writer) *Encoder {
// Encode writes the Index to the stream of the encoder.
func (e *Encoder) Encode(idx *Index) error {
- // TODO: support versions v3 and v4
+ // TODO: support v4
// TODO: support extensions
- if idx.Version != EncodeVersionSupported {
+ if idx.Version > EncodeVersionSupported {
return ErrUnsupportedVersion
}
@@ -68,8 +68,12 @@ func (e *Encoder) encodeEntries(idx *Index) error {
if err := e.encodeEntry(entry); err != nil {
return err
}
+ entryLength := entryHeaderLength
+ if entry.IntentToAdd || entry.SkipWorktree {
+ entryLength += 2
+ }
- wrote := entryHeaderLength + len(entry.Name)
+ wrote := entryLength + len(entry.Name)
if err := e.padEntry(wrote); err != nil {
return err
}
@@ -79,10 +83,6 @@ func (e *Encoder) encodeEntries(idx *Index) error {
}
func (e *Encoder) encodeEntry(entry *Entry) error {
- if entry.IntentToAdd || entry.SkipWorktree {
- return ErrUnsupportedVersion
- }
-
sec, nsec, err := e.timeToUint32(&entry.CreatedAt)
if err != nil {
return err
@@ -110,9 +110,25 @@ func (e *Encoder) encodeEntry(entry *Entry) error {
entry.GID,
entry.Size,
entry.Hash[:],
- flags,
}
+ flagsFlow := []interface{}{flags}
+
+ if entry.IntentToAdd || entry.SkipWorktree {
+ var extendedFlags uint16
+
+ if entry.IntentToAdd {
+ extendedFlags |= intentToAddMask
+ }
+ if entry.SkipWorktree {
+ extendedFlags |= skipWorkTreeMask
+ }
+
+ flagsFlow = []interface{}{flags | entryExtended, extendedFlags}
+ }
+
+ flow = append(flow, flagsFlow...)
+
if err := binary.Write(e.w, flow...); err != nil {
return err
}
diff --git a/plumbing/format/index/encoder_test.go b/plumbing/format/index/encoder_test.go
index b7a73cb..25c24f1 100644
--- a/plumbing/format/index/encoder_test.go
+++ b/plumbing/format/index/encoder_test.go
@@ -57,7 +57,7 @@ func (s *IndexSuite) TestEncode(c *C) {
}
func (s *IndexSuite) TestEncodeUnsupportedVersion(c *C) {
- idx := &Index{Version: 3}
+ idx := &Index{Version: 4}
buf := bytes.NewBuffer(nil)
e := NewEncoder(buf)
@@ -67,24 +67,40 @@ func (s *IndexSuite) TestEncodeUnsupportedVersion(c *C) {
func (s *IndexSuite) TestEncodeWithIntentToAddUnsupportedVersion(c *C) {
idx := &Index{
- Version: 2,
+ Version: 3,
Entries: []*Entry{{IntentToAdd: true}},
}
buf := bytes.NewBuffer(nil)
e := NewEncoder(buf)
err := e.Encode(idx)
- c.Assert(err, Equals, ErrUnsupportedVersion)
+ c.Assert(err, IsNil)
+
+ output := &Index{}
+ d := NewDecoder(buf)
+ err = d.Decode(output)
+ c.Assert(err, IsNil)
+
+ c.Assert(cmp.Equal(idx, output), Equals, true)
+ c.Assert(output.Entries[0].IntentToAdd, Equals, true)
}
func (s *IndexSuite) TestEncodeWithSkipWorktreeUnsupportedVersion(c *C) {
idx := &Index{
- Version: 2,
+ Version: 3,
Entries: []*Entry{{SkipWorktree: true}},
}
buf := bytes.NewBuffer(nil)
e := NewEncoder(buf)
err := e.Encode(idx)
- c.Assert(err, Equals, ErrUnsupportedVersion)
+ c.Assert(err, IsNil)
+
+ output := &Index{}
+ d := NewDecoder(buf)
+ err = d.Decode(output)
+ c.Assert(err, IsNil)
+
+ c.Assert(cmp.Equal(idx, output), Equals, true)
+ c.Assert(output.Entries[0].SkipWorktree, Equals, true)
}
diff --git a/plumbing/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go
index 46ad6fd..5dbd8ac 100644
--- a/plumbing/protocol/packp/updreq.go
+++ b/plumbing/protocol/packp/updreq.go
@@ -87,9 +87,9 @@ type Action string
const (
Create Action = "create"
- Update = "update"
- Delete = "delete"
- Invalid = "invalid"
+ Update Action = "update"
+ Delete Action = "delete"
+ Invalid Action = "invalid"
)
type Command struct {
diff --git a/remote.go b/remote.go
index 426bde9..fecdebf 100644
--- a/remote.go
+++ b/remote.go
@@ -326,7 +326,7 @@ func (r *Remote) newReferenceUpdateRequest(
}
}
- if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune); err != nil {
+ if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune, o.ForceWithLease); err != nil {
return nil, err
}
@@ -568,6 +568,7 @@ func (r *Remote) addReferencesToUpdate(
remoteRefs storer.ReferenceStorer,
req *packp.ReferenceUpdateRequest,
prune bool,
+ forceWithLease *ForceWithLease,
) error {
// This references dictionary will be used to search references by name.
refsDict := make(map[string]*plumbing.Reference)
@@ -581,7 +582,7 @@ func (r *Remote) addReferencesToUpdate(
return err
}
} else {
- err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req)
+ err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req, forceWithLease)
if err != nil {
return err
}
@@ -603,6 +604,7 @@ func (r *Remote) addOrUpdateReferences(
refsDict map[string]*plumbing.Reference,
remoteRefs storer.ReferenceStorer,
req *packp.ReferenceUpdateRequest,
+ forceWithLease *ForceWithLease,
) error {
// If it is not a wilcard refspec we can directly search for the reference
// in the references dictionary.
@@ -616,11 +618,11 @@ func (r *Remote) addOrUpdateReferences(
return nil
}
- return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req)
+ return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease)
}
for _, ref := range localRefs {
- err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req)
+ err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease)
if err != nil {
return err
}
@@ -706,7 +708,7 @@ func (r *Remote) addCommit(rs config.RefSpec,
func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference,
- req *packp.ReferenceUpdateRequest) error {
+ req *packp.ReferenceUpdateRequest, forceWithLease *ForceWithLease) error {
if localRef.Type() != plumbing.HashReference {
return nil
@@ -738,7 +740,11 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
return nil
}
- if !rs.IsForceUpdate() {
+ if forceWithLease != nil {
+ if err = r.checkForceWithLease(localRef, cmd, forceWithLease); err != nil {
+ return err
+ }
+ } else if !rs.IsForceUpdate() {
if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
return err
}
@@ -748,6 +754,31 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
return nil
}
+func (r *Remote) checkForceWithLease(localRef *plumbing.Reference, cmd *packp.Command, forceWithLease *ForceWithLease) error {
+ remotePrefix := fmt.Sprintf("refs/remotes/%s/", r.Config().Name)
+
+ ref, err := storer.ResolveReference(
+ r.s,
+ plumbing.ReferenceName(remotePrefix+strings.Replace(localRef.Name().String(), "refs/heads/", "", -1)))
+ if err != nil {
+ return err
+ }
+
+ if forceWithLease.RefName.String() == "" || (forceWithLease.RefName == cmd.Name) {
+ expectedOID := ref.Hash()
+
+ if !forceWithLease.Hash.IsZero() {
+ expectedOID = forceWithLease.Hash
+ }
+
+ if cmd.Old != expectedOID {
+ return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String())
+ }
+ }
+
+ return nil
+}
+
func (r *Remote) references() ([]*plumbing.Reference, error) {
var localRefs []*plumbing.Reference
diff --git a/remote_test.go b/remote_test.go
index df07c08..ebe9a55 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -816,6 +816,133 @@ func (s *RemoteSuite) TestPushForceWithOption(c *C) {
c.Assert(newRef, Not(DeepEquals), oldRef)
}
+func (s *RemoteSuite) TestPushForceWithLease_success(c *C) {
+ testCases := []struct {
+ desc string
+ forceWithLease ForceWithLease
+ }{
+ {
+ desc: "no arguments",
+ forceWithLease: ForceWithLease{},
+ },
+ {
+ desc: "ref name",
+ forceWithLease: ForceWithLease{
+ RefName: plumbing.ReferenceName("refs/heads/branch"),
+ },
+ },
+ {
+ desc: "ref name and sha",
+ forceWithLease: ForceWithLease{
+ RefName: plumbing.ReferenceName("refs/heads/branch"),
+ Hash: plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ c.Log("Executing test cases:", tc.desc)
+
+ f := fixtures.Basic().One()
+ sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
+ dstFs := f.DotGit()
+ dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
+
+ newCommit := plumbing.NewHashReference(
+ "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
+ )
+ c.Assert(sto.SetReference(newCommit), IsNil)
+
+ ref, err := sto.Reference("refs/heads/branch")
+ c.Log(ref.String())
+
+ url := dstFs.Root()
+ r := NewRemote(sto, &config.RemoteConfig{
+ Name: DefaultRemoteName,
+ URLs: []string{url},
+ })
+
+ oldRef, err := dstSto.Reference("refs/heads/branch")
+ c.Assert(err, IsNil)
+ c.Assert(oldRef, NotNil)
+
+ c.Assert(r.Push(&PushOptions{
+ RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"},
+ ForceWithLease: &ForceWithLease{},
+ }), IsNil)
+
+ newRef, err := dstSto.Reference("refs/heads/branch")
+ c.Assert(err, IsNil)
+ c.Assert(newRef, DeepEquals, newCommit)
+ }
+}
+
+func (s *RemoteSuite) TestPushForceWithLease_failure(c *C) {
+ testCases := []struct {
+ desc string
+ forceWithLease ForceWithLease
+ }{
+ {
+ desc: "no arguments",
+ forceWithLease: ForceWithLease{},
+ },
+ {
+ desc: "ref name",
+ forceWithLease: ForceWithLease{
+ RefName: plumbing.ReferenceName("refs/heads/branch"),
+ },
+ },
+ {
+ desc: "ref name and sha",
+ forceWithLease: ForceWithLease{
+ RefName: plumbing.ReferenceName("refs/heads/branch"),
+ Hash: plumbing.NewHash("152175bf7e5580299fa1f0ba41ef6474cc043b70"),
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ c.Log("Executing test cases:", tc.desc)
+
+ f := fixtures.Basic().One()
+ sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
+ c.Assert(sto.SetReference(
+ plumbing.NewHashReference(
+ "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
+ ),
+ ), IsNil)
+
+ dstFs := f.DotGit()
+ dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
+ c.Assert(dstSto.SetReference(
+ plumbing.NewHashReference(
+ "refs/heads/branch", plumbing.NewHash("ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
+ ),
+ ), IsNil)
+
+ url := dstFs.Root()
+ r := NewRemote(sto, &config.RemoteConfig{
+ Name: DefaultRemoteName,
+ URLs: []string{url},
+ })
+
+ oldRef, err := dstSto.Reference("refs/heads/branch")
+ c.Assert(err, IsNil)
+ c.Assert(oldRef, NotNil)
+
+ err = r.Push(&PushOptions{
+ RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"},
+ ForceWithLease: &ForceWithLease{},
+ })
+
+ c.Assert(err, DeepEquals, errors.New("non-fast-forward update: refs/heads/branch"))
+
+ newRef, err := dstSto.Reference("refs/heads/branch")
+ c.Assert(err, IsNil)
+ c.Assert(newRef, Not(DeepEquals), plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"))
+ }
+}
+
func (s *RemoteSuite) TestPushPrune(c *C) {
fs := fixtures.Basic().One().DotGit()
diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go
index 4c2ae94..1a09fde 100644
--- a/storage/filesystem/dotgit/dotgit_test.go
+++ b/storage/filesystem/dotgit/dotgit_test.go
@@ -3,6 +3,7 @@ package dotgit
import (
"bufio"
"encoding/hex"
+ "io"
"io/ioutil"
"os"
"path/filepath"
@@ -510,13 +511,13 @@ func (s *SuiteDotGit) TestObjectPackWithKeepDescriptors(c *C) {
c.Assert(filepath.Ext(pack.Name()), Equals, ".pack")
// Move to an specific offset
- pack.Seek(42, os.SEEK_SET)
+ pack.Seek(42, io.SeekStart)
pack2, err := dir.ObjectPack(plumbing.NewHash(f.PackfileHash))
c.Assert(err, IsNil)
// If the file is the same the offset should be the same
- offset, err := pack2.Seek(0, os.SEEK_CUR)
+ offset, err := pack2.Seek(0, io.SeekCurrent)
c.Assert(err, IsNil)
c.Assert(offset, Equals, int64(42))
@@ -527,7 +528,7 @@ func (s *SuiteDotGit) TestObjectPackWithKeepDescriptors(c *C) {
c.Assert(err, IsNil)
// If the file is opened again its offset should be 0
- offset, err = pack2.Seek(0, os.SEEK_CUR)
+ offset, err = pack2.Seek(0, io.SeekCurrent)
c.Assert(err, IsNil)
c.Assert(offset, Equals, int64(0))
diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go
index 59b40d3..1c3267b 100644
--- a/storage/filesystem/object_test.go
+++ b/storage/filesystem/object_test.go
@@ -71,7 +71,7 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) {
pack1, err := dg.ObjectPack(packfiles[0])
c.Assert(err, IsNil)
- pack1.Seek(42, os.SEEK_SET)
+ pack1.Seek(42, io.SeekStart)
err = o.Close()
c.Assert(err, IsNil)
@@ -79,7 +79,7 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) {
pack2, err := dg.ObjectPack(packfiles[0])
c.Assert(err, IsNil)
- offset, err := pack2.Seek(0, os.SEEK_CUR)
+ offset, err := pack2.Seek(0, io.SeekCurrent)
c.Assert(err, IsNil)
c.Assert(offset, Equals, int64(0))