From 1e239fe6991ac94c8e41043a6a2232792fba621c Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:20:57 -0800 Subject: [PATCH 01/10] git/odb: add new ObjectType for annotated tags --- git/odb/object_type.go | 5 +++++ git/odb/object_type_test.go | 2 ++ 2 files changed, 7 insertions(+) diff --git a/git/odb/object_type.go b/git/odb/object_type.go index d2834201..eb256216 100644 --- a/git/odb/object_type.go +++ b/git/odb/object_type.go @@ -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 "" } diff --git a/git/odb/object_type_test.go b/git/odb/object_type_test.go index b65f70a4..030109b6 100644 --- a/git/odb/object_type_test.go +++ b/git/odb/object_type_test.go @@ -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): "", } { From b78fa93988bff45515c235ae7cd3a1ed8e5c9818 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:21:13 -0800 Subject: [PATCH 02/10] git/odb: add implementation of annotated Tag object --- git/odb/tag.go | 118 ++++++++++++++++++++++++++++++++++++++++++++ git/odb/tag_test.go | 65 ++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 git/odb/tag.go create mode 100644 git/odb/tag_test.go diff --git a/git/odb/tag.go b/git/odb/tag.go new file mode 100644 index 00000000..9ef7c565 --- /dev/null +++ b/git/odb/tag.go @@ -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 +} diff --git a/git/odb/tag_test.go b/git/odb/tag_test.go new file mode 100644 index 00000000..786a04bf --- /dev/null +++ b/git/odb/tag_test.go @@ -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 ", + + 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 ") + 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 \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 ", tag.Tagger) + assert.Equal(t, "The quick brown fox jumps over the lazy dog.", tag.Message) +} From 944eaf8e55fb2fd0c66b9d63fa9f5fbf78846063 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:21:32 -0800 Subject: [PATCH 03/10] git/odb: teach *ObjectType how to read/write tags --- git/odb/object_db.go | 21 ++++++++++++++++ git/odb/object_db_test.go | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/git/odb/object_db.go b/git/odb/object_db.go index ae23cb52..807814db 100644 --- a/git/odb/object_db.go +++ b/git/odb/object_db.go @@ -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. diff --git a/git/odb/object_db_test.go b/git/odb/object_db_test.go index 23139a91..69ebfd67 100644 --- a/git/odb/object_db_test.go +++ b/git/odb/object_db_test.go @@ -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 \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 ", 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 ", + + 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") From e0c66cbbd1001aef9854e4f220e5b7dd4abc33d6 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:22:23 -0800 Subject: [PATCH 04/10] git/githistory: have *RefUpdater hold *odb.ObjectDatabase reference --- git/githistory/ref_updater.go | 3 +++ git/githistory/ref_updater_test.go | 2 ++ git/githistory/rewriter.go | 2 ++ 3 files changed, 7 insertions(+) diff --git a/git/githistory/ref_updater.go b/git/githistory/ref_updater.go index f0752dbd..d699d352 100644 --- a/git/githistory/ref_updater.go +++ b/git/githistory/ref_updater.go @@ -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: diff --git a/git/githistory/ref_updater_test.go b/git/githistory/ref_updater_test.go index 9a08d640..df70d425 100644 --- a/git/githistory/ref_updater_test.go +++ b/git/githistory/ref_updater_test.go @@ -26,6 +26,7 @@ func TestRefUpdaterMovesRefs(t *testing.T) { }, }, Root: root, + db: db, } err := updater.UpdateRefs() @@ -55,6 +56,7 @@ func TestRefUpdaterIgnoresUnovedRefs(t *testing.T) { }, }, Root: root, + db: db, } err := updater.UpdateRefs() diff --git a/git/githistory/rewriter.go b/git/githistory/rewriter.go index d2a2d309..a13acf6c 100644 --- a/git/githistory/rewriter.go +++ b/git/githistory/rewriter.go @@ -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 { From 879519bc1fa30deaff36fff3b9a6738acfa8ca6d Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:23:27 -0800 Subject: [PATCH 05/10] git/githistory/fixtures: add annotated tags fixture --- .../linear-history-with-annotated-tags.git/HEAD | 1 + .../linear-history-with-annotated-tags.git/config | 7 +++++++ .../linear-history-with-annotated-tags.git/index | Bin 0 -> 137 bytes .../logs/HEAD | 3 +++ .../logs/refs/heads/master | 3 +++ .../05/797a38b05f910e6efe40dc1a5c0a046a9403e8 | Bin 0 -> 125 bytes .../20/ecedad3e74a113695fe5f00ab003694e2e1e9c | Bin 0 -> 53 bytes .../22/8afe30855933151f7a88e70d9d88314fd2f191 | Bin 0 -> 156 bytes .../2e/65efe2a145dda7ee51d1741299f848e5bf752e | Bin 0 -> 16 bytes .../34/10062ba67c5ed59b854387a8bc0ec012479368 | Bin 0 -> 16 bytes .../3c/b3201d7942353fff5f45e03d114e8e7a061f87 | Bin 0 -> 53 bytes .../4a/78e180c45f18489941174df19d538c26d5318b | Bin 0 -> 125 bytes .../52/a8963f48d54c7d352695a278ca4b025e130cb4 | Bin 0 -> 52 bytes .../63/d8dbd40c23542e740659a7168a0ce3138ea748 | Bin 0 -> 16 bytes .../6b/de0b381fa1a039396445e2ce5a28c0451fde15 | Bin 0 -> 149 bytes .../91/b85be6928569390e937479509b80a1d0dccb0c | Bin 0 -> 127 bytes .../d9/41e4756add6b06f5bee766fcf669f55419f13f | Bin 0 -> 156 bytes .../refs/heads/master | 1 + .../refs/tags/middle | 1 + 19 files changed, 16 insertions(+) create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/HEAD create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/config create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/index create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/logs/HEAD create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/logs/refs/heads/master create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/05/797a38b05f910e6efe40dc1a5c0a046a9403e8 create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/20/ecedad3e74a113695fe5f00ab003694e2e1e9c create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/22/8afe30855933151f7a88e70d9d88314fd2f191 create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/34/10062ba67c5ed59b854387a8bc0ec012479368 create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/3c/b3201d7942353fff5f45e03d114e8e7a061f87 create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/4a/78e180c45f18489941174df19d538c26d5318b create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/52/a8963f48d54c7d352695a278ca4b025e130cb4 create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/63/d8dbd40c23542e740659a7168a0ce3138ea748 create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/6b/de0b381fa1a039396445e2ce5a28c0451fde15 create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/91/b85be6928569390e937479509b80a1d0dccb0c create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/d9/41e4756add6b06f5bee766fcf669f55419f13f create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/refs/heads/master create mode 100644 git/githistory/fixtures/linear-history-with-annotated-tags.git/refs/tags/middle diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/HEAD b/git/githistory/fixtures/linear-history-with-annotated-tags.git/HEAD new file mode 100644 index 00000000..cb089cd8 --- /dev/null +++ b/git/githistory/fixtures/linear-history-with-annotated-tags.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/config b/git/githistory/fixtures/linear-history-with-annotated-tags.git/config new file mode 100644 index 00000000..6c9406b7 --- /dev/null +++ b/git/githistory/fixtures/linear-history-with-annotated-tags.git/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/index b/git/githistory/fixtures/linear-history-with-annotated-tags.git/index new file mode 100644 index 0000000000000000000000000000000000000000..8da70a9be8f5b0f7dad8de1a4ef8c025675a6341 GIT binary patch literal 137 zcmZ?q402{*U|<4b#wdNpafF9!58nYH_sJOy9L4#$sd^<9B@7H9L9VVqb&?E*3I<#XZ{Du8D_JO<8UOSH h*9PWHKRvlQZ(o?M3HH%_cSY`Z( 1496954196 -0600 commit (initial): some.txt: a +91b85be6928569390e937479509b80a1d0dccb0c 228afe30855933151f7a88e70d9d88314fd2f191 Taylor Blau 1496954207 -0600 commit: some.txt: b +228afe30855933151f7a88e70d9d88314fd2f191 d941e4756add6b06f5bee766fcf669f55419f13f Taylor Blau 1496954214 -0600 commit: some.txt: c diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/logs/refs/heads/master b/git/githistory/fixtures/linear-history-with-annotated-tags.git/logs/refs/heads/master new file mode 100644 index 00000000..7de3b66b --- /dev/null +++ b/git/githistory/fixtures/linear-history-with-annotated-tags.git/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 91b85be6928569390e937479509b80a1d0dccb0c Taylor Blau 1496954196 -0600 commit (initial): some.txt: a +91b85be6928569390e937479509b80a1d0dccb0c 228afe30855933151f7a88e70d9d88314fd2f191 Taylor Blau 1496954207 -0600 commit: some.txt: b +228afe30855933151f7a88e70d9d88314fd2f191 d941e4756add6b06f5bee766fcf669f55419f13f Taylor Blau 1496954214 -0600 commit: some.txt: c diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/05/797a38b05f910e6efe40dc1a5c0a046a9403e8 b/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/05/797a38b05f910e6efe40dc1a5c0a046a9403e8 new file mode 100644 index 0000000000000000000000000000000000000000..931ed3b87cede947995822bf7baf10fe5ddd0d51 GIT binary patch literal 125 zcmV-@0D}K`0WFL{3c@fD06pgw`vGM)+t3Y&h;Q%%vDuVLLnW+-{@y6whJhJNGZ6D( zdpvbA@Lp0;MhPJ@Q=l>?5gjqdEW$LD>~&X!P((;i*YA(mt_Xh+IG$yPkmpa)A-Il4?9b#xvL0-*^upaaYi zlB!Tk$xuXE`Pzp(r_X)jbWf|?H##!^#8^si2dOO1HLD@WEqDMi?YzhIcPL)H(ZX-u K$t=F?q(f>cN=PyQ literal 0 HcmV?d00001 diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e b/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e new file mode 100644 index 0000000000000000000000000000000000000000..3e46ba4a32b0d9c687dbc65f4a2241a91250a71d GIT binary patch literal 16 XcmbIg((OCEph}s literal 0 HcmV?d00001 diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/34/10062ba67c5ed59b854387a8bc0ec012479368 b/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/34/10062ba67c5ed59b854387a8bc0ec012479368 new file mode 100644 index 0000000000000000000000000000000000000000..7582510252d990d1eda2a6e99c307d5c70e8b99e GIT binary patch literal 16 Xcmb9V=y!@Ff%bxD9+DK)hnqeVbDu`|7fA>-R18BFO~?+{NeF* Lf2ke-R*e!*BWV`o literal 0 HcmV?d00001 diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/4a/78e180c45f18489941174df19d538c26d5318b b/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/4a/78e180c45f18489941174df19d538c26d5318b new file mode 100644 index 0000000000000000000000000000000000000000..ab827c790c2eca6aeaca5f4708b89d4b1146ed05 GIT binary patch literal 125 zcmV-@0D}K`0WFL{3IZ_<06q5=`GAr()@DIOe1jjT+jLoJsg!uw-&@7oFfgNW0ZX)N zkEhHE&T-5_$lgyx*4rE+vjj|2VkUdc$z?lPquWbhZndszbUf;sQjsq%(%~Mr(mLEq fe8aiQRrM$OVH6h_TS7uS1=u4a8uPC|rpYQX#r-(* literal 0 HcmV?d00001 diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/52/a8963f48d54c7d352695a278ca4b025e130cb4 b/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/52/a8963f48d54c7d352695a278ca4b025e130cb4 new file mode 100644 index 0000000000000000000000000000000000000000..08e48bcbfd7ba2d5f3a388db8dc23f751b10ef2f GIT binary patch literal 52 zcmbD>n|J(B?P*&E I({FtL0pQRTAOHXW literal 0 HcmV?d00001 diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/63/d8dbd40c23542e740659a7168a0ce3138ea748 b/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/63/d8dbd40c23542e740659a7168a0ce3138ea748 new file mode 100644 index 0000000000000000000000000000000000000000..f9051fff80f9972c98cda05b66725cedc1ecb517 GIT binary patch literal 16 XcmbF!z`fjFJk z{-r|$DSjsaxe&D+3y|q8sF30l*NAJ-ih74!Sh|VpO$gJ)Qt=BUM D-Bd_e literal 0 HcmV?d00001 diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/91/b85be6928569390e937479509b80a1d0dccb0c b/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/91/b85be6928569390e937479509b80a1d0dccb0c new file mode 100644 index 0000000000000000000000000000000000000000..7a6199bb0367bf38b4a7cb6c286b7e54040d4be4 GIT binary patch literal 127 zcmV-_0D%8^0hNtg3c@fD0R7G>_5zmO=G7D_;u*X^OgAVr1mYIz?G4_*uYrMKQZ3~r zKps!hm_et99+6fb%&4u-IooV3qh5&&PwXQ)%9B@dmuGEokDsmMw#6MTg|8w%(S}sZ h0%U><)=+S8Mu(`n?K|PWL-nqO2RUShsJ?V2Hhqe_JA(iK literal 0 HcmV?d00001 diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/d9/41e4756add6b06f5bee766fcf669f55419f13f b/git/githistory/fixtures/linear-history-with-annotated-tags.git/objects/d9/41e4756add6b06f5bee766fcf669f55419f13f new file mode 100644 index 0000000000000000000000000000000000000000..e134799bafd6154d3fd8e46981ef87c478addace GIT binary patch literal 156 zcmV;N0Av4n0hNy32?8+?gndpGX~1JQKX-|Uh&9;2nru)oM&cf5_Xa!gX_yZ_W>Ty5 zCfZ=wNN3gtIB}+Id2xhj=jaRj0zeml(Ht8_j!8YK^J9o6P+<$f`)DnCEDMFe3*?wX zu;@xQ1tTh2<=VPF)8{_obf;DCD<4IEpiimQgGLv}pfl+77G?l7=(s2Rx2RsN@+5EB K>7>4jYD5?ZZ%9M{ literal 0 HcmV?d00001 diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/refs/heads/master b/git/githistory/fixtures/linear-history-with-annotated-tags.git/refs/heads/master new file mode 100644 index 00000000..35f2d5db --- /dev/null +++ b/git/githistory/fixtures/linear-history-with-annotated-tags.git/refs/heads/master @@ -0,0 +1 @@ +d941e4756add6b06f5bee766fcf669f55419f13f diff --git a/git/githistory/fixtures/linear-history-with-annotated-tags.git/refs/tags/middle b/git/githistory/fixtures/linear-history-with-annotated-tags.git/refs/tags/middle new file mode 100644 index 00000000..9e29216d --- /dev/null +++ b/git/githistory/fixtures/linear-history-with-annotated-tags.git/refs/tags/middle @@ -0,0 +1 @@ +05797a38b05f910e6efe40dc1a5c0a046a9403e8 From 33841d44b8120812d35363a588a6e81fa2a3a3bc Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:23:01 -0800 Subject: [PATCH 06/10] git/githistory: teach *refUpdater to rewrite local tags --- git/githistory/ref_updater.go | 30 ++++++++++++++++++++++++++++++ git/githistory/ref_updater_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/git/githistory/ref_updater.go b/git/githistory/ref_updater.go index d699d352..02fee5dc 100644 --- a/git/githistory/ref_updater.go +++ b/git/githistory/ref_updater.go @@ -54,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 } diff --git a/git/githistory/ref_updater_test.go b/git/githistory/ref_updater_test.go index df70d425..ccb93d6d 100644 --- a/git/githistory/ref_updater_test.go +++ b/git/githistory/ref_updater_test.go @@ -37,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() From b63973087b89ae6841077c86fc5b971fedc34c26 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:23:52 -0800 Subject: [PATCH 07/10] test: add migrate fixtures for repositories with tags --- test/test-migrate-fixtures.sh | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/test-migrate-fixtures.sh b/test/test-migrate-fixtures.sh index 25cc9266..e461279c 100755 --- a/test/test-migrate-fixtures.sh +++ b/test/test-migrate-fixtures.sh @@ -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 From 6a5b0bb63c403cce7b7ae76632126383bd9da096 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:24:06 -0800 Subject: [PATCH 08/10] test: add migrate import test for repositories with tags --- test/test-migrate-import.sh | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/test-migrate-import.sh b/test/test-migrate-import.sh index e4fcc3bd..3dcbe384 100755 --- a/test/test-migrate-import.sh +++ b/test/test-migrate-import.sh @@ -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 From b23cd068867a86fbb7b3e89f6642ddfde3049811 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Wed, 13 Dec 2017 11:06:01 -0800 Subject: [PATCH 09/10] git/odb: retain newlines when parsing commit messages --- git/odb/commit.go | 2 +- git/odb/commit_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/git/odb/commit.go b/git/odb/commit.go index 69d6ab9b..20a85b7c 100644 --- a/git/odb/commit.go +++ b/git/odb/commit.go @@ -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 } diff --git a/git/odb/commit_test.go b/git/odb/commit_test.go index 6301fe44..d7528764 100644 --- a/git/odb/commit_test.go +++ b/git/odb/commit_test.go @@ -116,6 +116,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) From dd79da576235289343e9f02359b3a383d3371391 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Wed, 13 Dec 2017 11:13:11 -0800 Subject: [PATCH 10/10] commands/migrate: support '^'-prefix refspec in arguments --- commands/command_migrate.go | 12 ++++++++++- docs/man/git-lfs-migrate.1.ronn | 3 +++ test/test-migrate-import.sh | 38 +++++++++++++++++++++++++++++++++ test/test-migrate-info.sh | 23 ++++++++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/commands/command_migrate.go b/commands/command_migrate.go index 5bb18ec5..8aef8ab9 100644 --- a/commands/command_migrate.go +++ b/commands/command_migrate.go @@ -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 { diff --git a/docs/man/git-lfs-migrate.1.ronn b/docs/man/git-lfs-migrate.1.ronn index 0f11061a..7ee3782a 100644 --- a/docs/man/git-lfs-migrate.1.ronn +++ b/docs/man/git-lfs-migrate.1.ronn @@ -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. diff --git a/test/test-migrate-import.sh b/test/test-migrate-import.sh index e4fcc3bd..ae2c433c 100755 --- a/test/test-migrate-import.sh +++ b/test/test-migrate-import.sh @@ -293,6 +293,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 diff --git a/test/test-migrate-info.sh b/test/test-migrate-info.sh index e48bbf99..62a8de07 100755 --- a/test/test-migrate-info.sh +++ b/test/test-migrate-info.sh @@ -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