Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Adapted non-force fetching with locking #1

Closed
wants to merge 12 commits into from
Closed
2 changes: 1 addition & 1 deletion _examples/storage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
### and what this means ...
*git* has as very well defined storage system, the `.git` directory, present on any repository. This is the place where `git` stores al the [`objects`](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects), [`references`](https://git-scm.com/book/es/v2/Git-Internals-Git-References) and [`configuration`](https://git-scm.com/docs/git-config#_configuration_file). This information is stored in plain files.

Our original **go-git** version was designed to work in memory, some time after we added support to read the `.git`, and now we have added support for fully customized [storages](https://godoc.org/github.com/src-d/go-git#Storer).
Our original **go-git** version was designed to work in memory, some time after we added support to read the `.git`, and now we have added support for fully customized [storages](https://godoc.org/gopkg.in/src-d/go-git.v4/storage#Storer).

This means that the internal database of any repository can be saved and accessed on any support, databases, distributed filesystems, etc. This functionality is pretty similar to the [libgit2 backends](http://blog.deveo.com/your-git-repository-in-a-database-pluggable-backends-in-libgit2/)

Expand Down
39 changes: 39 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"sort"
"strconv"

format "gopkg.in/src-d/go-git.v4/plumbing/format/config"
)
Expand Down Expand Up @@ -40,6 +41,14 @@ type Config struct {
// Worktree is the path to the root of the working tree.
Worktree string
}

Pack struct {
// Window controls the size of the sliding window for delta
// compression. The default is 10. A value of 0 turns off
// delta compression entirely.
Window uint
}

// Remotes list of repository remotes, the key of the map is the name
// of the remote, should equal to RemoteConfig.Name.
Remotes map[string]*RemoteConfig
Expand Down Expand Up @@ -81,10 +90,14 @@ const (
remoteSection = "remote"
submoduleSection = "submodule"
coreSection = "core"
packSection = "pack"
fetchKey = "fetch"
urlKey = "url"
bareKey = "bare"
worktreeKey = "worktree"
windowKey = "window"

defaultPackWindow = uint(10)
)

// Unmarshal parses a git-config file and stores it.
Expand All @@ -98,6 +111,9 @@ func (c *Config) Unmarshal(b []byte) error {
}

c.unmarshalCore()
if err := c.unmarshalPack(); err != nil {
return err
}
c.unmarshalSubmodules()
return c.unmarshalRemotes()
}
Expand All @@ -111,6 +127,21 @@ func (c *Config) unmarshalCore() {
c.Core.Worktree = s.Options.Get(worktreeKey)
}

func (c *Config) unmarshalPack() error {
s := c.Raw.Section(packSection)
window := s.Options.Get(windowKey)
if window == "" {
c.Pack.Window = defaultPackWindow
} else {
winUint, err := strconv.ParseUint(window, 10, 32)
if err != nil {
return err
}
c.Pack.Window = uint(winUint)
}
return nil
}

func (c *Config) unmarshalRemotes() error {
s := c.Raw.Section(remoteSection)
for _, sub := range s.Subsections {
Expand Down Expand Up @@ -138,6 +169,7 @@ func (c *Config) unmarshalSubmodules() {
// Marshal returns Config encoded as a git-config file.
func (c *Config) Marshal() ([]byte, error) {
c.marshalCore()
c.marshalPack()
c.marshalRemotes()
c.marshalSubmodules()

Expand All @@ -158,6 +190,13 @@ func (c *Config) marshalCore() {
}
}

func (c *Config) marshalPack() {
s := c.Raw.Section(packSection)
if c.Pack.Window != defaultPackWindow {
s.SetOption(windowKey, fmt.Sprintf("%d", c.Pack.Window))
}
}

func (c *Config) marshalRemotes() {
s := c.Raw.Section(remoteSection)
newSubsections := make(format.Subsections, 0, len(c.Remotes))
Expand Down
8 changes: 8 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ func (s *ConfigSuite) TestUnmarshall(c *C) {
input := []byte(`[core]
bare = true
worktree = foo
[pack]
window = 20
[remote "origin"]
url = [email protected]:mcuadros/go-git.git
fetch = +refs/heads/*:refs/remotes/origin/*
Expand All @@ -33,6 +35,7 @@ func (s *ConfigSuite) TestUnmarshall(c *C) {

c.Assert(cfg.Core.IsBare, Equals, true)
c.Assert(cfg.Core.Worktree, Equals, "foo")
c.Assert(cfg.Pack.Window, Equals, uint(20))
c.Assert(cfg.Remotes, HasLen, 2)
c.Assert(cfg.Remotes["origin"].Name, Equals, "origin")
c.Assert(cfg.Remotes["origin"].URLs, DeepEquals, []string{"[email protected]:mcuadros/go-git.git"})
Expand All @@ -51,6 +54,8 @@ func (s *ConfigSuite) TestMarshall(c *C) {
output := []byte(`[core]
bare = true
worktree = bar
[pack]
window = 20
[remote "alt"]
url = [email protected]:mcuadros/go-git.git
url = [email protected]:src-d/go-git.git
Expand All @@ -65,6 +70,7 @@ func (s *ConfigSuite) TestMarshall(c *C) {
cfg := NewConfig()
cfg.Core.IsBare = true
cfg.Core.Worktree = "bar"
cfg.Pack.Window = 20
cfg.Remotes["origin"] = &RemoteConfig{
Name: "origin",
URLs: []string{"[email protected]:mcuadros/go-git.git"},
Expand Down Expand Up @@ -92,6 +98,8 @@ func (s *ConfigSuite) TestUnmarshallMarshall(c *C) {
bare = true
worktree = foo
custom = ignored
[pack]
window = 20
[remote "origin"]
url = [email protected]:mcuadros/go-git.git
fetch = +refs/heads/*:refs/remotes/origin/*
Expand Down
6 changes: 6 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ type PullOptions struct {
// stored, if nil nothing is stored and the capability (if supported)
// no-progress, is sent to the server to avoid send this information.
Progress sideband.Progress
// Force allows the pull to update a local branch even when the remote
// branch does not descend from it.
Force bool
}

// Validate validates the fields and sets the default values.
Expand Down Expand Up @@ -142,6 +145,9 @@ type FetchOptions struct {
// Tags describe how the tags will be fetched from the remote repository,
// by default is TagFollowing.
Tags TagMode
// Force allows the fetch to update a local branch even when the remote
// branch does not descend from it.
Force bool
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is from someone else's PR, but I don't think this is strictly needed because the RefSpecs above already have an IsForce() method that says whether that particular ref should be forced or not. (For PullOptions, the default RefSpec is defined in the RemoteConfig.) Could you please update it to use the IsForce method instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now when actually doing this, are you sure we want this? CheckoutOptions and PullOptions have the Force bool in addition to FetchOptions. Removing it from one but not the others would feel inconsistent.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can keep the Force bool if you want, but in that case please also add support for the IsForce() method. The git remote protocol supports both force and non-force fetches in the same batch, so we'll need to be able to specify the force on individual refs.

}

// Validate validates the fields and sets the default values.
Expand Down
47 changes: 35 additions & 12 deletions plumbing/format/packfile/delta_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import (
)

const (
// How far back in the sorted list to search for deltas. 10 is
// the default in command line git.
deltaWindowSize = 10
// deltas based on deltas, how many steps we can do.
// 50 is the default value used in JGit
maxDepth = int64(50)
Expand All @@ -31,14 +28,24 @@ func newDeltaSelector(s storer.EncodedObjectStorer) *deltaSelector {
return &deltaSelector{s}
}

// ObjectsToPack creates a list of ObjectToPack from the hashes provided,
// creating deltas if it's suitable, using an specific internal logic
func (dw *deltaSelector) ObjectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack, error) {
otp, err := dw.objectsToPack(hashes)
// ObjectsToPack creates a list of ObjectToPack from the hashes
// provided, creating deltas if it's suitable, using an specific
// internal logic. `packWindow` specifies the size of the sliding
// window used to compare objects for delta compression; 0 turns off
// delta compression entirely.
func (dw *deltaSelector) ObjectsToPack(
hashes []plumbing.Hash,
packWindow uint,
) ([]*ObjectToPack, error) {
otp, err := dw.objectsToPack(hashes, packWindow)
if err != nil {
return nil, err
}

if packWindow == 0 {
return otp, nil
}

dw.sort(otp)

var objectGroups [][]*ObjectToPack
Expand All @@ -60,7 +67,7 @@ func (dw *deltaSelector) ObjectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack,
objs := objs
wg.Add(1)
go func() {
if walkErr := dw.walk(objs); walkErr != nil {
if walkErr := dw.walk(objs, packWindow); walkErr != nil {
once.Do(func() {
err = walkErr
})
Expand All @@ -77,10 +84,19 @@ func (dw *deltaSelector) ObjectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack,
return otp, nil
}

func (dw *deltaSelector) objectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack, error) {
func (dw *deltaSelector) objectsToPack(
hashes []plumbing.Hash,
packWindow uint,
) ([]*ObjectToPack, error) {
var objectsToPack []*ObjectToPack
for _, h := range hashes {
o, err := dw.encodedDeltaObject(h)
var o plumbing.EncodedObject
var err error
if packWindow == 0 {
o, err = dw.encodedObject(h)
} else {
o, err = dw.encodedDeltaObject(h)
}
if err != nil {
return nil, err
}
Expand All @@ -93,6 +109,10 @@ func (dw *deltaSelector) objectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack,
objectsToPack = append(objectsToPack, otp)
}

if packWindow == 0 {
return objectsToPack, nil
}

if err := dw.fixAndBreakChains(objectsToPack); err != nil {
return nil, err
}
Expand Down Expand Up @@ -201,7 +221,10 @@ func (dw *deltaSelector) sort(objectsToPack []*ObjectToPack) {
sort.Sort(byTypeAndSize(objectsToPack))
}

func (dw *deltaSelector) walk(objectsToPack []*ObjectToPack) error {
func (dw *deltaSelector) walk(
objectsToPack []*ObjectToPack,
packWindow uint,
) error {
indexMap := make(map[plumbing.Hash]*deltaIndex)
for i := 0; i < len(objectsToPack); i++ {
target := objectsToPack[i]
Expand All @@ -218,7 +241,7 @@ func (dw *deltaSelector) walk(objectsToPack []*ObjectToPack) error {
continue
}

for j := i - 1; j >= 0 && i-j < deltaWindowSize; j-- {
for j := i - 1; j >= 0 && i-j < int(packWindow); j-- {
base := objectsToPack[j]
// Objects must use only the same type as their delta base.
// Since objectsToPack is sorted by type and size, once we find
Expand Down
31 changes: 22 additions & 9 deletions plumbing/format/packfile/delta_selector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,31 +146,32 @@ func (s *DeltaSelectorSuite) createTestObjects() {
func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
// Different type
hashes := []plumbing.Hash{s.hashes["base"], s.hashes["treeType"]}
otp, err := s.ds.ObjectsToPack(hashes)
deltaWindowSize := uint(10)
otp, err := s.ds.ObjectsToPack(hashes, deltaWindowSize)
c.Assert(err, IsNil)
c.Assert(len(otp), Equals, 2)
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["base"]])
c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["treeType"]])

// Size radically different
hashes = []plumbing.Hash{s.hashes["bigBase"], s.hashes["target"]}
otp, err = s.ds.ObjectsToPack(hashes)
otp, err = s.ds.ObjectsToPack(hashes, deltaWindowSize)
c.Assert(err, IsNil)
c.Assert(len(otp), Equals, 2)
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["bigBase"]])
c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["target"]])

// Delta Size Limit with no best delta yet
hashes = []plumbing.Hash{s.hashes["smallBase"], s.hashes["smallTarget"]}
otp, err = s.ds.ObjectsToPack(hashes)
otp, err = s.ds.ObjectsToPack(hashes, deltaWindowSize)
c.Assert(err, IsNil)
c.Assert(len(otp), Equals, 2)
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["smallBase"]])
c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["smallTarget"]])

// It will create the delta
hashes = []plumbing.Hash{s.hashes["base"], s.hashes["target"]}
otp, err = s.ds.ObjectsToPack(hashes)
otp, err = s.ds.ObjectsToPack(hashes, deltaWindowSize)
c.Assert(err, IsNil)
c.Assert(len(otp), Equals, 2)
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["target"]])
Expand All @@ -185,7 +186,7 @@ func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
s.hashes["o2"],
s.hashes["o3"],
}
otp, err = s.ds.ObjectsToPack(hashes)
otp, err = s.ds.ObjectsToPack(hashes, deltaWindowSize)
c.Assert(err, IsNil)
c.Assert(len(otp), Equals, 3)
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["o1"]])
Expand All @@ -201,20 +202,32 @@ func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
// a delta.
hashes = make([]plumbing.Hash, 0, deltaWindowSize+2)
hashes = append(hashes, s.hashes["base"])
for i := 0; i < deltaWindowSize; i++ {
for i := uint(0); i < deltaWindowSize; i++ {
hashes = append(hashes, s.hashes["smallTarget"])
}
hashes = append(hashes, s.hashes["target"])

// Don't sort so we can easily check the sliding window without
// creating a bunch of new objects.
otp, err = s.ds.objectsToPack(hashes)
otp, err = s.ds.objectsToPack(hashes, deltaWindowSize)
c.Assert(err, IsNil)
err = s.ds.walk(otp)
err = s.ds.walk(otp, deltaWindowSize)
c.Assert(err, IsNil)
c.Assert(len(otp), Equals, deltaWindowSize+2)
c.Assert(len(otp), Equals, int(deltaWindowSize)+2)
targetIdx := len(otp) - 1
c.Assert(otp[targetIdx].IsDelta(), Equals, false)

// Check that no deltas are created, and the objects are unsorted,
// if compression is off.
hashes = []plumbing.Hash{s.hashes["base"], s.hashes["target"]}
otp, err = s.ds.ObjectsToPack(hashes, 0)
c.Assert(err, IsNil)
c.Assert(len(otp), Equals, 2)
c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["base"]])
c.Assert(otp[0].IsDelta(), Equals, false)
c.Assert(otp[1].Original, Equals, s.store.Objects[s.hashes["target"]])
c.Assert(otp[1].IsDelta(), Equals, false)
c.Assert(otp[1].Depth, Equals, 0)
}

func (s *DeltaSelectorSuite) TestMaxDepth(c *C) {
Expand Down
23 changes: 14 additions & 9 deletions plumbing/format/packfile/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import (
// Encoder gets the data from the storage and write it into the writer in PACK
// format
type Encoder struct {
selector *deltaSelector
w *offsetWriter
zw *zlib.Writer
hasher plumbing.Hasher
selector *deltaSelector
w *offsetWriter
zw *zlib.Writer
hasher plumbing.Hasher
// offsets is a map of object hashes to corresponding offsets in the packfile.
// It is used to determine offset of the base of a delta when a OFS_DELTA is
// used.
Expand Down Expand Up @@ -45,10 +45,15 @@ func NewEncoder(w io.Writer, s storer.EncodedObjectStorer, useRefDeltas bool) *E
}
}

// Encode creates a packfile containing all the objects referenced in hashes
// and writes it to the writer in the Encoder.
func (e *Encoder) Encode(hashes []plumbing.Hash) (plumbing.Hash, error) {
objects, err := e.selector.ObjectsToPack(hashes)
// Encode creates a packfile containing all the objects referenced in
// hashes and writes it to the writer in the Encoder. `packWindow`
// specifies the size of the sliding window used to compare objects
// for delta compression; 0 turns off delta compression entirely.
func (e *Encoder) Encode(
hashes []plumbing.Hash,
packWindow uint,
) (plumbing.Hash, error) {
objects, err := e.selector.ObjectsToPack(hashes, packWindow)
if err != nil {
return plumbing.ZeroHash, err
}
Expand Down Expand Up @@ -137,7 +142,7 @@ func (e *Encoder) writeOfsDeltaHeader(deltaOffset int64, base plumbing.Hash) err

// for OFS_DELTA, offset of the base is interpreted as negative offset
// relative to the type-byte of the header of the ofs-delta entry.
relativeOffset := deltaOffset-baseOffset
relativeOffset := deltaOffset - baseOffset
if relativeOffset <= 0 {
return fmt.Errorf("bad offset for OFS_DELTA entry: %d", relativeOffset)
}
Expand Down
Loading