Merge branch 'master' into git-odb-no-extraline

This commit is contained in:
Taylor Blau 2017-12-14 12:29:44 -05:00
commit aa4f5fe021
35 changed files with 543 additions and 2 deletions

@ -121,6 +121,12 @@ func includeExcludeRefs(l *tasklog.Logger, args []string) (include, exclude []st
}
for _, name := range args {
var excluded bool
if strings.HasPrefix("^", name) {
name = name[1:]
excluded = true
}
// Then, loop through each branch given, resolve that reference,
// and include it.
ref, err := git.ResolveRef(name)
@ -128,7 +134,11 @@ func includeExcludeRefs(l *tasklog.Logger, args []string) (include, exclude []st
return nil, nil, err
}
include = append(include, ref.Refspec())
if excluded {
exclude = append(exclude, ref.Refspec())
} else {
include = append(include, ref.Refspec())
}
}
if hardcore {

@ -39,6 +39,9 @@ git-lfs-migrate(1) - Migrate history to or from git-lfs
Migrate only the set of branches listed. If not given, `git-lfs-migrate(1)`
will migrate the currently checked out branch.
References beginning with '^' will be excluded, whereas branches that do not
begin with '^' will be included.
If any of `--include-ref` or `--exclude-ref` are given, the checked out
branch will not be appended, but branches given explicitly will be appended.

@ -0,0 +1 @@
ref: refs/heads/master

@ -0,0 +1,7 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true

@ -0,0 +1,3 @@
0000000000000000000000000000000000000000 91b85be6928569390e937479509b80a1d0dccb0c Taylor Blau <me@ttaylorr.com> 1496954196 -0600 commit (initial): some.txt: a
91b85be6928569390e937479509b80a1d0dccb0c 228afe30855933151f7a88e70d9d88314fd2f191 Taylor Blau <me@ttaylorr.com> 1496954207 -0600 commit: some.txt: b
228afe30855933151f7a88e70d9d88314fd2f191 d941e4756add6b06f5bee766fcf669f55419f13f Taylor Blau <me@ttaylorr.com> 1496954214 -0600 commit: some.txt: c

@ -0,0 +1,3 @@
0000000000000000000000000000000000000000 91b85be6928569390e937479509b80a1d0dccb0c Taylor Blau <me@ttaylorr.com> 1496954196 -0600 commit (initial): some.txt: a
91b85be6928569390e937479509b80a1d0dccb0c 228afe30855933151f7a88e70d9d88314fd2f191 Taylor Blau <me@ttaylorr.com> 1496954207 -0600 commit: some.txt: b
228afe30855933151f7a88e70d9d88314fd2f191 d941e4756add6b06f5bee766fcf669f55419f13f Taylor Blau <me@ttaylorr.com> 1496954214 -0600 commit: some.txt: c

@ -0,0 +1 @@
d941e4756add6b06f5bee766fcf669f55419f13f

@ -0,0 +1 @@
05797a38b05f910e6efe40dc1a5c0a046a9403e8

@ -7,6 +7,7 @@ import (
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/git/odb"
"github.com/git-lfs/git-lfs/tasklog"
"github.com/git-lfs/git-lfs/tools"
)
@ -25,6 +26,8 @@ type refUpdater struct {
// Root is the given directory on disk in which the repository is
// located.
Root string
db *odb.ObjectDatabase
}
// UpdateRefs performs the reference update(s) from existing locations (see:
@ -51,6 +54,36 @@ func (r *refUpdater) UpdateRefs() error {
}
to, ok := r.CacheFn(sha1)
if ref.Type == git.RefTypeLocalTag {
tag, _ := r.db.Tag(sha1)
if tag != nil && tag.ObjectType == odb.CommitObjectType {
// Assume that a non-nil error is an indication
// that the tag is bare (without annotation).
toObj, okObj := r.CacheFn(tag.Object)
if !okObj {
continue
}
newTag, err := r.db.WriteTag(&odb.Tag{
Object: toObj,
ObjectType: tag.ObjectType,
Name: tag.Name,
Tagger: tag.Tagger,
Message: tag.Message,
})
if err != nil {
return errors.Wrapf(err, "could not rewrite tag: %s", tag.Name)
}
to = newTag
ok = true
}
}
if !ok {
continue
}

@ -26,6 +26,7 @@ func TestRefUpdaterMovesRefs(t *testing.T) {
},
},
Root: root,
db: db,
}
err := updater.UpdateRefs()
@ -36,6 +37,36 @@ func TestRefUpdaterMovesRefs(t *testing.T) {
"refs/tags/middle", HexDecode(t, "d941e4756add6b06f5bee766fcf669f55419f13f"))
}
func TestRefUpdaterMovesRefsWithAnnotatedTags(t *testing.T) {
db := DatabaseFromFixture(t, "linear-history-with-annotated-tags.git")
root, _ := db.Root()
AssertRef(t, db,
"refs/tags/middle", HexDecode(t, "05797a38b05f910e6efe40dc1a5c0a046a9403e8"))
updater := &refUpdater{
CacheFn: func(old []byte) ([]byte, bool) {
return HexDecode(t, "d941e4756add6b06f5bee766fcf669f55419f13f"), true
},
Refs: []*git.Ref{
{
Name: "middle",
Sha: "05797a38b05f910e6efe40dc1a5c0a046a9403e8",
Type: git.RefTypeLocalTag,
},
},
Root: root,
db: db,
}
err := updater.UpdateRefs()
assert.NoError(t, err)
AssertRef(t, db,
"refs/tags/middle", HexDecode(t, "6873f9b24037dade0bd1d8a17b0913bf9a6a4f12"))
}
func TestRefUpdaterIgnoresUnovedRefs(t *testing.T) {
db := DatabaseFromFixture(t, "linear-history-with-tags.git")
root, _ := db.Root()
@ -55,6 +86,7 @@ func TestRefUpdaterIgnoresUnovedRefs(t *testing.T) {
},
},
Root: root,
db: db,
}
err := updater.UpdateRefs()

@ -280,6 +280,8 @@ func (r *Rewriter) Rewrite(opt *RewriteOptions) ([]byte, error) {
Logger: r.l,
Refs: refs,
Root: root,
db: r.db,
}
if err := updater.UpdateRefs(); err != nil {

@ -100,7 +100,7 @@ func (c *Commit) Decode(from io.Reader, size int64) (n int, err error) {
text := s.Text()
n = n + len(text+"\n")
if len(s.Text()) == 0 {
if len(s.Text()) == 0 && !finishedHeaders {
finishedHeaders = true
continue
}

@ -117,6 +117,33 @@ func TestCommitDecodingWithMessageKeywordPrefix(t *testing.T) {
treeId := []byte("aaaaaaaaaaaaaaaaaaaa")
treeIdAscii := hex.EncodeToString(treeId)
from := new(bytes.Buffer)
fmt.Fprintf(from, "author %s\n", author)
fmt.Fprintf(from, "committer %s\n", committer)
fmt.Fprintf(from, "tree %s\n", hex.EncodeToString(treeId))
fmt.Fprintf(from, "\nfirst line\n\nsecond line\n")
flen := from.Len()
commit := new(Commit)
n, err := commit.Decode(from, int64(flen))
assert.NoError(t, err)
assert.Equal(t, flen, n)
assert.Equal(t, author.String(), commit.Author)
assert.Equal(t, committer.String(), commit.Committer)
assert.Equal(t, treeIdAscii, hex.EncodeToString(commit.TreeID))
assert.Equal(t, "first line\n\nsecond line", commit.Message)
}
func TestCommitDecodingWithWhitespace(t *testing.T) {
author := &Signature{Name: "John Doe", Email: "john@example.com", When: time.Now()}
committer := &Signature{Name: "Jane Doe", Email: "jane@example.com", When: time.Now()}
treeId := []byte("aaaaaaaaaaaaaaaaaaaa")
treeIdAscii := hex.EncodeToString(treeId)
from := new(bytes.Buffer)
fmt.Fprintf(from, "author %s\n", author)
fmt.Fprintf(from, "committer %s\n", committer)

@ -96,6 +96,17 @@ func (o *ObjectDatabase) Commit(sha []byte) (*Commit, error) {
return &c, nil
}
// Tag returns a *Tag as identified by the SHA given, or an error if one was
// encountered.
func (o *ObjectDatabase) Tag(sha []byte) (*Tag, error) {
var t Tag
if err := o.decode(sha, &t); err != nil {
return nil, err
}
return &t, nil
}
// WriteBlob stores a *Blob on disk and returns the SHA it is uniquely
// identified by, or an error if one was encountered.
func (o *ObjectDatabase) WriteBlob(b *Blob) ([]byte, error) {
@ -137,6 +148,16 @@ func (o *ObjectDatabase) WriteCommit(c *Commit) ([]byte, error) {
return sha, nil
}
// WriteTag stores a *Tag on disk and returns the SHA it is uniquely identified
// by, or an error if one was encountered.
func (o *ObjectDatabase) WriteTag(t *Tag) ([]byte, error) {
sha, _, err := o.encode(t)
if err != nil {
return nil, err
}
return sha, nil
}
// Root returns the filesystem root that this *ObjectDatabase works within, if
// backed by a fileStorer (constructed by FromFilesystem). If so, it returns
// the fully-qualified path on a disk and a value of true.

@ -166,6 +166,57 @@ func TestWriteCommit(t *testing.T) {
assert.NotNil(t, fs.fs[hex.EncodeToString(sha)])
}
func TestDecodeTag(t *testing.T) {
const sha = "7639ba293cd2c457070e8446ecdea56682af0f48"
tagShaHex, err := hex.DecodeString(sha)
var buf bytes.Buffer
zw := zlib.NewWriter(&buf)
fmt.Fprintf(zw, "tag 165\x00")
fmt.Fprintf(zw, "object 6161616161616161616161616161616161616161\n")
fmt.Fprintf(zw, "type commit\n")
fmt.Fprintf(zw, "tag v2.4.0\n")
fmt.Fprintf(zw, "tagger A U Thor <author@example.com>\n")
fmt.Fprintf(zw, "\n")
fmt.Fprintf(zw, "The quick brown fox jumps over the lazy dog.\n")
zw.Close()
odb := &ObjectDatabase{s: newMemoryStorer(map[string]io.ReadWriter{
sha: &buf,
})}
tag, err := odb.Tag(tagShaHex)
assert.Nil(t, err)
assert.Equal(t, []byte("aaaaaaaaaaaaaaaaaaaa"), tag.Object)
assert.Equal(t, CommitObjectType, tag.ObjectType)
assert.Equal(t, "v2.4.0", tag.Name)
assert.Equal(t, "A U Thor <author@example.com>", tag.Tagger)
assert.Equal(t, "The quick brown fox jumps over the lazy dog.", tag.Message)
}
func TestWriteTag(t *testing.T) {
fs := newMemoryStorer(make(map[string]io.ReadWriter))
odb := &ObjectDatabase{s: fs}
sha, err := odb.WriteTag(&Tag{
Object: []byte("aaaaaaaaaaaaaaaaaaaa"),
ObjectType: CommitObjectType,
Name: "v2.4.0",
Tagger: "A U Thor <author@example.com>",
Message: "The quick brown fox jumps over the lazy dog.",
})
expected := "b0ea0039d536fb739dfa44e74e488b635bbb3a86"
assert.Nil(t, err)
assert.Equal(t, expected, hex.EncodeToString(sha))
assert.NotNil(t, fs.fs[hex.EncodeToString(sha)])
}
func TestReadingAMissingObjectAfterClose(t *testing.T) {
sha, _ := hex.DecodeString("af5626b4a114abcb82d63db7c8082c3c4756e51b")

@ -11,6 +11,7 @@ const (
BlobObjectType
TreeObjectType
CommitObjectType
TagObjectType
)
// ObjectTypeFromString converts from a given string to an ObjectType
@ -23,6 +24,8 @@ func ObjectTypeFromString(s string) ObjectType {
return TreeObjectType
case "commit":
return CommitObjectType
case "tag":
return TagObjectType
default:
return UnknownObjectType
}
@ -40,6 +43,8 @@ func (t ObjectType) String() string {
return "tree"
case CommitObjectType:
return "commit"
case TagObjectType:
return "tag"
}
return "<unknown>"
}

@ -12,6 +12,7 @@ func TestObjectTypeFromString(t *testing.T) {
"blob": BlobObjectType,
"tree": TreeObjectType,
"commit": CommitObjectType,
"tag": TagObjectType,
"something else": UnknownObjectType,
} {
t.Run(str, func(t *testing.T) {
@ -25,6 +26,7 @@ func TestObjectTypeToString(t *testing.T) {
BlobObjectType: "blob",
TreeObjectType: "tree",
CommitObjectType: "commit",
TagObjectType: "tag",
UnknownObjectType: "unknown",
ObjectType(math.MaxUint8): "<unknown>",
} {

118
git/odb/tag.go Normal file

@ -0,0 +1,118 @@
package odb
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"io"
"strings"
"github.com/git-lfs/git-lfs/errors"
)
type Tag struct {
Object []byte
ObjectType ObjectType
Name string
Tagger string
Message string
}
// Decode implements Object.Decode and decodes the uncompressed tag being
// read. It returns the number of uncompressed bytes being consumed off of the
// stream, which should be strictly equal to the size given.
//
// If any error was encountered along the way it will be returned, and the
// receiving *Tag is considered invalid.
func (t *Tag) Decode(r io.Reader, size int64) (int, error) {
scanner := bufio.NewScanner(io.LimitReader(r, size))
var (
finishedHeaders bool
message []string
)
for scanner.Scan() {
if finishedHeaders {
message = append(message, scanner.Text())
} else {
if len(scanner.Bytes()) == 0 {
finishedHeaders = true
continue
}
parts := strings.SplitN(scanner.Text(), " ", 2)
if len(parts) < 2 {
return 0, errors.Errorf("git/odb: invalid tag header: %s", scanner.Text())
}
switch parts[0] {
case "object":
sha, err := hex.DecodeString(parts[1])
if err != nil {
return 0, errors.Wrap(err, "git/odb: unable to decode SHA-1")
}
t.Object = sha
case "type":
t.ObjectType = ObjectTypeFromString(parts[1])
case "tag":
t.Name = parts[1]
case "tagger":
t.Tagger = parts[1]
default:
return 0, errors.Errorf("git/odb: unknown tag header: %s", parts[0])
}
}
}
if err := scanner.Err(); err != nil {
return 0, err
}
t.Message = strings.Join(message, "\n")
return int(size), nil
}
// Encode encodes the Tag's contents to the given io.Writer, "w". If there was
// any error copying the Tag's contents, that error will be returned.
//
// Otherwise, the number of bytes written will be returned.
func (t *Tag) Encode(w io.Writer) (int, error) {
headers := []string{
fmt.Sprintf("object %s", hex.EncodeToString(t.Object)),
fmt.Sprintf("type %s", t.ObjectType),
fmt.Sprintf("tag %s", t.Name),
fmt.Sprintf("tagger %s", t.Tagger),
}
return fmt.Fprintf(w, "%s\n\n%s\n", strings.Join(headers, "\n"), t.Message)
}
// Equal returns whether the receiving and given Tags are equal, or in other
// words, whether they are represented by the same SHA-1 when saved to the
// object database.
func (t *Tag) Equal(other *Tag) bool {
if (t == nil) != (other == nil) {
return false
}
if t != nil {
return bytes.Equal(t.Object, other.Object) &&
t.ObjectType == other.ObjectType &&
t.Name == other.Name &&
t.Tagger == other.Tagger &&
t.Message == other.Message
}
return true
}
// Type implements Object.ObjectType by returning the correct object type for
// Tags, TagObjectType.
func (t *Tag) Type() ObjectType {
return TagObjectType
}

65
git/odb/tag_test.go Normal file

@ -0,0 +1,65 @@
package odb
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTagTypeReturnsCorrectObjectType(t *testing.T) {
assert.Equal(t, TagObjectType, new(Tag).Type())
}
func TestTagEncode(t *testing.T) {
tag := &Tag{
Object: []byte("aaaaaaaaaaaaaaaaaaaa"),
ObjectType: CommitObjectType,
Name: "v2.4.0",
Tagger: "A U Thor <author@example.com>",
Message: "The quick brown fox jumps over the lazy dog.",
}
buf := new(bytes.Buffer)
n, err := tag.Encode(buf)
assert.Nil(t, err)
assert.EqualValues(t, buf.Len(), n)
assertLine(t, buf, "object 6161616161616161616161616161616161616161")
assertLine(t, buf, "type commit")
assertLine(t, buf, "tag v2.4.0")
assertLine(t, buf, "tagger A U Thor <author@example.com>")
assertLine(t, buf, "")
assertLine(t, buf, "The quick brown fox jumps over the lazy dog.")
assert.Equal(t, 0, buf.Len())
}
func TestTagDecode(t *testing.T) {
from := new(bytes.Buffer)
fmt.Fprintf(from, "object 6161616161616161616161616161616161616161\n")
fmt.Fprintf(from, "type commit\n")
fmt.Fprintf(from, "tag v2.4.0\n")
fmt.Fprintf(from, "tagger A U Thor <author@example.com>\n")
fmt.Fprintf(from, "\n")
fmt.Fprintf(from, "The quick brown fox jumps over the lazy dog.\n")
flen := from.Len()
tag := new(Tag)
n, err := tag.Decode(from, int64(flen))
assert.Nil(t, err)
assert.Equal(t, n, flen)
assert.Equal(t, []byte("aaaaaaaaaaaaaaaaaaaa"), tag.Object)
assert.Equal(t, CommitObjectType, tag.ObjectType)
assert.Equal(t, "v2.4.0", tag.Name)
assert.Equal(t, "A U Thor <author@example.com>", tag.Tagger)
assert.Equal(t, "The quick brown fox jumps over the lazy dog.", tag.Message)
}

@ -159,6 +159,67 @@ setup_multiple_remote_branches() {
git checkout master
}
# setup_single_local_branch_with_tags creates a repository as follows:
#
# A---B
# |\
# | refs/heads/master
# |
# \
# refs/tags/v1.0.0
#
# - Commit 'A' has 1 byte of data in 'a.txt'
# - Commit 'B' has 2 bytes of data in 'a.txt', and is tagged at 'v1.0.0'.
setup_single_local_branch_with_tags() {
set -e
reponame="migrate-single-local-branch-tags"
remove_and_create_local_repo "$reponame"
base64 < /dev/urandom | head -c 1 > a.txt
git add a.txt
git commit -m "initial commit"
base64 < /dev/urandom | head -c 2 > a.txt
git add a.txt
git commit -m "secondary commit"
git tag "v1.0.0"
}
# setup_single_local_branch_with_annotated_tags creates a repository as follows:
#
# A---B
# |\
# | refs/heads/master
# |
# \
# refs/tags/v1.0.0 (annotated)
#
# - Commit 'A' has 1 byte of data in 'a.txt'
# - Commit 'B' has 2 bytes of data in 'a.txt', and is tagged (with annotation)
# at 'v1.0.0'.
setup_single_local_branch_with_annotated_tags() {
set -e
reponame="migrate-single-local-branch-annotated-tags"
remove_and_create_local_repo "$reponame"
base64 < /dev/urandom | head -c 1 > a.txt
git add a.txt
git commit -m "initial commit"
base64 < /dev/urandom | head -c 2 > a.txt
git add a.txt
git commit -m "secondary commit"
git tag "v1.0.0" -m "v1.0.0"
}
# setup_single_local_branch_deep_trees creates a repository as follows:
#
# A

@ -253,6 +253,40 @@ begin_test "migrate import (given ref, --skip-fetch)"
)
end_test
begin_test "migrate import (un-annotated tags)"
(
set -e
setup_single_local_branch_with_tags
txt_master_oid="$(calc_oid "$(git cat-file -p "refs/heads/master:a.txt")")"
git lfs migrate import --everything
assert_pointer "refs/heads/master" "a.txt" "$txt_master_oid" "2"
assert_local_object "$txt_master_oid" "2"
git tag --points-at "$(git rev-parse HEAD)" | grep -q "v1.0.0"
)
end_test
begin_test "migrate import (annotated tags)"
(
set -e
setup_single_local_branch_with_annotated_tags
txt_master_oid="$(calc_oid "$(git cat-file -p "refs/heads/master:a.txt")")"
git lfs migrate import --everything
assert_pointer "refs/heads/master" "a.txt" "$txt_master_oid" "2"
assert_local_object "$txt_master_oid" "2"
git tag --points-at "$(git rev-parse HEAD)" | grep -q "v1.0.0"
)
end_test
begin_test "migrate import (include/exclude ref)"
(
set -e
@ -293,6 +327,44 @@ begin_test "migrate import (include/exclude ref)"
)
end_test
begin_test "migrate import (include/exclude ref args)"
(
set -e
setup_multiple_remote_branches
md_master_oid="$(calc_oid "$(git cat-file -p "refs/heads/master:a.md")")"
md_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/master:a.md")")"
md_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.md")")"
txt_master_oid="$(calc_oid "$(git cat-file -p "refs/heads/master:a.txt")")"
txt_remote_oid="$(calc_oid "$(git cat-file -p "refs/remotes/origin/master:a.txt")")"
txt_feature_oid="$(calc_oid "$(git cat-file -p "refs/heads/my-feature:a.txt")")"
git lfs migrate import my-feature ^master
assert_pointer "refs/heads/my-feature" "a.md" "$md_feature_oid" "31"
assert_pointer "refs/heads/my-feature" "a.txt" "$txt_feature_oid" "30"
assert_local_object "$md_feature_oid" "31"
refute_local_object "$md_master_oid" "21"
assert_local_object "$txt_feature_oid" "30"
refute_local_object "$txt_master_oid" "20"
refute_local_object "$md_remote_oid" "11"
refute_local_object "$txt_remote_oid" "10"
master="$(git rev-parse refs/heads/master)"
feature="$(git rev-parse refs/heads/my-feature)"
remote="$(git rev-parse refs/remotes/origin/master)"
[ ! $(git cat-file -p "$master:.gitattributes") ]
[ ! $(git cat-file -p "$remote:.gitattributes") ]
feature_attrs="$(git cat-file -p "$feature:.gitattributes")"
echo "$feature_attrs" | grep -q "*.md filter=lfs diff=lfs merge=lfs"
echo "$feature_attrs" | grep -q "*.txt filter=lfs diff=lfs merge=lfs"
)
end_test
begin_test "migrate import (include/exclude ref with filter)"
(
set -e

@ -193,6 +193,29 @@ begin_test "migrate info (include/exclude ref)"
)
end_test
begin_test "migrate info (include/exclude ref args)"
(
set -e
setup_multiple_remote_branches
original_master="$(git rev-parse refs/heads/master)"
original_feature="$(git rev-parse refs/heads/my-feature)"
diff -u <(git lfs migrate info \
my-feature ^master 2>&1 | tail -n 2) <(cat <<-EOF
*.md 31 B 1/1 files(s) 100%
*.txt 30 B 1/1 files(s) 100%
EOF)
migrated_master="$(git rev-parse refs/heads/master)"
migrated_feature="$(git rev-parse refs/heads/my-feature)"
assert_ref_unmoved "refs/heads/master" "$original_master" "$migrated_master"
assert_ref_unmoved "refs/heads/my-feature" "$original_feature" "$migrated_feature"
)
end_test
begin_test "migrate info (include/exclude ref with filter)"
(
set -e