diff options
-rw-r--r-- | blame/blame.go | 147 | ||||
-rw-r--r-- | blame/blame_test.go | 478 | ||||
-rw-r--r-- | commit.go | 16 | ||||
-rw-r--r-- | commit_test.go | 96 | ||||
-rw-r--r-- | common.go | 41 | ||||
-rw-r--r-- | common_test.go | 92 | ||||
-rw-r--r-- | diff/diff_ext_test.go | 65 | ||||
-rw-r--r-- | file.go | 35 | ||||
-rw-r--r-- | file_test.go | 133 | ||||
-rw-r--r-- | repository.go | 2 | ||||
-rw-r--r-- | repository_test.go | 4 | ||||
-rw-r--r-- | revlist/revlist.go | 106 | ||||
-rw-r--r-- | revlist/revlist_test.go | 86 | ||||
-rw-r--r-- | tree.go | 6 |
14 files changed, 686 insertions, 621 deletions
diff --git a/blame/blame.go b/blame/blame.go index 774362b..9475f2a 100644 --- a/blame/blame.go +++ b/blame/blame.go @@ -10,8 +10,8 @@ package blame import ( "bytes" + "errors" "fmt" - "sort" "strconv" "strings" "unicode/utf8" @@ -62,52 +62,125 @@ import ( // 2. Improve how to traverse the history (example a backward // traversal will be much more efficient) // -// TODO: ways to improve the functrion in general +// TODO: ways to improve the function in general: // // 1. Add memoization betweenn revlist and assign. // // 2. It is using much more memmory than needed, see the TODOs below. -func Blame(repo *git.Repository, commit *git.Commit, path string) ([]*git.Commit, error) { + +type Blame struct { + Repo string + Path string + Rev string + Lines []*line +} + +func New(repo *git.Repository, path string, commit *git.Commit) (*Blame, error) { // init the internal blame struct b := new(blame) b.repo = repo b.fRev = commit b.path = path - // calculte the history of the file and store it in the - // internal blame struct. - var err error - b.revs, err = revlist.New(b.repo, b.fRev, b.path) + // get all the file revisions + if err := b.fillRevs(); err != nil { + return nil, err + } + + // calculate the line tracking graph and fill in + // file contents in data. + if err := b.fillGraphAndData(); err != nil { + return nil, err + } + + file, err := b.fRev.File(b.path) + if err != nil { + return nil, err + } + finalLines := file.Lines() + + lines, err := newLines(finalLines, b.sliceGraph(len(b.graph)-1)) if err != nil { return nil, err } - sort.Sort(b.revs) // for forward blame, we need the history sorted by commit date - // allocate space for the data in all the revisions of the file - b.data = make([]string, len(b.revs)) + return &Blame{ + Repo: repo.URL, + Path: path, + Rev: commit.Hash.String(), + Lines: lines, + }, nil +} - // init the graph - b.graph = make([][]vertex, len(b.revs)) +type line struct { + author string + text string +} - // for all every revision of the file, starting with the first +func newLine(author, text string) *line { + return &line{ + author: author, + text: text, + } +} + +func newLines(contents []string, commits []*git.Commit) ([]*line, error) { + if len(contents) != len(commits) { + fmt.Println(len(contents)) + fmt.Println(len(commits)) + return nil, errors.New("contents and commits have different length") + } + result := make([]*line, 0, len(contents)) + for i := range contents { + l := newLine(commits[i].Author.Email, contents[i]) + result = append(result, l) + } + return result, nil +} + +// this struct is internally used by the blame function to hold its +// inputs, outputs and state. +type blame struct { + repo *git.Repository // the repo holding the history of the file to blame + path string // the path of the file to blame + fRev *git.Commit // the commit of the final revision of the file to blame + revs revlist.Revs // the chain of revisions affecting the the file to blame + data []string // the contents of the file across all its revisions + graph [][]*git.Commit // the graph of the lines in the file across all the revisions TODO: not all commits are needed, only the current rev and the prev +} + +// calculte the history of a file "path", from commit "from, sorted by commit date. +func (b *blame) fillRevs() error { + var err error + b.revs, err = revlist.NewRevs(b.repo, b.fRev, b.path) + if err != nil { + return err + } + return nil +} + +// build graph of a file from its revision history +func (b *blame) fillGraphAndData() error { + b.graph = make([][]*git.Commit, len(b.revs)) + b.data = make([]string, len(b.revs)) // file contents in all the revisions + // for every revision of the file, starting with the first // one... - var found bool for i, rev := range b.revs { // get the contents of the file - b.data[i], found = git.Data(b.path, rev) - if !found { - continue + file, err := rev.File(b.path) + if err != nil { + return nil } - // count its lines + b.data[i] = file.Contents() nLines := git.CountLines(b.data[i]) // create a node for each line - b.graph[i] = make([]vertex, nLines) + b.graph[i] = make([]*git.Commit, nLines) // assign a commit to each node // if this is the first revision, then the node is assigned to // this first commit. if i == 0 { for j := 0; j < nLines; j++ { - b.graph[i][j] = vertex(b.revs[i]) + b.graph[i][j] = (*git.Commit)(b.revs[i]) } } else { // if this is not the first commit, then assign to the old @@ -116,31 +189,21 @@ func Blame(repo *git.Repository, commit *git.Commit, path string) ([]*git.Commit b.assignOrigin(i, i-1) } } + return nil +} - // fill in the output results: copy the nodes of the last revision - // into the result. - fVs := b.graph[len(b.graph)-1] +// sliceGraph returns a slice of commits (one per line) for a particular +// revision of a file (0=first revision). +func (b *blame) sliceGraph(i int) []*git.Commit { + fVs := b.graph[i] result := make([]*git.Commit, 0, len(fVs)) for _, v := range fVs { c := git.Commit(*v) result = append(result, &c) } - return result, nil -} - -// this struct is internally used by the blame function to hold its -// intputs, outputs and state. -type blame struct { - repo *git.Repository // the repo holding the history of the file to blame - path string // the path of the file to blame - fRev *git.Commit // the commit of the final revision of the file to blame - revs revlist.Revs // the chain of revisions affecting the the file to blame - data []string // the contents on the file in all the revisions TODO: not all data is needed, only the current rev and the prev - graph [][]vertex // the graph of the lines in the file across all the revisions TODO: not all vertexes are needed, only the current rev and the prev + return result } -type vertex *git.Commit // a vertex only needs to store the original commit it came from - // Assigns origin to vertexes in current (c) rev from data in its previous (p) // revision func (b *blame) assignOrigin(c, p int) { @@ -151,7 +214,6 @@ func (b *blame) assignOrigin(c, p int) { for h := range hunks { hLines := git.CountLines(hunks[h].Text) for hl := 0; hl < hLines; hl++ { - // fmt.Printf("file=%q, rev=%d, r=%d, h=%d, hunk=%v, hunkLine=%d\n", file, rev, r, h, hunks[h], hl) switch { case hunks[h].Type == 0: sl++ @@ -159,7 +221,7 @@ func (b *blame) assignOrigin(c, p int) { b.graph[c][dl] = b.graph[p][sl] case hunks[h].Type == 1: dl++ - b.graph[c][dl] = vertex(b.revs[c]) + b.graph[c][dl] = (*git.Commit)(b.revs[c]) case hunks[h].Type == -1: sl++ default: @@ -169,14 +231,15 @@ func (b *blame) assignOrigin(c, p int) { } } -// This will print the results of a Blame as in git-blame. +// PrettyPrint prints the results of a Blame using git-blame's style. func (b *blame) PrettyPrint() string { var buf bytes.Buffer - contents, found := git.Data(b.path, b.fRev) - if !found { + file, err := b.fRev.File(b.path) + if err != nil { panic("PrettyPrint: internal error in repo.Data") } + contents := file.Contents() lines := strings.Split(contents, "\n") // max line number length diff --git a/blame/blame_test.go b/blame/blame_test.go index 81c02f0..3c24852 100644 --- a/blame/blame_test.go +++ b/blame/blame_test.go @@ -2,14 +2,14 @@ package blame import ( "bytes" - "fmt" "os" "testing" - . "gopkg.in/check.v1" "gopkg.in/src-d/go-git.v2" "gopkg.in/src-d/go-git.v2/core" "gopkg.in/src-d/go-git.v2/formats/packfile" + + . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } @@ -32,59 +32,143 @@ var fixtureRepos = [...]struct { func (s *SuiteCommon) SetUpSuite(c *C) { s.repos = make(map[string]*git.Repository, 0) for _, fixRepo := range fixtureRepos { - s.repos[fixRepo.url] = git.NewPlainRepository() + repo := git.NewPlainRepository() + repo.URL = fixRepo.url d, err := os.Open(fixRepo.packfile) - defer func() { - c.Assert(d.Close(), IsNil) - }() c.Assert(err, IsNil) r := packfile.NewReader(d) - r.Format = packfile.OFSDeltaFormat // TODO: how to know the format of a pack file ahead of time? + // TODO: how to know the format of a pack file ahead of time? + // Some info at: + // https://codewords.recurse.com/issues/three/unpacking-git-packfiles + r.Format = packfile.OFSDeltaFormat - _, err = r.Read(s.repos[fixRepo.url].Storage) + _, err = r.Read(repo.Storage) c.Assert(err, IsNil) + + c.Assert(d.Close(), IsNil) + + s.repos[fixRepo.url] = repo } } -var blameTests = [...]struct { - // input data to revlist +type blameTest struct { repo string - branch string // TODO: remove this, it is no longer needed for local packfiles - commit string + rev string path string - // expected output data form the revlist - blames []string -}{ + blames []string // the commits blamed for each line +} + +func (s *SuiteCommon) mockBlame(t blameTest, c *C) (blame *Blame) { + repo, ok := s.repos[t.repo] + c.Assert(ok, Equals, true) + + commit, err := repo.Commit(core.NewHash(t.rev)) + c.Assert(err, IsNil, Commentf("%v: repo=%s, rev=%s", err, repo, t.rev)) + + file, err := commit.File(t.path) + c.Assert(err, IsNil) + lines := file.Lines() + c.Assert(len(t.blames), Equals, len(lines), Commentf( + "repo=%s, path=%s, rev=%s: the number of lines in the file and the number of expected blames differ (len(blames)=%d, len(lines)=%d)\nblames=%#q\nlines=%#q", t.repo, t.path, t.rev, len(t.blames), len(lines), t.blames, lines)) + + blamedLines := make([]*line, 0, len(t.blames)) + for i := range t.blames { + commit, err := repo.Commit(core.NewHash(t.blames[i])) + c.Assert(err, IsNil) + l := &line{ + author: commit.Author.Email, + text: lines[i], + } + blamedLines = append(blamedLines, l) + } + + return &Blame{ + Repo: t.repo, + Path: t.path, + Rev: t.rev, + Lines: blamedLines, + } +} + +// run a blame on all the suite's tests +func (s *SuiteCommon) TestBlame(c *C) { + for i, t := range blameTests { + expected := s.mockBlame(t, c) + + repo, ok := s.repos[t.repo] + c.Assert(ok, Equals, true) + + commit, err := repo.Commit(core.NewHash(t.rev)) + c.Assert(err, IsNil) + + obtained, err := New(repo, t.path, commit) + c.Assert(err, IsNil, Commentf("subtest %d", i)) + + c.Assert(obtained, DeepEquals, expected, Commentf("subtest %d: %s", + i, sideBySide(obtained, expected))) + } +} + +func sideBySide(output, expected *Blame) string { + var buf bytes.Buffer + buf.WriteString(output.Repo) + buf.WriteString(" ") + buf.WriteString(expected.Repo) + return buf.String() +} + +// utility function to avoid writing so many repeated commits +func repeat(s string, n int) []string { + if n < 0 { + panic("repeat: n < 0") + } + r := make([]string, 0, n) + for i := 0; i < n; i++ { + r = append(r, s) + } + return r +} + +// utility function to concat slices +func concat(vargs ...[]string) []string { + var result []string + for _, ss := range vargs { + result = append(result, ss...) + } + return result +} + +var blameTests = [...]blameTest{ // use the blame2humantest.bash script to easily add more tests. - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "binary.jpg", concat(&[]string{}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "binary.jpg", concat( repeat("35e85108805c84807bc66a02d91535e1e24b38b9", 285), )}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", concat(&[]string{}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", concat( repeat("b8e471f58bcbca63b07bda20e428190409c2db47", 1), )}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "go/example.go", concat(&[]string{}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "go/example.go", concat( repeat("918c48b83bd081e863dbe1b80f8998f058cd8294", 142), )}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/long.json", concat(&[]string{}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/long.json", concat( repeat("af2d6a6954d532f8ffb47615169c8fdf9d383a1a", 6492), )}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/short.json", concat(&[]string{}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/short.json", concat( repeat("af2d6a6954d532f8ffb47615169c8fdf9d383a1a", 22), )}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", concat(&[]string{}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", concat( repeat("b029517f6300c2da0f4b651b8642506cd6aaf45d", 22), )}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "php/crappy.php", concat(&[]string{}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "php/crappy.php", concat( repeat("918c48b83bd081e863dbe1b80f8998f058cd8294", 259), )}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "vendor/foo.go", concat(&[]string{}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "vendor/foo.go", concat( repeat("6ecf0ef2c2dffb796033e5a02219af86ec6584e5", 7), )}, /* // Failed - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "InstallSpinnaker.sh", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "InstallSpinnaker.sh", concat( repeat("ce9f123d790717599aaeb76bc62510de437761be", 2), repeat("a47d0aaeda421f06df248ad65bd58230766bf118", 1), repeat("23673af3ad70b50bba7fdafadc2323302f5ba520", 1), @@ -239,11 +323,11 @@ var blameTests = [...]struct { repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 3), )}, */ - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/reconfigure_spinnaker.py", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/reconfigure_spinnaker.py", concat( repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 22), repeat("c89dab0d42f1856d157357e9010f8cc6a12f5b1f", 7), )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/validate_configuration.py", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/validate_configuration.py", concat( repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 29), repeat("1e3d328a2cabda5d0aaddc5dec65271343e0dc37", 19), repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 15), @@ -274,12 +358,12 @@ var blameTests = [...]struct { repeat("b5d999e2986e190d81767cd3cfeda0260f9f6fb8", 7), repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/run.py", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/run.py", concat( repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 185), )}, /* // Fail by 3 - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/configurator.py", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/configurator.py", concat( repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 53), repeat("c89dab0d42f1856d157357e9010f8cc6a12f5b1f", 1), repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), @@ -301,62 +385,8 @@ var blameTests = [...]struct { repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 43), )}, */ - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/fetch.py", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 140), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/yaml_util.py", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 68), - repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 8), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("023d4fb17b76e0fe0764971df8b8538b735a1d67", 3), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 7), - repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 12), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), - repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 33), - repeat("bb6325e4e629fc7348a6d0e6842280d5304160ff", 40), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("b5d999e2986e190d81767cd3cfeda0260f9f6fb8", 7), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), - repeat("b5d999e2986e190d81767cd3cfeda0260f9f6fb8", 4), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/spinnaker_runner.py", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 235), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 13), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 7), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 20), - repeat("707bcdce04eabdb0549868ad1a8efa2d76b9bdf1", 7), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 21), - repeat("2b28ea424acc8f2817d3298c143fae68bcad91a7", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 15), - repeat("d73f9cee49a5ad27a42a6e18af7c49a8f28ad8a8", 17), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 336), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/transform_old_config.py", concat(&[]string{}, - repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 43), - repeat("4584fab37e93d66fd1896d07fa3427f8056711bc", 1), - repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 12), - repeat("bb6325e4e629fc7348a6d0e6842280d5304160ff", 1), - repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 16), - repeat("0777fadf4ca6f458d7071de414f9bd5417911037", 1), - repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 1), - repeat("0777fadf4ca6f458d7071de414f9bd5417911037", 1), - repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 8), - repeat("0777fadf4ca6f458d7071de414f9bd5417911037", 1), - repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 31), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/__init__.py", []string{}}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/yaml/LICENSE", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 19), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "settings.gradle", concat(&[]string{}, - repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "gradle/wrapper/gradle-wrapper.jar", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/__init__.py", []string{}}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "gradle/wrapper/gradle-wrapper.jar", concat( repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 1), repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 7), repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 2), @@ -406,15 +436,7 @@ var blameTests = [...]struct { repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 6), repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 55), )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "gradle/wrapper/gradle-wrapper.properties", concat(&[]string{}, - repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), - repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 4), - repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "gradle/buildViaTravis.sh", concat(&[]string{}, - repeat("7ecc2ad58e24a5b52504985467a10c6a3bb85b9b", 24), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/settings.js", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/settings.js", concat( repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 17), repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 43), @@ -424,7 +446,7 @@ var blameTests = [...]struct { )}, /* // fail a few lines - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/default-spinnaker-local.yml", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/default-spinnaker-local.yml", concat( repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 9), repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 2), repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), @@ -449,36 +471,9 @@ var blameTests = [...]struct { repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), )}, */ - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/igor.yml", concat(&[]string{}, - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 15), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/echo.yml", concat(&[]string{}, - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 15), - repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 1), - repeat("7c8d9a6081d9cb7a56c479bfe64d70540ea32795", 4), - repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 24), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/orca.yml", concat(&[]string{}, - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 40), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/rosco.yml", concat(&[]string{}, - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 9), - repeat("24dc2d465c85cb242262ab6bc236bde3ffbb93e0", 3), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 3), - repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 2), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 3), - repeat("974b775a8978b120ff710cac93a21c7387b914c9", 2), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 1), - repeat("974b775a8978b120ff710cac93a21c7387b914c9", 1), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 2), - repeat("974b775a8978b120ff710cac93a21c7387b914c9", 2), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/rush.yml", concat(&[]string{}, - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 18), - )}, /* // fail one line - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/spinnaker.yml", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/spinnaker.yml", concat( repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 32), repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 2), repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 1), @@ -500,142 +495,15 @@ var blameTests = [...]struct { repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 15), )}, */ - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/gate.yml", concat(&[]string{}, - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 29), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/run_dev.sh", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 23), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/build_google_image.sh", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 16), - repeat("f66196ceed7d6aeca313b0632657ab762487ced3", 18), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/refresh_source.py", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 21), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 14), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 1), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 12), - repeat("e3620408a7776039f7853a21748921ea2a281953", 35), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 10), - repeat("e3620408a7776039f7853a21748921ea2a281953", 34), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 11), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 17), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 8), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 1), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 1), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 23), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 8), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), - repeat("e3620408a7776039f7853a21748921ea2a281953", 5), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 6), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 7), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 1), - repeat("ba10a5d5615f68eb9115cada1d639066a53ddc4d", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 7), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 1), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 9), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), - repeat("e3620408a7776039f7853a21748921ea2a281953", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 2), - repeat("a80d310d9ca42c3422627f2ecca7ee1dbefa602a", 13), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 11), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 6), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 3), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("b192881117651465df2385ef89344dd5dd4810f3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), - repeat("e3620408a7776039f7853a21748921ea2a281953", 5), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("e3620408a7776039f7853a21748921ea2a281953", 2), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 12), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("e3620408a7776039f7853a21748921ea2a281953", 11), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 14), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 2), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 12), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), - repeat("13ad4df676a16caf2ff1ca216be615d5aee37db3", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 36), - repeat("e3620408a7776039f7853a21748921ea2a281953", 7), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), - repeat("e3620408a7776039f7853a21748921ea2a281953", 4), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 21), - repeat("e3620408a7776039f7853a21748921ea2a281953", 3), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("e3620408a7776039f7853a21748921ea2a281953", 6), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 19), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 8), - repeat("e3620408a7776039f7853a21748921ea2a281953", 14), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("e3620408a7776039f7853a21748921ea2a281953", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("3d3819741bada73fb950f14309c97e1f63492ec6", 2), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("e3620408a7776039f7853a21748921ea2a281953", 1), - )}, /* - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/install_development.sh", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/install_development.sh", concat( repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 71), )}, */ /* // FAIL two lines interchanged - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/bootstrap_dev.sh", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/bootstrap_dev.sh", concat( repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 95), repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 10), @@ -695,116 +563,10 @@ var blameTests = [...]struct { repeat("838aed816872c52ed435e4876a7b64dba0bed500", 8), )}, */ - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/build_release.sh", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 17), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/stop_dev.sh", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 23), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/build_google_image.packer", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), - repeat("8fe3f13ad04ee25fde0add4ed19d29acd49a5916", 1), - repeat("f66196ceed7d6aeca313b0632657ab762487ced3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), - repeat("f66196ceed7d6aeca313b0632657ab762487ced3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), - repeat("8fe3f13ad04ee25fde0add4ed19d29acd49a5916", 1), - repeat("f66196ceed7d6aeca313b0632657ab762487ced3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 14), - repeat("f66196ceed7d6aeca313b0632657ab762487ced3", 1), - repeat("0d9c9cef53af38cefcb6801bb492aaed3f2c9a42", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - repeat("f66196ceed7d6aeca313b0632657ab762487ced3", 8), - repeat("6eb5d9c5225224bfe59c401182a2939d6c27fc00", 1), - repeat("f66196ceed7d6aeca313b0632657ab762487ced3", 1), - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), - )}, - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/refresh_source.sh", concat(&[]string{}, - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 17), - )}, /* // FAIL move? - {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/create_google_dev_vm.sh", concat(&[]string{}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/create_google_dev_vm.sh", concat( repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 20), )}, */ } - -// run a blame on all the suite's tests -func (s *SuiteCommon) TestBlame(c *C) { - for _, t := range blameTests { - fmt.Println("Blamming", t.repo, t.branch, t.commit, t.path) - repo, ok := s.repos[t.repo] - c.Assert(ok, Equals, true) - - commit, err := repo.Commit(core.NewHash(t.commit)) - c.Assert(err, IsNil) - - blames, err := Blame(repo, commit, t.path) - c.Assert(err, IsNil) - - c.Assert(len(blames), Equals, len(t.blames), Commentf("\nrepo=%s, branch=%s, commit=%s, path=%s", - t.repo, t.branch, t.commit, t.path)) - c.Assert(blames, DeepEquals, s.commits(c, t.repo, t.blames...), Commentf("\nrepo=%s, branch=%s, commit=%s, path=%s, \n%s", - t.repo, t.branch, t.commit, t.path, compareSideBySide2(t.blames, blames))) - } -} - -// TODO: duplicated from revlist/revlist_test.go -// returns the commits from a slice of hashes -func (s *SuiteCommon) commits(cc *C, repo string, hs ...string) []*git.Commit { - r, ok := s.repos[repo] - cc.Assert(ok, Equals, true) - result := make([]*git.Commit, 0, len(hs)) - for _, h := range hs { - c, err := r.Commit(core.NewHash(h)) - cc.Assert(err, IsNil) - result = append(result, c) - } - return result -} - -// TODO: duplicated from revlist/revlist_test.go -// same length is assumed -func compareSideBySide2(a []string, b []*git.Commit) string { - var buf bytes.Buffer - buf.WriteString("\t EXPECTED OBTAINED ") - var sep string - var obtained string - for i := range a { - obtained = b[i].Hash.String() - if a[i] != obtained { - sep = "------" - } else { - sep = " " - } - buf.WriteString(fmt.Sprintf("\n%d", i+1)) - buf.WriteString(sep) - buf.WriteString(a[i]) - buf.WriteString(sep) - buf.WriteString(obtained) - } - return buf.String() -} - -// utility function to avoid writing so many repeated commits -func repeat(s string, n int) []string { - if n < 0 { - panic("repeat: n < 0") - } - r := make([]string, 0, n) - for i := 0; i < n; i++ { - r = append(r, s) - } - return r -} - -// utility function to concat slices -func concat(dst *[]string, vargs ...[]string) []string { - for _, ss := range vargs { - for _, s := range ss { - *dst = append(*dst, s) - } - } - return *dst -} @@ -3,12 +3,16 @@ package git import ( "bufio" "bytes" + "errors" "fmt" "io" "gopkg.in/src-d/go-git.v2/core" ) +// New errors defined by this package. +var ErrFileNotFound = errors.New("file not found") + type Hash core.Hash // Commit points to a single tree, marking it as what the project looked like @@ -50,6 +54,18 @@ func (c *Commit) NumParents() int { return len(c.parents) } +// File returns the file with the specified "path" in the commit and a +// nil error if the file exists. If the file does not exists, it returns +// a nil file and the ErrFileNotFound error. +func (c *Commit) File(path string) (file *File, err error) { + for file := range c.Tree().Files() { + if file.Name == path { + return file, nil + } + } + return nil, ErrFileNotFound +} + // Decode transform an core.Object into a Blob struct func (c *Commit) Decode(o core.Object) error { c.Hash = o.Hash() diff --git a/commit_test.go b/commit_test.go index 14c2e74..67b9e77 100644 --- a/commit_test.go +++ b/commit_test.go @@ -1,16 +1,104 @@ package git import ( - . "gopkg.in/check.v1" + "os" + "gopkg.in/src-d/go-git.v2/core" + "gopkg.in/src-d/go-git.v2/formats/packfile" + + . "gopkg.in/check.v1" ) -type CommitCommon struct{} +type SuiteCommit struct { + repos map[string]*Repository +} + +var _ = Suite(&SuiteCommit{}) + +// create the repositories of the fixtures +func (s *SuiteCommit) SetUpSuite(c *C) { + fixtureRepos := [...]struct { + url string + packfile string + }{ + {"https://github.com/tyba/git-fixture.git", "formats/packfile/fixtures/git-fixture.ofs-delta"}, + } + s.repos = make(map[string]*Repository, 0) + for _, fixRepo := range fixtureRepos { + s.repos[fixRepo.url] = NewPlainRepository() + + d, err := os.Open(fixRepo.packfile) + c.Assert(err, IsNil) -var _ = Suite(&CommitCommon{}) + r := packfile.NewReader(d) + r.Format = packfile.OFSDeltaFormat // TODO: how to know the format of a pack file ahead of time? -func (s *CommitCommon) TestIterClose(c *C) { + _, err = r.Read(s.repos[fixRepo.url].Storage) + c.Assert(err, IsNil) + + c.Assert(d.Close(), IsNil) + } +} + +func (s *SuiteCommit) TestIterClose(c *C) { i := &iter{ch: make(chan core.Object, 1)} i.Close() i.Close() } + +var fileTests = []struct { + repo string // the repo name as in localRepos + commit string // the commit to search for the file + path string // the path of the file to find + blobHash string // expected hash of the returned file + found bool // expected found value +}{ + // use git ls-tree commit to get the hash of the blobs + {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", "not-found", + "", false}, + {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", ".gitignore", + "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88", true}, + {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", "LICENSE", + "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f", true}, + + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "not-found", + "", false}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", ".gitignore", + "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88", true}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "binary.jpg", + "d5c0f4ab811897cadf03aec358ae60d21f91c50d", true}, + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", + "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f", true}, + + {"https://github.com/tyba/git-fixture.git", "35e85108805c84807bc66a02d91535e1e24b38b9", "binary.jpg", + "d5c0f4ab811897cadf03aec358ae60d21f91c50d", true}, + {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", "binary.jpg", + "", false}, + + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", + "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", true}, + {"https://github.com/tyba/git-fixture.git", "1669dce138d9b841a518c64b10914d88f5e488ea", "CHANGELOG", + "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", true}, + {"https://github.com/tyba/git-fixture.git", "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", "CHANGELOG", + "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", true}, + {"https://github.com/tyba/git-fixture.git", "35e85108805c84807bc66a02d91535e1e24b38b9", "CHANGELOG", + "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", false}, + {"https://github.com/tyba/git-fixture.git", "b8e471f58bcbca63b07bda20e428190409c2db47", "CHANGELOG", + "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", true}, + {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", "CHANGELOG", + "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", false}, +} + +func (s *SuiteCommit) TestFile(c *C) { + for i, t := range fileTests { + commit, err := s.repos[t.repo].Commit(core.NewHash(t.commit)) + c.Assert(err, IsNil, Commentf("subtest %d: %v (%s)", i, err, t.commit)) + + file, err := commit.File(t.path) + found := err == nil + c.Assert(found, Equals, t.found, Commentf("subtest %d, path=%s, commit=%s", i, t.path, t.commit)) + if found { + c.Assert(file.Hash.String(), Equals, t.blobHash, Commentf("subtest %d, commit=%s, path=%s", i, t.commit, t.path)) + } + } +} @@ -1,15 +1,11 @@ package git -import ( - "bytes" - "strings" -) +import "strings" -// CountLines returns the number of lines in a string. -// The newline character is assumed to be '\n'. -// The empty string contains 0 lines. -// If the last line of the string doesn't end with a newline, it will -// still be considered a line. +// CountLines returns the number of lines in a string à la git, this is +// The newline character is assumed to be '\n'. The empty string +// contains 0 lines. If the last line of the string doesn't end with a +// newline, it will still be considered a line. func CountLines(s string) int { if s == "" { return 0 @@ -20,30 +16,3 @@ func CountLines(s string) int { } return nEol + 1 } - -// FindFile searches for a path in a commit. Returns the file and true if found. -// Returns nil and false if not found. -// TODO: should this be a method of git.Commit instead? -func FindFile(path string, commit *Commit) (file *File, found bool) { - tree := commit.Tree() - for file := range tree.Files() { - if file.Name == path { - return file, true - } - } - return nil, false -} - -// Data returns the contents of a file in a commit and true if found. -// Returns an empty string and false if the file is not found in the -// commit. -// TODO: should this be a method of git.Commit instead? -func Data(path string, commit *Commit) (contents string, found bool) { - file, found := FindFile(path, commit) - if !found { - return "", found - } - buf := new(bytes.Buffer) - buf.ReadFrom(file) - return buf.String(), found -} diff --git a/common_test.go b/common_test.go index 0695d5f..4c48419 100644 --- a/common_test.go +++ b/common_test.go @@ -5,10 +5,10 @@ import ( "os" "testing" - . "gopkg.in/check.v1" "gopkg.in/src-d/go-git.v2/clients/common" "gopkg.in/src-d/go-git.v2/core" - "gopkg.in/src-d/go-git.v2/formats/packfile" + + . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } @@ -44,39 +44,10 @@ func (s *MockGitUploadPackService) Fetch(*common.GitUploadPackRequest) (io.ReadC return r, nil } -type SuiteCommon struct { - repos map[string]*Repository -} +type SuiteCommon struct{} var _ = Suite(&SuiteCommon{}) -var fixtureRepos = [...]struct { - url string - packfile string -}{ - {"https://github.com/tyba/git-fixture.git", "formats/packfile/fixtures/git-fixture.ofs-delta"}, -} - -// create the repositories of the fixtures -func (s *SuiteCommon) SetUpSuite(c *C) { - s.repos = make(map[string]*Repository, 0) - for _, fixRepo := range fixtureRepos { - s.repos[fixRepo.url] = NewPlainRepository() - - d, err := os.Open(fixRepo.packfile) - defer func() { - c.Assert(d.Close(), IsNil) - }() - c.Assert(err, IsNil) - - r := packfile.NewReader(d) - r.Format = packfile.OFSDeltaFormat // TODO: how to know the format of a pack file ahead of time? - - _, err = r.Read(s.repos[fixRepo.url].Storage) - c.Assert(err, IsNil) - } -} - var countLinesTests = [...]struct { i string // the string we want to count lines from e int // the expected number of lines in i @@ -88,6 +59,7 @@ var countLinesTests = [...]struct { {"a\nb\n", 2}, {"a\nb\nc", 3}, {"a\nb\nc\n", 3}, + {"a\n\n\nb\n", 4}, {"first line\n\tsecond line\nthird line\n", 3}, } @@ -97,59 +69,3 @@ func (s *SuiteCommon) TestCountLines(c *C) { c.Assert(o, Equals, t.e, Commentf("subtest %d, input=%q", i, t.i)) } } - -var findFileTests = []struct { - repo string // the repo name as in localRepos - commit string // the commit to search for the file - path string // the path of the file to find - blobHash string // expected hash of the returned file - found bool // expected found value -}{ - // use git ls-tree commit to get the hash of the blobs - {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", "not-found", - "", false}, - {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", ".gitignore", - "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88", true}, - {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", "LICENSE", - "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f", true}, - - {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "not-found", - "", false}, - {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", ".gitignore", - "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88", true}, - {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "binary.jpg", - "d5c0f4ab811897cadf03aec358ae60d21f91c50d", true}, - {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", - "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f", true}, - - {"https://github.com/tyba/git-fixture.git", "35e85108805c84807bc66a02d91535e1e24b38b9", "binary.jpg", - "d5c0f4ab811897cadf03aec358ae60d21f91c50d", true}, - {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", "binary.jpg", - "", false}, - - {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", - "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", true}, - {"https://github.com/tyba/git-fixture.git", "1669dce138d9b841a518c64b10914d88f5e488ea", "CHANGELOG", - "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", true}, - {"https://github.com/tyba/git-fixture.git", "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", "CHANGELOG", - "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", true}, - {"https://github.com/tyba/git-fixture.git", "35e85108805c84807bc66a02d91535e1e24b38b9", "CHANGELOG", - "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", false}, - {"https://github.com/tyba/git-fixture.git", "b8e471f58bcbca63b07bda20e428190409c2db47", "CHANGELOG", - "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", true}, - {"https://github.com/tyba/git-fixture.git", "b029517f6300c2da0f4b651b8642506cd6aaf45d", "CHANGELOG", - "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", false}, -} - -func (s *SuiteCommon) TestFindFile(c *C) { - for i, t := range findFileTests { - commit, err := s.repos[t.repo].Commit(core.NewHash(t.commit)) - c.Assert(err, IsNil, Commentf("subtest %d: %v (%s)", i, err, t.commit)) - - file, found := FindFile(t.path, commit) - c.Assert(found, Equals, t.found, Commentf("subtest %d, path=%s, commit=%s", i, t.path, t.commit)) - if found { - c.Assert(file.Hash.String(), Equals, t.blobHash, Commentf("subtest %d, commit=%s, path=%s", i, t.commit, t.path)) - } - } -} diff --git a/diff/diff_ext_test.go b/diff/diff_ext_test.go index 5ab8c7d..460cf8a 100644 --- a/diff/diff_ext_test.go +++ b/diff/diff_ext_test.go @@ -3,9 +3,10 @@ package diff_test import ( "testing" - . "gopkg.in/check.v1" - "gopkg.in/src-d/go-git.v2/diff" + + "github.com/sergi/go-diff/diffmatchpatch" + . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } @@ -39,7 +40,7 @@ var diffTests = [...]struct { {"a\nbbbbb\n\tccc\ndd\n\tfffffffff\n", "bbbbb\n\tccc\n\tDD\n\tffff\n"}, } -func (s *suiteCommon) TestCountLines(c *C) { +func (s *suiteCommon) TestAll(c *C) { for i, t := range diffTests { diffs := diff.Do(t.src, t.dst) src := diff.Src(diffs) @@ -48,3 +49,61 @@ func (s *suiteCommon) TestCountLines(c *C) { c.Assert(dst, Equals, t.dst, Commentf("subtest %d, src=%q, dst=%q, bad calculated dst", i, t.src, t.dst)) } } + +var doTests = [...]struct { + src, dst string + expected []diffmatchpatch.Diff +}{ + { + src: "", + dst: "", + expected: []diffmatchpatch.Diff{}, + }, + { + src: "a", + dst: "a", + expected: []diffmatchpatch.Diff{ + { + Type: 0, + Text: "a", + }, + }, + }, + { + src: "", + dst: "abc\ncba", + expected: []diffmatchpatch.Diff{ + { + Type: 1, + Text: "abc\ncba", + }, + }, + }, + { + src: "abc\ncba", + dst: "", + expected: []diffmatchpatch.Diff{ + { + Type: -1, + Text: "abc\ncba", + }, + }, + }, + { + src: "abc\nbcd\ncde", + dst: "000\nabc\n111\nBCD\n", + expected: []diffmatchpatch.Diff{ + {Type: 1, Text: "000\n"}, + {Type: 0, Text: "abc\n"}, + {Type: -1, Text: "bcd\ncde"}, + {Type: 1, Text: "111\nBCD\n"}, + }, + }, +} + +func (s *suiteCommon) TestDo(c *C) { + for i, t := range doTests { + diffs := diff.Do(t.src, t.dst) + c.Assert(diffs, DeepEquals, t.expected, Commentf("subtest %d", i)) + } +} @@ -0,0 +1,35 @@ +package git + +import ( + "bytes" + "io" + "strings" + + "gopkg.in/src-d/go-git.v2/core" +) + +// File represents git file objects. +type File struct { + Name string + io.Reader + Hash core.Hash +} + +// Contents returns the contents of a file as a string. +func (f *File) Contents() string { + buf := new(bytes.Buffer) + buf.ReadFrom(f) + return buf.String() +} + +// Lines returns a slice of lines from the contents of a file, stripping +// all end of line characters. If the last line is empty (does not end +// in an end of line), it is also stripped. +func (f *File) Lines() []string { + splits := strings.Split(f.Contents(), "\n") + // remove the last line if it is empty + if splits[len(splits)-1] == "" { + return splits[:len(splits)-1] + } + return splits +} diff --git a/file_test.go b/file_test.go new file mode 100644 index 0000000..8c22bb3 --- /dev/null +++ b/file_test.go @@ -0,0 +1,133 @@ +package git + +import ( + "os" + + "gopkg.in/src-d/go-git.v2/core" + "gopkg.in/src-d/go-git.v2/formats/packfile" + + . "gopkg.in/check.v1" +) + +type SuiteFile struct { + repos map[string]*Repository +} + +var _ = Suite(&SuiteFile{}) + +// create the repositories of the fixtures +func (s *SuiteFile) SetUpSuite(c *C) { + fixtureRepos := [...]struct { + url string + packfile string + }{ + {"https://github.com/tyba/git-fixture.git", "formats/packfile/fixtures/git-fixture.ofs-delta"}, + } + s.repos = make(map[string]*Repository, 0) + for _, fixRepo := range fixtureRepos { + s.repos[fixRepo.url] = NewPlainRepository() + + d, err := os.Open(fixRepo.packfile) + c.Assert(err, IsNil) + + r := packfile.NewReader(d) + r.Format = packfile.OFSDeltaFormat + + _, err = r.Read(s.repos[fixRepo.url].Storage) + c.Assert(err, IsNil) + + c.Assert(d.Close(), IsNil) + } +} + +var contentsTests = []struct { + repo string // the repo name as in localRepos + commit string // the commit to search for the file + path string // the path of the file to find + contents string // expected contents of the file +}{ + { + "https://github.com/tyba/git-fixture.git", + "b029517f6300c2da0f4b651b8642506cd6aaf45d", + ".gitignore", + `*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +`, + }, + { + "https://github.com/tyba/git-fixture.git", + "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "CHANGELOG", + `Initial changelog +`, + }, +} + +func (s *SuiteFile) TestContents(c *C) { + for i, t := range contentsTests { + commit, err := s.repos[t.repo].Commit(core.NewHash(t.commit)) + c.Assert(err, IsNil, Commentf("subtest %d: %v (%s)", i, err, t.commit)) + + file, err := commit.File(t.path) + c.Assert(err, IsNil) + c.Assert(file.Contents(), Equals, t.contents, Commentf( + "subtest %d: commit=%s, path=%s", i, t.commit, t.path)) + } +} + +var linesTests = []struct { + repo string // the repo name as in localRepos + commit string // the commit to search for the file + path string // the path of the file to find + lines []string // expected lines in the file +}{ + { + "https://github.com/tyba/git-fixture.git", + "b029517f6300c2da0f4b651b8642506cd6aaf45d", + ".gitignore", + []string{ + "*.class", + "", + "# Mobile Tools for Java (J2ME)", + ".mtj.tmp/", + "", + "# Package Files #", + "*.jar", + "*.war", + "*.ear", + "", + "# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml", + "hs_err_pid*", + }, + }, + { + "https://github.com/tyba/git-fixture.git", + "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "CHANGELOG", + []string{ + "Initial changelog", + }, + }, +} + +func (s *SuiteFile) TestLines(c *C) { + for i, t := range linesTests { + commit, err := s.repos[t.repo].Commit(core.NewHash(t.commit)) + c.Assert(err, IsNil, Commentf("subtest %d: %v (%s)", i, err, t.commit)) + + file, err := commit.File(t.path) + c.Assert(err, IsNil) + c.Assert(file.Lines(), DeepEquals, t.lines, Commentf( + "subtest %d: commit=%s, path=%s", i, t.commit, t.path)) + } +} diff --git a/repository.go b/repository.go index 32a6fcf..e63869a 100644 --- a/repository.go +++ b/repository.go @@ -20,6 +20,7 @@ const ( type Repository struct { Remotes map[string]*Remote Storage *core.RAWObjectStorage + URL string } // NewRepository creates a new repository setting remote as default remote @@ -39,6 +40,7 @@ func NewRepository(url string, auth common.AuthMethod) (*Repository, error) { r := NewPlainRepository() r.Remotes[DefaultRemoteName] = remote + r.URL = url return r, nil } diff --git a/repository_test.go b/repository_test.go index a8fe50a..20aaf0c 100644 --- a/repository_test.go +++ b/repository_test.go @@ -1,9 +1,10 @@ package git import ( - . "gopkg.in/check.v1" "gopkg.in/src-d/go-git.v2/clients/http" "gopkg.in/src-d/go-git.v2/core" + + . "gopkg.in/check.v1" ) type SuiteRepository struct{} @@ -14,6 +15,7 @@ func (s *SuiteRepository) TestNewRepository(c *C) { r, err := NewRepository(RepositoryFixture, nil) c.Assert(err, IsNil) c.Assert(r.Remotes["origin"].Auth, IsNil) + c.Assert(r.URL, Equals, RepositoryFixture) } func (s *SuiteRepository) TestNewRepositoryWithAuth(c *C) { diff --git a/revlist/revlist.go b/revlist/revlist.go index f34ddb5..181e56d 100644 --- a/revlist/revlist.go +++ b/revlist/revlist.go @@ -12,26 +12,25 @@ // The current implementation tries to get something similar to what you // whould get using git-revlist. See the failing tests for some // insight about how the current implementation and git-revlist differs. +// +// Another way to get the revision history for a file is: +// git log --follow -p -- file package revlist import ( "bytes" - "errors" "io" "sort" - "github.com/sergi/go-diff/diffmatchpatch" - "gopkg.in/src-d/go-git.v2" "gopkg.in/src-d/go-git.v2/core" "gopkg.in/src-d/go-git.v2/diff" -) -// New errors defined by the package. -var ErrFileNotFound = errors.New("file not found") + "github.com/sergi/go-diff/diffmatchpatch" +) // A Revs is a list of revisions for a file (basically a list of commits). -// It implements sort.Interface. +// It implements sort.Interface using the commit time. type Revs []*git.Commit func (l Revs) Len() int { @@ -48,7 +47,7 @@ func (l Revs) Swap(i, j int) { } // for debugging -func (l Revs) String() string { +func (l Revs) GoString() string { var buf bytes.Buffer for _, c := range l { buf.WriteString(c.Hash.String()[:8]) @@ -57,16 +56,16 @@ func (l Revs) String() string { return buf.String() } -// New returns a Revs pointer for the -// file at "path", from commit "commit" backwards in time. -// The commits are stored in arbitrary order. +// NewRevs returns a Revs pointer for the +// file at "path", from commit "commit". +// The commits are sorted in commit order. // It stops searching a branch for a file upon reaching the commit -// were it was created. +// were the file was created. // Moves and copies are not currently supported. -// Cherry-picks are not detected and therefore are added to the list +// Cherry-picks are not detected unless there are no commits between +// them and therefore can appear repeated in the list. // (see git path-id for hints on how to fix this). -// This function implements is equivalent to running go-rev-Revs. -func New(repo *git.Repository, commit *git.Commit, path string) (Revs, error) { +func NewRevs(repo *git.Repository, commit *git.Commit, path string) (Revs, error) { result := make(Revs, 0) seen := make(map[core.Hash]struct{}, 0) err := walkGraph(&result, &seen, repo, commit, path) @@ -74,7 +73,7 @@ func New(repo *git.Repository, commit *git.Commit, path string) (Revs, error) { return nil, err } sort.Sort(result) - result = removeComp(path, result, equivalent) // for merges of identical cherry-picks + result, err = removeComp(path, result, equivalent) // for merges of identical cherry-picks if err != nil { return nil, err } @@ -91,10 +90,12 @@ func walkGraph(result *Revs, seen *map[core.Hash]struct{}, repo *git.Repository, (*seen)[current.Hash] = struct{}{} // if the path is not in the current commit, stop searching. - if _, found := git.FindFile(path, current); !found { + if _, err := current.File(path); err != nil { return nil } + // optimization: don't traverse branches that does not + // contain the path. parents := parentsContainingPath(path, current) switch len(parents) { @@ -103,7 +104,6 @@ func walkGraph(result *Revs, seen *map[core.Hash]struct{}, repo *git.Repository, // stop searching. This includes the case when current is the // initial commit. case 0: - //fmt.Println(current.Hash.String(), ": case 0") *result = append(*result, current) return nil case 1: // only one parent contains the path @@ -113,7 +113,6 @@ func walkGraph(result *Revs, seen *map[core.Hash]struct{}, repo *git.Repository, return err } if len(different) == 1 { - //fmt.Println(current.Hash.String(), ": case 1") *result = append(*result, current) } // in any case, walk the parent @@ -144,7 +143,7 @@ func parentsContainingPath(path string, c *git.Commit) []*git.Commit { } panic("unreachable") } - if _, found := git.FindFile(path, parent); found { + if _, err := parent.File(path); err == nil { result = append(result, parent) } } @@ -156,7 +155,7 @@ func differentContents(path string, c *git.Commit, cs []*git.Commit) ([]*git.Com result := make([]*git.Commit, 0, len(cs)) h, found := blobHash(path, c) if !found { - return nil, ErrFileNotFound + return nil, git.ErrFileNotFound } for _, cx := range cs { if hx, found := blobHash(path, cx); found && h != hx { @@ -168,8 +167,8 @@ func differentContents(path string, c *git.Commit, cs []*git.Commit) ([]*git.Com // blobHash returns the hash of a path in a commit func blobHash(path string, commit *git.Commit) (hash core.Hash, found bool) { - file, found := git.FindFile(path, commit) - if !found { + file, err := commit.File(path) + if err != nil { var empty core.Hash return empty, found } @@ -179,48 +178,71 @@ func blobHash(path string, commit *git.Commit) (hash core.Hash, found bool) { // Returns a new slice of commits, with duplicates removed. Expects a // sorted commit list. Duplication is defined according to "comp". It // will always keep the first commit of a series of duplicated commits. -func removeComp(path string, cs []*git.Commit, comp func(string, *git.Commit, *git.Commit) bool) []*git.Commit { +func removeComp(path string, cs []*git.Commit, comp func(string, *git.Commit, *git.Commit) (bool, error)) ([]*git.Commit, error) { result := make([]*git.Commit, 0, len(cs)) if len(cs) == 0 { - return result + return result, nil } result = append(result, cs[0]) for i := 1; i < len(cs); i++ { - if !comp(path, cs[i], cs[i-1]) { + equals, err := comp(path, cs[i], cs[i-1]) + if err != nil { + return nil, err + } + if !equals { result = append(result, cs[i]) } } - return result + return result, nil } // Equivalent commits are commits whos patch is the same. -func equivalent(path string, a, b *git.Commit) bool { +func equivalent(path string, a, b *git.Commit) (bool, error) { numParentsA := a.NumParents() numParentsB := b.NumParents() // the first commit is not equivalent to anyone // and "I think" merges can not be equivalent to anything if numParentsA != 1 || numParentsB != 1 { - return false + return false, nil } - iterA := a.Parents() - parentA, _ := iterA.Next() - iterB := b.Parents() - parentB, _ := iterB.Next() + diffsA, err := patch(a, path) + if err != nil { + return false, err + } + diffsB, err := patch(b, path) + if err != nil { + return false, err + } - dataA, _ := git.Data(path, a) - dataParentA, _ := git.Data(path, parentA) - dataB, _ := git.Data(path, b) - dataParentB, _ := git.Data(path, parentB) + return sameDiffs(diffsA, diffsB), nil +} - diffsA := diff.Do(dataParentA, dataA) - diffsB := diff.Do(dataParentB, dataB) +func patch(c *git.Commit, path string) ([]diffmatchpatch.Diff, error) { + // get contents of the file in the commit + file, err := c.File(path) + if err != nil { + return nil, err + } + content := file.Contents() - if sameDiffs(diffsA, diffsB) { - return true + // get contents of the file in the first parent of the commit + var contentParent string + iter := c.Parents() + parent, err := iter.Next() + if err != nil { + return nil, err } - return false + file, err = parent.File(path) + if err != nil { + contentParent = "" + } else { + contentParent = file.Contents() + } + + // compare the contents of parent and child + return diff.Do(content, contentParent), nil } func sameDiffs(a, b []diffmatchpatch.Diff) bool { diff --git a/revlist/revlist_test.go b/revlist/revlist_test.go index 7aea54c..2fe7c83 100644 --- a/revlist/revlist_test.go +++ b/revlist/revlist_test.go @@ -51,41 +51,40 @@ func (s *SuiteCommon) SetUpSuite(c *C) { var revListTests = [...]struct { // input data to revlist repo string - branch string // TODO: remove this, it is no longer needed for local packfiles commit string path string // expected output data form the revlist revs []string }{ // Tyba git-fixture - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "binary.jpg", []string{ + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "binary.jpg", []string{ "35e85108805c84807bc66a02d91535e1e24b38b9", }}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", []string{ + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", []string{ "b8e471f58bcbca63b07bda20e428190409c2db47", }}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "go/example.go", []string{ + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "go/example.go", []string{ "918c48b83bd081e863dbe1b80f8998f058cd8294", }}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/long.json", []string{ + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/long.json", []string{ "af2d6a6954d532f8ffb47615169c8fdf9d383a1a", }}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/short.json", []string{ + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/short.json", []string{ "af2d6a6954d532f8ffb47615169c8fdf9d383a1a", }}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", []string{ + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", []string{ "b029517f6300c2da0f4b651b8642506cd6aaf45d", }}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "php/crappy.php", []string{ + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "php/crappy.php", []string{ "918c48b83bd081e863dbe1b80f8998f058cd8294", }}, - {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "vendor/foo.go", []string{ + {"https://github.com/tyba/git-fixture.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "vendor/foo.go", []string{ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", }}, - {"https://github.com/jamesob/desk.git", "master", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "LICENSE", []string{ + {"https://github.com/jamesob/desk.git", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "LICENSE", []string{ "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", }}, - {"https://github.com/jamesob/desk.git", "master", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "README.md", []string{ + {"https://github.com/jamesob/desk.git", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "README.md", []string{ "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", "2e87a2dcc63a115f9a61bd969d1e85fb132a431b", "215b0ac06225b0671bc3460d10da88c3406f796f", @@ -111,25 +110,25 @@ var revListTests = [...]struct { "d3f3c8faca048d11709969fbfc0cdf2901b87578", "8777dde1abe18c805d021366643218d3f3356dd9", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/reconfigure_spinnaker.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/reconfigure_spinnaker.py", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/validate_configuration.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/validate_configuration.py", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", "1e3d328a2cabda5d0aaddc5dec65271343e0dc37", "b5d999e2986e190d81767cd3cfeda0260f9f6fb8", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/fetch.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/fetch.py", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/yaml_util.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/yaml_util.py", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", "b5d999e2986e190d81767cd3cfeda0260f9f6fb8", "023d4fb17b76e0fe0764971df8b8538b735a1d67", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "dev/build_release.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "dev/build_release.py", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", "f42771ba298b93a7c4f5b16c5b30ab96c15305a8", @@ -141,10 +140,10 @@ var revListTests = [...]struct { "5422a86a10a8c5a1ef6728f5fc8894d9a4c54cb9", "09a4ea729b25714b6368959eea5113c99938f7b6", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pkg_scripts/postUninstall.sh", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pkg_scripts/postUninstall.sh", []string{ "ce9f123d790717599aaeb76bc62510de437761be", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/first_google_boot.sh", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/first_google_boot.sh", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", "de25f576b888569192e6442b0202d30ca7b2d8ec", "a596972a661d9a7deca8abd18b52ce1a39516e89", @@ -157,13 +156,13 @@ var revListTests = [...]struct { "a57b08a9072f6a865f760551be2a4944f72f804a", "0777fadf4ca6f458d7071de414f9bd5417911037", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_spinnaker.sh", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_spinnaker.sh", []string{ "0d9c9cef53af38cefcb6801bb492aaed3f2c9a42", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_fake_openjdk8.sh", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_fake_openjdk8.sh", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_spinnaker.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_spinnaker.py", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", "37f94770d81232b1895fca447878f68d65aac652", "46c9dcbb55ca3f4735e82ad006e8cae2fdd050d9", @@ -174,10 +173,10 @@ var revListTests = [...]struct { "739d8c6fe16edcb6ef9185dc74197de561b84315", "d33c2d1e350b03fb989eefc612e8c9d5fa7cadc2", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/__init__.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/__init__.py", []string{ "a24001f6938d425d0e7504bdf5d27fc866a85c3d", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "experimental/docker-compose/docker-compose.yml", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "experimental/docker-compose/docker-compose.yml", []string{ "fda357835d889595dc39dfebc6181d863cce7d4f", "57c59e7144354a76e1beba69ae2f85db6b1727af", "7682dff881029c722d893a112a64fea6849a0428", @@ -190,7 +189,7 @@ var revListTests = [...]struct { "ddaae195b628150233b0a48f50a1674fd9d1a924", "7119ad9cf7d4e4d8b059e5337374baae4adc7458", }}, - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/validate_configuration_test.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/validate_configuration_test.py", []string{ "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", "1e3d328a2cabda5d0aaddc5dec65271343e0dc37", }}, @@ -198,19 +197,19 @@ var revListTests = [...]struct { // FAILS /* // this contains an empty move - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "google/dev/build_google_tarball.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "google/dev/build_google_tarball.py", []string{ "88e60ac93f832efc2616b3c165e99a8f2ffc3e0c", "9e49443da49b8c862cc140b660744f84eebcfa51", }}, */ /* - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/yaml_util_test.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/yaml_util_test.py", []string{ "edf909edb9319c5e615e4ce73da47bbdca388ebe", "023d4fb17b76e0fe0764971df8b8538b735a1d67", }}, */ /* - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/configurator_test.py", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/configurator_test.py", []string{ "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", "edf909edb9319c5e615e4ce73da47bbdca388ebe", "d14f793a6cd7169ef708a4fc276ad876bd3edd4e", @@ -219,7 +218,7 @@ var revListTests = [...]struct { */ /* // this contains a cherry-pick at 094d0e7d5d691 (with 3f34438d) - {"https://github.com/jamesob/desk.git", "master", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "desk", []string{ + {"https://github.com/jamesob/desk.git", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "desk", []string{ "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", "a0c1e853158ccbaf95574220bbf3b54509034a9f", "decfc524570c407d6bba0f217e534c8b47dbdbee", @@ -250,7 +249,7 @@ var revListTests = [...]struct { }}, */ /* - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "InstallSpinnaker.sh", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "InstallSpinnaker.sh", []string{ "ce9f123d790717599aaeb76bc62510de437761be", "23673af3ad70b50bba7fdafadc2323302f5ba520", "b7015a5d36990d69a054482556127b9c7404a24a", @@ -286,7 +285,7 @@ var revListTests = [...]struct { }}, */ /* - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "config/default-spinnaker-local.yml", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "config/default-spinnaker-local.yml", []string{ "ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", "99534ecc895fe17a1d562bb3049d4168a04d0865", "caf6d62e8285d4681514dd8027356fb019bc97ff", @@ -303,7 +302,7 @@ var revListTests = [...]struct { }}, */ /* - {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "config/spinnaker.yml", []string{ + {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "config/spinnaker.yml", []string{ "ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", "caf6d62e8285d4681514dd8027356fb019bc97ff", "eaf7614cad81e8ab5c813dd4821129d0c04ea449", @@ -327,25 +326,28 @@ func (s *SuiteCommon) TestRevList(c *C) { commit, err := repo.Commit(core.NewHash(t.commit)) c.Assert(err, IsNil) - revs, err := New(repo, commit, t.path) - c.Assert(err, IsNil) + revs, err := NewRevs(repo, commit, t.path) + c.Assert(err, IsNil, Commentf("\nrepo=%s, commit=%s, path=%s\n", + t.repo, t.commit, t.path)) - c.Assert(len(revs), Equals, len(t.revs), Commentf("\nrepo=%s, branch=%s, commit=%s, path=%s\n EXPECTED (len %d)\n%s\n OBTAINED (len %d)\n%s\n", - t.repo, t.branch, t.commit, t.path, len(t.revs), t.revs, len(revs), revs.String())) + c.Assert(len(revs), Equals, len(t.revs), Commentf("\nrepo=%s, commit=%s, path=%s\n EXPECTED (len %d)\n%s\n OBTAINED (len %d)\n%s\n", + t.repo, t.commit, t.path, len(t.revs), t.revs, len(revs), revs.GoString())) for i := range revs { if revs[i].Hash.String() != t.revs[i] { commit, err := repo.Commit(core.NewHash(t.revs[i])) c.Assert(err, IsNil) - if equivalent(t.path, revs[i], commit) { + equiv, err := equivalent(t.path, revs[i], commit) + c.Assert(err, IsNil) + if equiv { fmt.Printf("cherry-pick detected: %s %s\n", revs[i].Hash.String(), t.revs[i]) } else { - c.Fatalf("\nrepo=%s, branch=%s, commit=%s, path=%s, \n%s", - t.repo, t.branch, t.commit, t.path, compareSideBySide(t.revs, revs)) + c.Fatalf("\nrepo=%s, commit=%s, path=%s, \n%s", + t.repo, t.commit, t.path, compareSideBySide(t.revs, revs)) } } } - fmt.Printf("OK repo=%s, branch=%s, commit=%s, path=%s\n", - t.repo, t.branch, t.commit, t.path) + fmt.Printf("OK repo=%s, commit=%s, path=%s\n", + t.repo, t.commit, t.path) } } @@ -380,7 +382,9 @@ var cherryPicks = [...][]string{ func (s *SuiteCommon) TestEquivalent(c *C) { for _, t := range cherryPicks { cs := s.commits(c, t[0], t[2], t[3]) - c.Assert(equivalent(t[1], cs[0], cs[1]), Equals, true, Commentf("repo=%s, file=%s, a=%s b=%s", t[0], t[1], t[2], t[3])) + equiv, err := equivalent(t[1], cs[0], cs[1]) + c.Assert(err, IsNil) + c.Assert(equiv, Equals, true, Commentf("repo=%s, file=%s, a=%s b=%s", t[0], t[1], t[2], t[3])) } } @@ -115,9 +115,3 @@ func (i *TreeIter) Next() (*Tree, error) { tree := &Tree{r: i.r} return tree, tree.Decode(obj) } - -type File struct { - Name string - io.Reader - Hash core.Hash -} |