Merge pull request #2340 from git-lfs/githistory-ref-updater
git/githistory: introduce `*refUpdater` to update references
This commit is contained in:
commit
cdd17d024e
@ -70,6 +70,8 @@ func rewriteOptions(args []string, opts *githistory.RewriteOptions) (*githistory
|
||||
Include: include,
|
||||
Exclude: exclude,
|
||||
|
||||
UpdateRefs: opts.UpdateRefs,
|
||||
|
||||
BlobFn: opts.BlobFn,
|
||||
TreeCallbackFn: opts.TreeCallbackFn,
|
||||
}, nil
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -129,6 +130,20 @@ func AssertCommitTree(t *testing.T, db *odb.ObjectDatabase, sha, tree string) {
|
||||
assert.Equal(t, decoded, commit.TreeID, "git/odb: expected tree ID: %s (got: %x)", tree, commit.TreeID)
|
||||
}
|
||||
|
||||
// AssertRef asserts that a given refname points at the expected commit.
|
||||
func AssertRef(t *testing.T, db *odb.ObjectDatabase, ref string, expected []byte) {
|
||||
root, ok := db.Root()
|
||||
assert.True(t, ok, "git/odb: expected *odb.ObjectDatabase to have Root()")
|
||||
|
||||
cmd := exec.Command("git", "rev-parse", ref)
|
||||
cmd.Dir = root
|
||||
out, err := cmd.Output()
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, hex.EncodeToString(expected), strings.TrimSpace(string(out)))
|
||||
}
|
||||
|
||||
// HexDecode decodes the given ASCII hex-encoded string into []byte's, or fails
|
||||
// the test immediately if the given "sha" wasn't a valid hex-encoded sequence.
|
||||
func HexDecode(t *testing.T, sha string) []byte {
|
||||
|
59
git/githistory/ref_updater.go
Normal file
59
git/githistory/ref_updater.go
Normal file
@ -0,0 +1,59 @@
|
||||
package githistory
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/git-lfs/git-lfs/errors"
|
||||
"github.com/git-lfs/git-lfs/git"
|
||||
"github.com/git-lfs/git-lfs/git/githistory/log"
|
||||
)
|
||||
|
||||
// refUpdater is a type responsible for moving references from one point in the
|
||||
// Git object graph to another.
|
||||
type refUpdater struct {
|
||||
// CacheFn is a function that returns the SHA1 transformation from an
|
||||
// original hash to a new one. It specifies a "bool" return value
|
||||
// signaling whether or not that given "old" SHA1 was migrated.
|
||||
CacheFn func(old []byte) ([]byte, bool)
|
||||
// Logger logs the progress of reference updating.
|
||||
Logger *log.Logger
|
||||
// Refs is a set of *git.Ref's to migrate.
|
||||
Refs []*git.Ref
|
||||
// Root is the given directory on disk in which the repository is
|
||||
// located.
|
||||
Root string
|
||||
}
|
||||
|
||||
// UpdateRefs performs the reference update(s) from existing locations (see:
|
||||
// Refs) to their respective new locations in the graph (see CacheFn).
|
||||
//
|
||||
// It creates reflog entries as well as stderr log entries as it progresses
|
||||
// through the reference updates.
|
||||
//
|
||||
// It returns any error encountered, or nil if the reference update(s) was/were
|
||||
// successful.
|
||||
func (r *refUpdater) UpdateRefs() error {
|
||||
list := r.Logger.List("migrate: Updating refs")
|
||||
defer list.Complete()
|
||||
|
||||
for _, ref := range r.Refs {
|
||||
sha1, err := hex.DecodeString(ref.Sha)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not decode: %q", ref.Sha)
|
||||
}
|
||||
|
||||
to, ok := r.CacheFn(sha1)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := git.UpdateRefIn(r.Root, ref, to, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list.Entry(fmt.Sprintf("%s\t%s -> %x", ref, ref.Sha, to))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
66
git/githistory/ref_updater_test.go
Normal file
66
git/githistory/ref_updater_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
package githistory
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/git-lfs/git-lfs/git"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRefUpdaterMovesRefs(t *testing.T) {
|
||||
db := DatabaseFromFixture(t, "linear-history-with-tags.git")
|
||||
root, _ := db.Root()
|
||||
|
||||
AssertRef(t, db,
|
||||
"refs/tags/middle", HexDecode(t, "228afe30855933151f7a88e70d9d88314fd2f191"))
|
||||
|
||||
updater := &refUpdater{
|
||||
CacheFn: func(old []byte) ([]byte, bool) {
|
||||
return HexDecode(t, "d941e4756add6b06f5bee766fcf669f55419f13f"), true
|
||||
},
|
||||
Refs: []*git.Ref{
|
||||
{
|
||||
Name: "middle",
|
||||
Sha: "228afe30855933151f7a88e70d9d88314fd2f191",
|
||||
Type: git.RefTypeLocalTag,
|
||||
},
|
||||
},
|
||||
Root: root,
|
||||
}
|
||||
|
||||
err := updater.UpdateRefs()
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
AssertRef(t, db,
|
||||
"refs/tags/middle", HexDecode(t, "d941e4756add6b06f5bee766fcf669f55419f13f"))
|
||||
}
|
||||
|
||||
func TestRefUpdaterIgnoresUnovedRefs(t *testing.T) {
|
||||
db := DatabaseFromFixture(t, "linear-history-with-tags.git")
|
||||
root, _ := db.Root()
|
||||
|
||||
AssertRef(t, db,
|
||||
"refs/tags/middle", HexDecode(t, "228afe30855933151f7a88e70d9d88314fd2f191"))
|
||||
|
||||
updater := &refUpdater{
|
||||
CacheFn: func(old []byte) ([]byte, bool) {
|
||||
return nil, false
|
||||
},
|
||||
Refs: []*git.Ref{
|
||||
{
|
||||
Name: "middle",
|
||||
Sha: "228afe30855933151f7a88e70d9d88314fd2f191",
|
||||
Type: git.RefTypeLocalTag,
|
||||
},
|
||||
},
|
||||
Root: root,
|
||||
}
|
||||
|
||||
err := updater.UpdateRefs()
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
AssertRef(t, db,
|
||||
"refs/tags/middle", HexDecode(t, "228afe30855933151f7a88e70d9d88314fd2f191"))
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/git-lfs/git-lfs/errors"
|
||||
"github.com/git-lfs/git-lfs/filepathfilter"
|
||||
"github.com/git-lfs/git-lfs/git"
|
||||
"github.com/git-lfs/git-lfs/git/githistory/log"
|
||||
@ -46,6 +47,11 @@ type RewriteOptions struct {
|
||||
// will be excluded.
|
||||
Exclude []string
|
||||
|
||||
// UpdateRefs specifies whether the Rewriter should move refs from the
|
||||
// original graph onto the migrated one. If true, the refs will be
|
||||
// moved, and a reflog entry will be created.
|
||||
UpdateRefs bool
|
||||
|
||||
// BlobFn specifies a function to rewrite blobs.
|
||||
//
|
||||
// It is called once per unique, unchanged path. That is to say, if
|
||||
@ -233,6 +239,26 @@ func (r *Rewriter) Rewrite(opt *RewriteOptions) ([]byte, error) {
|
||||
tip = rewrittenCommit
|
||||
}
|
||||
|
||||
if opt.UpdateRefs {
|
||||
refs, err := r.refsToMigrate()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not find refs to update")
|
||||
}
|
||||
|
||||
root, _ := r.db.Root()
|
||||
|
||||
updater := &refUpdater{
|
||||
CacheFn: r.uncacheCommit,
|
||||
Logger: r.l,
|
||||
Refs: refs,
|
||||
Root: root,
|
||||
}
|
||||
|
||||
if err := updater.UpdateRefs(); err != nil {
|
||||
return nil, errors.Wrap(err, "could not update refs")
|
||||
}
|
||||
}
|
||||
|
||||
r.l.Close()
|
||||
|
||||
return tip, err
|
||||
@ -365,6 +391,15 @@ func (r *Rewriter) commitsToMigrate(opt *RewriteOptions) ([][]byte, error) {
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
// refsToMigrate returns a list of references to migrate, or an error if loading
|
||||
// those references failed.
|
||||
func (r *Rewriter) refsToMigrate() ([]*git.Ref, error) {
|
||||
if root, ok := r.db.Root(); ok {
|
||||
return git.AllRefsIn(root)
|
||||
}
|
||||
return git.AllRefs()
|
||||
}
|
||||
|
||||
// scannerOpts returns a *git.ScanRefsOptions instance to be given to the
|
||||
// *git.RevListScanner.
|
||||
//
|
||||
|
@ -300,6 +300,40 @@ func TestHistoryRewriterUseOriginalParentsForPartialMigration(t *testing.T) {
|
||||
AssertCommitParent(t, db, hex.EncodeToString(tip), expectedParent)
|
||||
}
|
||||
|
||||
func TestHistoryRewriterUpdatesRefs(t *testing.T) {
|
||||
db := DatabaseFromFixture(t, "linear-history.git")
|
||||
r := NewRewriter(db)
|
||||
|
||||
AssertRef(t, db,
|
||||
"refs/heads/master", HexDecode(t, "e669b63f829bfb0b91fc52a5bcea53dd7977a0ee"))
|
||||
|
||||
tip, err := r.Rewrite(&RewriteOptions{
|
||||
Include: []string{"refs/heads/master"},
|
||||
|
||||
UpdateRefs: true,
|
||||
|
||||
BlobFn: func(path string, b *odb.Blob) (*odb.Blob, error) {
|
||||
suffix := strings.NewReader("_suffix")
|
||||
|
||||
return &odb.Blob{
|
||||
Contents: io.MultiReader(b.Contents, suffix),
|
||||
Size: b.Size + int64(suffix.Len()),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
c1 := hex.EncodeToString(tip)
|
||||
c2 := "66561fe3ae68651658e18e48053dcfe66a2e9da1"
|
||||
c3 := "8268d8486c48024a871fa42fc487dbeabd6e3d86"
|
||||
|
||||
AssertRef(t, db, "refs/heads/master", tip)
|
||||
|
||||
AssertCommitParent(t, db, c1, c2)
|
||||
AssertCommitParent(t, db, c2, c3)
|
||||
}
|
||||
|
||||
func TestHistoryRewriterReturnsFilter(t *testing.T) {
|
||||
f := filepathfilter.New([]string{"a"}, []string{"b"})
|
||||
r := NewRewriter(nil, WithFilter(f))
|
||||
|
Loading…
Reference in New Issue
Block a user