diff options
author | Alberto Cortés <alberto@sourced.tech> | 2015-11-16 03:20:01 +0100 |
---|---|---|
committer | Alberto Cortés <alberto@sourced.tech> | 2015-11-25 11:09:51 +0100 |
commit | d643cea1e8a6d618b2eddfdbed086c7bdf208658 (patch) | |
tree | b862c72ccb674910d24eac2ab424a7fb5ea3f0fb | |
parent | caab43e7f4ee10a15b2af826485b688473b34356 (diff) | |
download | go-git-d643cea1e8a6d618b2eddfdbed086c7bdf208658.tar.gz |
Blame support for files
This also includes a diff package and revlist package (needed by
blame)
Some extra packfiles (<1MB) are also included, to be used as fixtures in
the tests.
-rw-r--r-- | blame/blame.go | 223 | ||||
-rwxr-xr-x | blame/blame2humantest.bash | 47 | ||||
-rw-r--r-- | blame/blame_test.go | 810 | ||||
-rw-r--r-- | commit.go | 5 | ||||
-rw-r--r-- | common.go | 49 | ||||
-rw-r--r-- | common_test.go | 111 | ||||
-rw-r--r-- | diff/diff.go | 45 | ||||
-rw-r--r-- | diff/diff_ext_test.go | 50 | ||||
-rw-r--r-- | revlist/revlist.go | 250 | ||||
-rwxr-xr-x | revlist/revlist2humantest.bash | 36 | ||||
-rw-r--r-- | revlist/revlist_test.go | 398 | ||||
-rw-r--r-- | tree.go | 3 |
12 files changed, 2026 insertions, 1 deletions
diff --git a/blame/blame.go b/blame/blame.go new file mode 100644 index 0000000..774362b --- /dev/null +++ b/blame/blame.go @@ -0,0 +1,223 @@ +// Package blame contains blaming functionality for files in the repo. +// +// Blaming a file is finding what commit was the last to modify each of +// the lines in the file, therefore the output of a blaming operation is +// usualy a slice of commits, one commit per line in the file. +// +// This package also provides a pretty print function to output the +// results of a blame in a similar format to the git-blame command. +package blame + +import ( + "bytes" + "fmt" + "sort" + "strconv" + "strings" + "unicode/utf8" + + "gopkg.in/src-d/go-git.v2" + "gopkg.in/src-d/go-git.v2/core" + "gopkg.in/src-d/go-git.v2/diff" + "gopkg.in/src-d/go-git.v2/revlist" +) + +// Blame returns the last commit that modified each line of a file in +// a repository. +// +// The file to blame is identified by the input arguments: repo, commit and path. +// The output is a slice of commits, one for each line in the file. +// +// Blaming a file is a two step process: +// +// 1. Create a linear history of the commits affecting a file. We use +// revlist.New for that. +// +// 2. Then build a graph with a node for every line in every file in +// the history of the file. +// +// Each node (line) holds the commit where it was introduced or +// last modified. To achieve that we use the FORWARD algorithm +// described in Zimmermann, et al. "Mining Version Archives for +// Co-changed Lines", in proceedings of the Mining Software +// Repositories workshop, Shanghai, May 22-23, 2006. +// +// Each node is asigned a commit: Start by the nodes in the first +// commit. Assign that commit as the creator of all its lines. +// +// Then jump to the nodes in the next commit, and calculate the diff +// between the two files. Newly created lines get +// assigned the new commit as its origin. Modified lines also get +// this new commit. Untouched lines retain the old commit. +// +// All this work is done in the assignOrigin function. +// +// This function holds all the internal relevant data in a blame +// struct, that is not exported. +// +// TODO: ways to improve the efficiency of this function: +// +// 1. Improve revlist +// +// 2. Improve how to traverse the history (example a backward +// traversal will be much more efficient) +// +// TODO: ways to improve the functrion 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) { + // 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) + 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)) + + // init the graph + b.graph = make([][]vertex, len(b.revs)) + + // for all 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 + } + // count its lines + nLines := git.CountLines(b.data[i]) + // create a node for each line + b.graph[i] = make([]vertex, 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]) + } + } else { + // if this is not the first commit, then assign to the old + // commit or to the new one, depending on what the diff + // says. + b.assignOrigin(i, i-1) + } + } + + // fill in the output results: copy the nodes of the last revision + // into the result. + fVs := b.graph[len(b.graph)-1] + 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 +} + +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) { + // assign origin based on diff info + hunks := diff.Do(b.data[p], b.data[c]) + sl := -1 // source line + dl := -1 // destination line + 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++ + dl++ + b.graph[c][dl] = b.graph[p][sl] + case hunks[h].Type == 1: + dl++ + b.graph[c][dl] = vertex(b.revs[c]) + case hunks[h].Type == -1: + sl++ + default: + panic("unreachable") + } + } + } +} + +// This will print the results of a Blame as in git-blame. +func (b *blame) PrettyPrint() string { + var buf bytes.Buffer + + contents, found := git.Data(b.path, b.fRev) + if !found { + panic("PrettyPrint: internal error in repo.Data") + } + + lines := strings.Split(contents, "\n") + // max line number length + mlnl := len(fmt.Sprintf("%s", strconv.Itoa(len(lines)))) + // max author length + mal := b.maxAuthorLength() + format := fmt.Sprintf("%%s (%%-%ds %%%dd) %%s\n", + mal, mlnl) + + fVs := b.graph[len(b.graph)-1] + for ln, v := range fVs { + fmt.Fprintf(&buf, format, v.Hash.String()[:8], + prettyPrintAuthor(fVs[ln]), ln+1, lines[ln]) + } + return buf.String() +} + +// utility function to pretty print the author. +func prettyPrintAuthor(c *git.Commit) string { + return fmt.Sprintf("%s %s", c.Author.Name, c.Author.When.Format("2006-01-02")) +} + +// utility function to calculate the number of runes needed +// to print the longest author name in the blame of a file. +func (b *blame) maxAuthorLength() int { + memo := make(map[core.Hash]struct{}, len(b.graph)-1) + fVs := b.graph[len(b.graph)-1] + m := 0 + for ln := range fVs { + if _, ok := memo[fVs[ln].Hash]; ok { + continue + } + memo[fVs[ln].Hash] = struct{}{} + m = max(m, utf8.RuneCountInString(prettyPrintAuthor(fVs[ln]))) + } + return m +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/blame/blame2humantest.bash b/blame/blame2humantest.bash new file mode 100755 index 0000000..259988f --- /dev/null +++ b/blame/blame2humantest.bash @@ -0,0 +1,47 @@ +#!/bin/bash + +set -e + +repo=`git remote show origin | grep Fetch | cut -d' ' -f5` +branch="master" +if [ "$#" -eq 1 ] ; then + commit=`git log | head -1 | cut -d' ' -f2` + path=$1 +elif [ "$#" -eq 2 ] ; then + commit=$1 + path=$2 +else + echo "bad number of parameters" > /dev/stderr + echo > /dev/stderr + echo " try with: [commit] path" > /dev/stderr + exit +fi + +blames=`git blame --root $path | cut -d' ' -f1` +declare -a blame +i=0 +for shortBlame in $blames ; do + blame[$i]=`git show $shortBlame | head -1 | cut -d' ' -f2` + i=`expr $i + 1` +done + +# some remotes have the .git, other don't, +# repoDot makes sure all have +repoDot="${repo%.git}.git" + +echo -e "\t{\"${repoDot}\", \"${branch}\", \"${commit}\", \"${path}\", concat(&[]string{}," +prev="" +count=1 +for i in ${blame[@]} ; do + if [ "${prev}" == "" ] ; then + prev=$i + elif [ "$prev" == "$i" ] ; then + count=`expr $count + 1` + else + echo -e "\t\trepeat(\"${prev}\", $count)," + count=1 + prev=$i + fi +done +echo -e "\t\trepeat(\"${prev}\", $count)," +echo -e "\t)}," diff --git a/blame/blame_test.go b/blame/blame_test.go new file mode 100644 index 0000000..81c02f0 --- /dev/null +++ b/blame/blame_test.go @@ -0,0 +1,810 @@ +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" +) + +func Test(t *testing.T) { TestingT(t) } + +type SuiteCommon struct { + repos map[string]*git.Repository +} + +var _ = Suite(&SuiteCommon{}) + +var fixtureRepos = [...]struct { + url string + packfile string +}{ + {"https://github.com/tyba/git-fixture.git", "../formats/packfile/fixtures/git-fixture.ofs-delta"}, + {"https://github.com/spinnaker/spinnaker.git", "../formats/packfile/fixtures/spinnaker-spinnaker.pack"}, +} + +// create the repositories of the fixtures +func (s *SuiteCommon) SetUpSuite(c *C) { + s.repos = make(map[string]*git.Repository, 0) + for _, fixRepo := range fixtureRepos { + s.repos[fixRepo.url] = git.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 blameTests = [...]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 + blames []string +}{ + // use the blame2humantest.bash script to easily add more tests. + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "binary.jpg", concat(&[]string{}, + repeat("35e85108805c84807bc66a02d91535e1e24b38b9", 285), + )}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", concat(&[]string{}, + repeat("b8e471f58bcbca63b07bda20e428190409c2db47", 1), + )}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "go/example.go", concat(&[]string{}, + repeat("918c48b83bd081e863dbe1b80f8998f058cd8294", 142), + )}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/long.json", concat(&[]string{}, + repeat("af2d6a6954d532f8ffb47615169c8fdf9d383a1a", 6492), + )}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/short.json", concat(&[]string{}, + repeat("af2d6a6954d532f8ffb47615169c8fdf9d383a1a", 22), + )}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", concat(&[]string{}, + repeat("b029517f6300c2da0f4b651b8642506cd6aaf45d", 22), + )}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "php/crappy.php", concat(&[]string{}, + repeat("918c48b83bd081e863dbe1b80f8998f058cd8294", 259), + )}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "vendor/foo.go", concat(&[]string{}, + repeat("6ecf0ef2c2dffb796033e5a02219af86ec6584e5", 7), + )}, + /* + // Failed + {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "InstallSpinnaker.sh", concat(&[]string{}, + repeat("ce9f123d790717599aaeb76bc62510de437761be", 2), + repeat("a47d0aaeda421f06df248ad65bd58230766bf118", 1), + repeat("23673af3ad70b50bba7fdafadc2323302f5ba520", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 29), + repeat("9a06d3f20eabb254d0a1e2ff7735ef007ccd595e", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 4), + repeat("a47d0aaeda421f06df248ad65bd58230766bf118", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 5), + repeat("0c5bb1e4392e751f884f3c57de5d4aee72c40031", 2), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 3), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 7), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 2), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 5), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 7), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 3), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 6), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 10), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 4), + repeat("0c5bb1e4392e751f884f3c57de5d4aee72c40031", 2), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 2), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 4), + repeat("23673af3ad70b50bba7fdafadc2323302f5ba520", 4), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 4), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 1), + repeat("0c5bb1e4392e751f884f3c57de5d4aee72c40031", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 13), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 2), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 6), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 2), + repeat("0c5bb1e4392e751f884f3c57de5d4aee72c40031", 1), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 4), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 3), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 2), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 4), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 3), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 15), + repeat("b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", 1), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 1), + repeat("b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", 8), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 2), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 12), + repeat("505577dc87d300cf562dc4702a05a5615d90d855", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 5), + repeat("370d61cdbc1f3c90db6759f1599ccbabd40ad6c1", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 4), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 1), + repeat("b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", 5), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 3), + repeat("b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", 2), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 2), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 9), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 1), + repeat("b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", 3), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 4), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("8eb116de9128c314ac8a6f5310ca500b8c74f5db", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 6), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 6), + repeat("d2f6214b625db706384b378a29cc4c22237db97a", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 3), + repeat("d2f6214b625db706384b378a29cc4c22237db97a", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 4), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 1), + repeat("c9c2a0ec03968ab17e8b16fdec9661eb1dbea173", 1), + repeat("d2f6214b625db706384b378a29cc4c22237db97a", 2), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 12), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 5), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 3), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 5), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 3), + repeat("a47d0aaeda421f06df248ad65bd58230766bf118", 5), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 5), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 2), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 1), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("b2c7142082d52b09ca20228606c31c7479c0833e", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("495c7118e7cf757aa04eab410b64bfb5b5149ad2", 1), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 1), + repeat("495c7118e7cf757aa04eab410b64bfb5b5149ad2", 3), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 1), + repeat("495c7118e7cf757aa04eab410b64bfb5b5149ad2", 1), + repeat("50d0556563599366f29cb286525780004fa5a317", 1), + repeat("dd2d03c19658ff96d371aef00e75e2e54702da0e", 1), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 1), + repeat("dd2d03c19658ff96d371aef00e75e2e54702da0e", 2), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 2), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 1), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("b5c6053a46993b20d1b91e7b7206bffa54669ad7", 1), + repeat("9e74d009894d73dd07773ea6b3bdd8323db980f7", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", 4), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 1), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 1), + repeat("d2f6214b625db706384b378a29cc4c22237db97a", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 3), + repeat("b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", 2), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 2), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 4), + repeat("d2f6214b625db706384b378a29cc4c22237db97a", 1), + repeat("b7015a5d36990d69a054482556127b9c7404a24a", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 5), + repeat("b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", 2), + repeat("d2f6214b625db706384b378a29cc4c22237db97a", 1), + repeat("ce9f123d790717599aaeb76bc62510de437761be", 5), + repeat("ba486de7c025457963701114c683dcd4708e1dee", 4), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 1), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 3), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 1), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 3), + repeat("6328ee836affafc1b52127147b5ca07300ac78e6", 2), + repeat("01e65d67eed8afcb67a6bdf1c962541f62b299c9", 3), + repeat("3de4f77c105f700f50d9549d32b9a05a01b46c4b", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 2), + repeat("370d61cdbc1f3c90db6759f1599ccbabd40ad6c1", 6), + repeat("dd7e66c862209e8b912694a582a09c0db3227f0d", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 2), + repeat("dd7e66c862209e8b912694a582a09c0db3227f0d", 3), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("dd7e66c862209e8b912694a582a09c0db3227f0d", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 3), + )}, + */ + {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/reconfigure_spinnaker.py", concat(&[]string{}, + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 22), + repeat("c89dab0d42f1856d157357e9010f8cc6a12f5b1f", 7), + )}, + {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/validate_configuration.py", concat(&[]string{}, + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 29), + repeat("1e3d328a2cabda5d0aaddc5dec65271343e0dc37", 19), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 15), + repeat("b5d999e2986e190d81767cd3cfeda0260f9f6fb8", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 12), + repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), + repeat("b5d999e2986e190d81767cd3cfeda0260f9f6fb8", 8), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), + repeat("b5d999e2986e190d81767cd3cfeda0260f9f6fb8", 4), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 46), + repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), + repeat("1e3d328a2cabda5d0aaddc5dec65271343e0dc37", 42), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), + repeat("1e3d328a2cabda5d0aaddc5dec65271343e0dc37", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), + repeat("1e3d328a2cabda5d0aaddc5dec65271343e0dc37", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), + repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 8), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), + repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 2), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), + repeat("1e3d328a2cabda5d0aaddc5dec65271343e0dc37", 3), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 12), + repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 10), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 69), + repeat("b5d999e2986e190d81767cd3cfeda0260f9f6fb8", 7), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), + )}, + {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/run.py", concat(&[]string{}, + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 185), + )}, + /* + // Fail by 3 + {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/configurator.py", concat(&[]string{}, + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 53), + repeat("c89dab0d42f1856d157357e9010f8cc6a12f5b1f", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), + repeat("e805183c72f0426fb073728c01901c2fd2db1da6", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 6), + repeat("023d4fb17b76e0fe0764971df8b8538b735a1d67", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 36), + repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), + repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 3), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), + repeat("c89dab0d42f1856d157357e9010f8cc6a12f5b1f", 13), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), + repeat("c89dab0d42f1856d157357e9010f8cc6a12f5b1f", 18), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), + repeat("1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", 1), + repeat("023d4fb17b76e0fe0764971df8b8538b735a1d67", 17), + repeat("c89dab0d42f1856d157357e9010f8cc6a12f5b1f", 1), + 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{}, + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 1), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 7), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 2), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 2), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 3), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 1), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 10), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 11), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 29), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 7), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 58), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 1), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 1), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 2), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 2), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 13), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 4), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 3), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 13), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 2), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 9), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 3), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 1), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 17), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 3), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 6), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 6), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 3), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 5), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 4), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 3), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + repeat("11d6c1020b1765e236ca65b2709d37b5bfdba0f4", 2), + repeat("bc02440df2ff95a014a7b3cb11b98c3a2bded777", 1), + 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{}, + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 17), + repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 43), + repeat("d2838db9f6ef9628645e7d04cd9658a83e8708ea", 1), + repeat("637ba49300f701cfbd859c1ccf13c4f39a9ba1c8", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 13), + )}, + /* + // fail a few lines + {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/default-spinnaker-local.yml", concat(&[]string{}, + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 9), + repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 2), + repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 2), + repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 5), + repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 2), + repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 5), + repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 25), + repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), + repeat("eaf7614cad81e8ab5c813dd4821129d0c04ea449", 1), + repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 24), + repeat("974b775a8978b120ff710cac93a21c7387b914c9", 2), + repeat("3ce7b902a51bac2f10994f7d1f251b616c975e54", 1), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 6), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 14), + repeat("7c8d9a6081d9cb7a56c479bfe64d70540ea32795", 5), + 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{}, + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 32), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 2), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 1), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 6), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 2), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 3), + repeat("7c8d9a6081d9cb7a56c479bfe64d70540ea32795", 3), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 50), + repeat("974b775a8978b120ff710cac93a21c7387b914c9", 2), + repeat("d4553dac205023fa77652308af1a2d1cf52138fb", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 9), + repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), + repeat("eaf7614cad81e8ab5c813dd4821129d0c04ea449", 1), + repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 39), + repeat("079e42e7c979541b6fab7343838f7b9fd4a360cd", 6), + 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{}, + repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 71), + )}, + */ + /* + // FAIL two lines interchanged + {"https://github.com/spinnaker/spinnaker.git", "master", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/bootstrap_dev.sh", concat(&[]string{}, + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 95), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 10), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 7), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 3), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 12), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 2), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 6), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 4), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), + repeat("376599177551c3f04ccc94d71bbb4d037dec0c3f", 2), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 17), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 2), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 2), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 3), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 8), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 4), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 1), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 6), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 4), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 10), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 2), + repeat("fc28a378558cdb5bbc08b6dcb96ee77c5b716760", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 1), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 8), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 1), + repeat("fc28a378558cdb5bbc08b6dcb96ee77c5b716760", 1), + repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 4), + repeat("24551a5d486969a2972ee05e87f16444890f9555", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 2), + repeat("24551a5d486969a2972ee05e87f16444890f9555", 1), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 8), + repeat("838aed816872c52ed435e4876a7b64dba0bed500", 13), + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 5), + repeat("24551a5d486969a2972ee05e87f16444890f9555", 1), + 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{}, + 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 +} @@ -45,6 +45,11 @@ func (c *Commit) Parents() *CommitIter { return i } +// NumParents returns the number of parents in a commit. +func (c *Commit) NumParents() int { + return len(c.parents) +} + // Decode transform an core.Object into a Blob struct func (c *Commit) Decode(o core.Object) error { c.Hash = o.Hash() diff --git a/common.go b/common.go new file mode 100644 index 0000000..3e25fc7 --- /dev/null +++ b/common.go @@ -0,0 +1,49 @@ +package git + +import ( + "bytes" + "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. +func CountLines(s string) int { + if s == "" { + return 0 + } + nEol := strings.Count(s, "\n") + if strings.HasSuffix(s, "\n") { + return nEol + } + 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 cf4fc29..0695d5f 100644 --- a/common_test.go +++ b/common_test.go @@ -8,6 +8,7 @@ import ( . "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" ) func Test(t *testing.T) { TestingT(t) } @@ -42,3 +43,113 @@ func (s *MockGitUploadPackService) Fetch(*common.GitUploadPackRequest) (io.ReadC r, _ := os.Open("formats/packfile/fixtures/git-fixture.ref-delta") return r, nil } + +type SuiteCommon struct { + repos map[string]*Repository +} + +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 +}{ + {"", 0}, + {"a", 1}, + {"a\n", 1}, + {"a\nb", 2}, + {"a\nb\n", 2}, + {"a\nb\nc", 3}, + {"a\nb\nc\n", 3}, + {"first line\n\tsecond line\nthird line\n", 3}, +} + +func (s *SuiteCommon) TestCountLines(c *C) { + for i, t := range countLinesTests { + o := CountLines(t.i) + 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.go b/diff/diff.go new file mode 100644 index 0000000..b840ad6 --- /dev/null +++ b/diff/diff.go @@ -0,0 +1,45 @@ +// Package diff implements line oriented diffs, similar to the ancient +// Unix diff command. +// +// The current implementation is just a wrapper around Sergi's +// go-diff/diffmatchpatch library, which is a go port of Neil +// Fraser's google-diff-match-patch code +package diff + +import ( + "bytes" + + "github.com/sergi/go-diff/diffmatchpatch" +) + +// Do computes the (line oriented) modifications needed to turn the src +// string into the dst string. +func Do(src, dst string) (diffs []diffmatchpatch.Diff) { + dmp := diffmatchpatch.New() + wSrc, wDst, warray := dmp.DiffLinesToChars(src, dst) + diffs = dmp.DiffMain(wSrc, wDst, false) + diffs = dmp.DiffCharsToLines(diffs, warray) + return diffs +} + +// Dst computes and returns the destination text. +func Dst(diffs []diffmatchpatch.Diff) string { + var text bytes.Buffer + for _, d := range diffs { + if d.Type != diffmatchpatch.DiffDelete { + text.WriteString(d.Text) + } + } + return text.String() +} + +// Src computes and returns the source text +func Src(diffs []diffmatchpatch.Diff) string { + var text bytes.Buffer + for _, d := range diffs { + if d.Type != diffmatchpatch.DiffInsert { + text.WriteString(d.Text) + } + } + return text.String() +} diff --git a/diff/diff_ext_test.go b/diff/diff_ext_test.go new file mode 100644 index 0000000..5ab8c7d --- /dev/null +++ b/diff/diff_ext_test.go @@ -0,0 +1,50 @@ +package diff_test + +import ( + "testing" + + . "gopkg.in/check.v1" + + "gopkg.in/src-d/go-git.v2/diff" +) + +func Test(t *testing.T) { TestingT(t) } + +type suiteCommon struct{} + +var _ = Suite(&suiteCommon{}) + +var diffTests = [...]struct { + src string // the src string to diff + dst string // the dst string to diff +}{ + // equal inputs + {"", ""}, + {"a", "a"}, + {"a\n", "a\n"}, + {"a\nb", "a\nb"}, + {"a\nb\n", "a\nb\n"}, + {"a\nb\nc", "a\nb\nc"}, + {"a\nb\nc\n", "a\nb\nc\n"}, + // missing '\n' + {"", "\n"}, + {"\n", ""}, + {"a", "a\n"}, + {"a\n", "a"}, + {"a\nb", "a\nb"}, + {"a\nb\n", "a\nb\n"}, + {"a\nb\nc", "a\nb\nc"}, + {"a\nb\nc\n", "a\nb\nc\n"}, + // generic + {"a\nbbbbb\n\tccc\ndd\n\tfffffffff\n", "bbbbb\n\tccc\n\tDD\n\tffff\n"}, +} + +func (s *suiteCommon) TestCountLines(c *C) { + for i, t := range diffTests { + diffs := diff.Do(t.src, t.dst) + src := diff.Src(diffs) + dst := diff.Dst(diffs) + c.Assert(src, Equals, t.src, Commentf("subtest %d, src=%q, dst=%q, bad calculated src", i, t.src, t.dst)) + c.Assert(dst, Equals, t.dst, Commentf("subtest %d, src=%q, dst=%q, bad calculated dst", i, t.src, t.dst)) + } +} diff --git a/revlist/revlist.go b/revlist/revlist.go new file mode 100644 index 0000000..f34ddb5 --- /dev/null +++ b/revlist/revlist.go @@ -0,0 +1,250 @@ +// Package revlist allows to create the revision history of a file, this +// is, the list of commits in the past that affect the file. +// +// The general idea is to traverse the git commit graph backward, +// flattening the graph into a linear history, and skipping commits that +// are irrelevant for the particular file. +// +// There is no single answer for this operation. The git command +// "git-revlist" returns different histories depending on its arguments +// and some internal heuristics. +// +// 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. +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") + +// A Revs is a list of revisions for a file (basically a list of commits). +// It implements sort.Interface. +type Revs []*git.Commit + +func (l Revs) Len() int { + return len(l) +} + +// sorts from older to newer commit. +func (l Revs) Less(i, j int) bool { + return l[i].Committer.When.Before(l[j].Committer.When) +} + +func (l Revs) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +// for debugging +func (l Revs) String() string { + var buf bytes.Buffer + for _, c := range l { + buf.WriteString(c.Hash.String()[:8]) + buf.WriteString("\n") + } + 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. +// It stops searching a branch for a file upon reaching the commit +// were it was created. +// Moves and copies are not currently supported. +// Cherry-picks are not detected and therefore are added to 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) { + result := make(Revs, 0) + seen := make(map[core.Hash]struct{}, 0) + err := walkGraph(&result, &seen, repo, commit, path) + if err != nil { + return nil, err + } + sort.Sort(result) + result = removeComp(path, result, equivalent) // for merges of identical cherry-picks + if err != nil { + return nil, err + } + return result, nil +} + +// Recursive traversal of the commit graph, generating a linear history +// of the path. +func walkGraph(result *Revs, seen *map[core.Hash]struct{}, repo *git.Repository, current *git.Commit, path string) error { + // check and update seen + if _, ok := (*seen)[current.Hash]; ok { + return nil + } + (*seen)[current.Hash] = struct{}{} + + // if the path is not in the current commit, stop searching. + if _, found := git.FindFile(path, current); !found { + return nil + } + + parents := parentsContainingPath(path, current) + + switch len(parents) { + // if the path is not found in any of its parents, the path was + // created by this commit; we must add it to the revisions list and + // 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 + // if the file contents has change, add the current commit + different, err := differentContents(path, current, parents) + if err != nil { + return err + } + if len(different) == 1 { + //fmt.Println(current.Hash.String(), ": case 1") + *result = append(*result, current) + } + // in any case, walk the parent + return walkGraph(result, seen, repo, parents[0], path) + default: // more than one parent contains the path + // TODO: detect merges that had a conflict, because they must be + // included in the result here. + for _, p := range parents { + err := walkGraph(result, seen, repo, p, path) + if err != nil { + return err + } + } + } + return nil +} + +// TODO: benchmark this making git.Commit.parent public instead of using +// an iterator +func parentsContainingPath(path string, c *git.Commit) []*git.Commit { + var result []*git.Commit + iter := c.Parents() + for { + parent, err := iter.Next() + if err != nil { + if err == io.EOF { + return result + } + panic("unreachable") + } + if _, found := git.FindFile(path, parent); found { + result = append(result, parent) + } + } +} + +// Returns an slice of the commits in "cs" that has the file "path", but with different +// contents than what can be found in "c". +func differentContents(path string, c *git.Commit, cs []*git.Commit) ([]*git.Commit, error) { + result := make([]*git.Commit, 0, len(cs)) + h, found := blobHash(path, c) + if !found { + return nil, ErrFileNotFound + } + for _, cx := range cs { + if hx, found := blobHash(path, cx); found && h != hx { + result = append(result, cx) + } + } + return result, nil +} + +// 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 { + var empty core.Hash + return empty, found + } + return file.Hash, true +} + +// 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 { + result := make([]*git.Commit, 0, len(cs)) + if len(cs) == 0 { + return result + } + result = append(result, cs[0]) + for i := 1; i < len(cs); i++ { + if !comp(path, cs[i], cs[i-1]) { + result = append(result, cs[i]) + } + } + return result +} + +// Equivalent commits are commits whos patch is the same. +func equivalent(path string, a, b *git.Commit) bool { + 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 + } + + iterA := a.Parents() + parentA, _ := iterA.Next() + iterB := b.Parents() + parentB, _ := iterB.Next() + + dataA, _ := git.Data(path, a) + dataParentA, _ := git.Data(path, parentA) + dataB, _ := git.Data(path, b) + dataParentB, _ := git.Data(path, parentB) + + diffsA := diff.Do(dataParentA, dataA) + diffsB := diff.Do(dataParentB, dataB) + + if sameDiffs(diffsA, diffsB) { + return true + } + return false +} + +func sameDiffs(a, b []diffmatchpatch.Diff) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if !sameDiff(a[i], b[i]) { + return false + } + } + return true +} + +func sameDiff(a, b diffmatchpatch.Diff) bool { + if a.Type != b.Type { + return false + } + switch a.Type { + case 0: + return git.CountLines(a.Text) == git.CountLines(b.Text) + case 1, -1: + return a.Text == b.Text + default: + panic("unreachable") + } +} diff --git a/revlist/revlist2humantest.bash b/revlist/revlist2humantest.bash new file mode 100755 index 0000000..b7d2672 --- /dev/null +++ b/revlist/revlist2humantest.bash @@ -0,0 +1,36 @@ +#!/bin/bash + +# you can run this over a whole repo with: +# +# for file in `find . -type f | sed 's/^\.\///' | egrep -v '^\.git\/.*$'` ; do revlist2humantest.bash $file ; done > /tmp/output +# +# be careful with files with spaces, though + +set -e + +repo=`git remote show origin | grep Fetch | cut -d' ' -f5` +branch=`git branch | egrep '^\* .*' | cut -d' ' -f2` +if [ "$#" -eq 1 ] ; then + commit=`git log | head -1 | cut -d' ' -f2` + path=$1 +elif [ "$#" -eq 2 ] ; then + commit=$1 + path=$2 +else + echo "bad number of parameters" > /dev/stderr + echo > /dev/stderr + echo " try with: [commit] path" > /dev/stderr + exit +fi + +hashes=`git rev-list --remove-empty --reverse $commit -- $path` + +# some remotes have the .git, other don't, +# repoDot makes sure all have +repoDot="${repo%.git}.git" + +echo -e "\t&humanTest{\"${repoDot}\", \"${branch}\", \"${commit}\", \"${path}\", []string{" +for i in $hashes ; do + echo -e "\t\t\"${i}\"," +done +echo -e "\t}}," diff --git a/revlist/revlist_test.go b/revlist/revlist_test.go new file mode 100644 index 0000000..7aea54c --- /dev/null +++ b/revlist/revlist_test.go @@ -0,0 +1,398 @@ +package revlist + +import ( + "bytes" + "fmt" + "os" + "testing" + + "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) } + +type SuiteCommon struct { + repos map[string]*git.Repository +} + +var _ = Suite(&SuiteCommon{}) + +var fixtureRepos = [...]struct { + url string + packfile string +}{ + {"https://github.com/tyba/git-fixture.git", "../formats/packfile/fixtures/git-fixture.ofs-delta"}, + {"https://github.com/jamesob/desk.git", "../formats/packfile/fixtures/jamesob-desk.pack"}, + {"https://github.com/spinnaker/spinnaker.git", "../formats/packfile/fixtures/spinnaker-spinnaker.pack"}, +} + +// create the repositories of the fixtures +func (s *SuiteCommon) SetUpSuite(c *C) { + s.repos = make(map[string]*git.Repository, 0) + for _, fixRepo := range fixtureRepos { + s.repos[fixRepo.url] = git.NewPlainRepository() + + d, err := os.Open(fixRepo.packfile) + defer d.Close() + 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 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{ + "35e85108805c84807bc66a02d91535e1e24b38b9", + }}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", []string{ + "b8e471f58bcbca63b07bda20e428190409c2db47", + }}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "go/example.go", []string{ + "918c48b83bd081e863dbe1b80f8998f058cd8294", + }}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/long.json", []string{ + "af2d6a6954d532f8ffb47615169c8fdf9d383a1a", + }}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/short.json", []string{ + "af2d6a6954d532f8ffb47615169c8fdf9d383a1a", + }}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", []string{ + "b029517f6300c2da0f4b651b8642506cd6aaf45d", + }}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "php/crappy.php", []string{ + "918c48b83bd081e863dbe1b80f8998f058cd8294", + }}, + {"https://github.com/tyba/git-fixture.git", "master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "vendor/foo.go", []string{ + "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + }}, + {"https://github.com/jamesob/desk.git", "master", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "LICENSE", []string{ + "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", + }}, + {"https://github.com/jamesob/desk.git", "master", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "README.md", []string{ + "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", + "2e87a2dcc63a115f9a61bd969d1e85fb132a431b", + "215b0ac06225b0671bc3460d10da88c3406f796f", + "0260eb7a2623dd2309ab439f74e8681fccdc4285", + "d46b48933e94f30992486374fa9a6becfd28ea17", + "9cb4df2a88efee8836f9b8ad27ca2717f624164e", + "8c49acdec2ed441706d8799f8b17878aae4c1ffe", + "ebaca0c6f54c23193ee8175c3530e370cb2dabe3", + "77675f82039551a19de4fbccbe69366fe63680df", + "b9741594fb8ab7374f9be07d6a09a3bf96719816", + "04db6acd94de714ca48128c606b17ee1149a630e", + "ff737bd8a962a714a446d7592fae423a56e61e12", + "eadd03f7a1cc54810bd10eef6747ad9562ad246d", + "b5072ab5c1cf89191d71f1244eecc5d1f369ef7e", + "bfa6ebc9948f1939402b063c0a2a24bf2b1c1cc3", + "d9aef39828c670dfdb172502021a2ebcda8cf2fb", + "1a6b6e45c91e1831494eb139ee3f8e21649c7fb0", + "09fdbe4612066cf63ea46aee43c7cfaaff02ecfb", + "236f6526b1150cc1f1723566b4738f443fc70777", + "7862953f470b62397d22f6782a884f5bea6d760d", + "b0b0152d08c2333680266977a5bc9c4e50e1e968", + "13ce6c1c77c831f381974aa1c62008a414bd2b37", + "d3f3c8faca048d11709969fbfc0cdf2901b87578", + "8777dde1abe18c805d021366643218d3f3356dd9", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/reconfigure_spinnaker.py", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/validate_configuration.py", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", + "1e3d328a2cabda5d0aaddc5dec65271343e0dc37", + "b5d999e2986e190d81767cd3cfeda0260f9f6fb8", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/fetch.py", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/yaml_util.py", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", + "b5d999e2986e190d81767cd3cfeda0260f9f6fb8", + "023d4fb17b76e0fe0764971df8b8538b735a1d67", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "dev/build_release.py", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", + "f42771ba298b93a7c4f5b16c5b30ab96c15305a8", + "dd52703a50e71891f63fcf05df1f69836f4e7056", + "0d9c9cef53af38cefcb6801bb492aaed3f2c9a42", + "d375f1994ff4d0bdc32d614e698f1b50e1093f14", + "abad497f11a366548aa95303c8c2f165fe7ae918", + "6986d885626792dee4ef6b7474dfc9230c5bda54", + "5422a86a10a8c5a1ef6728f5fc8894d9a4c54cb9", + "09a4ea729b25714b6368959eea5113c99938f7b6", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "pkg_scripts/postUninstall.sh", []string{ + "ce9f123d790717599aaeb76bc62510de437761be", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/first_google_boot.sh", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + "de25f576b888569192e6442b0202d30ca7b2d8ec", + "a596972a661d9a7deca8abd18b52ce1a39516e89", + "9467ec579708b3c71dd9e5b3906772841c144a30", + "c4a9091e4076cb740fa46e790dd5b658e19012ad", + "6eb5d9c5225224bfe59c401182a2939d6c27fc00", + "495c7118e7cf757aa04eab410b64bfb5b5149ad2", + "dd2d03c19658ff96d371aef00e75e2e54702da0e", + "2a3b1d3b134e937c7bafdab6cc2950e264bf5dee", + "a57b08a9072f6a865f760551be2a4944f72f804a", + "0777fadf4ca6f458d7071de414f9bd5417911037", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_spinnaker.sh", []string{ + "0d9c9cef53af38cefcb6801bb492aaed3f2c9a42", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_fake_openjdk8.sh", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_spinnaker.py", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + "37f94770d81232b1895fca447878f68d65aac652", + "46c9dcbb55ca3f4735e82ad006e8cae2fdd050d9", + "124a88cfda413cb7182ca9c739a284a9e50042a1", + "eb4faf67a8b775d7985d07a708e3ffeac4273580", + "0d9c9cef53af38cefcb6801bb492aaed3f2c9a42", + "01171a8a2e843bef3a574ba73b258ac29e5d5405", + "739d8c6fe16edcb6ef9185dc74197de561b84315", + "d33c2d1e350b03fb989eefc612e8c9d5fa7cadc2", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/__init__.py", []string{ + "a24001f6938d425d0e7504bdf5d27fc866a85c3d", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "experimental/docker-compose/docker-compose.yml", []string{ + "fda357835d889595dc39dfebc6181d863cce7d4f", + "57c59e7144354a76e1beba69ae2f85db6b1727af", + "7682dff881029c722d893a112a64fea6849a0428", + "66f1c938c380a4096674b27540086656076a597f", + "56dc238f6f397e93f1d1aad702976889c830e8bf", + "b95e442c064935709e789fa02126f17ddceef10b", + "f98965a8f42037bd038b86c3401da7e6dfbf4f2e", + "5344429749e8b68b168d2707b7903692436cc2ea", + "6a31f5d219766b0cec4ea4fbbbfe47bdcdb0ab8e", + "ddaae195b628150233b0a48f50a1674fd9d1a924", + "7119ad9cf7d4e4d8b059e5337374baae4adc7458", + }}, + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/validate_configuration_test.py", []string{ + "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", + "1e3d328a2cabda5d0aaddc5dec65271343e0dc37", + }}, + + // FAILS + /* + // this contains an empty move + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "google/dev/build_google_tarball.py", []string{ + "88e60ac93f832efc2616b3c165e99a8f2ffc3e0c", + "9e49443da49b8c862cc140b660744f84eebcfa51", + }}, + */ + /* + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/yaml_util_test.py", []string{ + "edf909edb9319c5e615e4ce73da47bbdca388ebe", + "023d4fb17b76e0fe0764971df8b8538b735a1d67", + }}, + */ + /* + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/configurator_test.py", []string{ + "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", + "edf909edb9319c5e615e4ce73da47bbdca388ebe", + "d14f793a6cd7169ef708a4fc276ad876bd3edd4e", + "023d4fb17b76e0fe0764971df8b8538b735a1d67", + }}, + */ + /* + // this contains a cherry-pick at 094d0e7d5d691 (with 3f34438d) + {"https://github.com/jamesob/desk.git", "master", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "desk", []string{ + "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", + "a0c1e853158ccbaf95574220bbf3b54509034a9f", + "decfc524570c407d6bba0f217e534c8b47dbdbee", + "1413872d5b3af7cd674bbe0e1f23387cd5d940e6", + "40cd5a91d916e7b2f331e4e85fdc52636fd7cff7", + "8e07d73aa0e3780f8c7cf8ad1a6b263df26a0a52", + "19c56f95720ac3630efe9f29b1a252581d6cbc0c", + "9ea46ccc6d253cffb4b7b66e936987d87de136e4", + "094d0e7d5d69141c98a606910ba64786c5565da0", + "801e62706a9e4fef75fcaca9c78744de0bc36e6a", + "eddf335f31c73624ed3f40dc5fcad50136074b2b", + "c659093f06eb2bd68c6252caeab605e5cd8aa49e", + "d94b3fe8ce0e3a474874d742992d432cd040582f", + "93cddf036df2d8509f910063696acd556ca7600f", + "b3d4cb0c826b16b301f088581d681654d8de6c07", + "52d90f9b513dd3c5330663cba39396e6b8a3ba4e", + "15919e99ded03c6ceea9ff98558e77a322a4dadb", + "803bf37847633e2f685a46a27b11facf22efebec", + "c07ad524ee1e616c70bf2ea7a0ee4f4a01195d78", + "b91aff30f318fda461d009c308490613b394f3e2", + "67cec1e8a3f21c6eb11678e3f31ffd228b55b783", + "bbe404c78af7525fabc57b9e7aa7c100b0d39f7a", + "5dd078848786c2babc2511e9502fa98518cf3535", + "7970ae7cc165c5205945dfb704d67d53031f550a", + "33091ac904747747ff30f107d4d0f22fa872eccf", + "069f81cab12d185ba1b509be946c47897cd4fb1f", + "13ce6c1c77c831f381974aa1c62008a414bd2b37", + }}, + */ + /* + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "InstallSpinnaker.sh", []string{ + "ce9f123d790717599aaeb76bc62510de437761be", + "23673af3ad70b50bba7fdafadc2323302f5ba520", + "b7015a5d36990d69a054482556127b9c7404a24a", + "582da9622e3a72a19cd261a017276d72b5b0051a", + "0c5bb1e4392e751f884f3c57de5d4aee72c40031", + "c9c2a0ec03968ab17e8b16fdec9661eb1dbea173", + "a3cdf880826b4d9af42b93f4a2df29a91ab31d35", + "18526c447f5174d33c96aac6d6433318b0e2021c", + "2a6288be1c8ea160c443ca3cd0fe826ff2387d37", + "9e74d009894d73dd07773ea6b3bdd8323db980f7", + "d2f6214b625db706384b378a29cc4c22237db97a", + "202a9c720b3ba8106e022a0ad027ebe279040c78", + "791bcd1592828d9d5d16e83f3a825fb08b0ba22d", + "01e65d67eed8afcb67a6bdf1c962541f62b299c9", + "6328ee836affafc1b52127147b5ca07300ac78e6", + "3de4f77c105f700f50d9549d32b9a05a01b46c4b", + "8980daf661408a3faa1f22c225702a5c1d11d5c9", + "8eb116de9128c314ac8a6f5310ca500b8c74f5db", + "88e841aad37b71b78a8fb88bc75fe69499d527c7", + "370d61cdbc1f3c90db6759f1599ccbabd40ad6c1", + "505577dc87d300cf562dc4702a05a5615d90d855", + "b5c6053a46993b20d1b91e7b7206bffa54669ad7", + "ba486de7c025457963701114c683dcd4708e1dee", + "b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", + "a47d0aaeda421f06df248ad65bd58230766bf118", + "495c7118e7cf757aa04eab410b64bfb5b5149ad2", + "46670eb6477c353d837dbaba3cf36c5f8b86f037", + "dd2d03c19658ff96d371aef00e75e2e54702da0e", + "4bbcad219ec55a465fb48ce236cb10ca52d43b1f", + "50d0556563599366f29cb286525780004fa5a317", + "9a06d3f20eabb254d0a1e2ff7735ef007ccd595e", + "d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", + }}, + */ + /* + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "config/default-spinnaker-local.yml", []string{ + "ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", + "99534ecc895fe17a1d562bb3049d4168a04d0865", + "caf6d62e8285d4681514dd8027356fb019bc97ff", + "eaf7614cad81e8ab5c813dd4821129d0c04ea449", + "5a2a845bc08974a36d599a4a4b7e25be833823b0", + "41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", + "974b775a8978b120ff710cac93a21c7387b914c9", + "87e459a9a044b3109dfeb943cc82c627b61d84a6", + "5e09821cbd7d710405b61cab0a795c2982a71b9c", + "8cc2d4bdb0a15aafc7fe02cdcb03ab90c974cafa", + "3ce7b902a51bac2f10994f7d1f251b616c975e54", + "a596972a661d9a7deca8abd18b52ce1a39516e89", + "8980daf661408a3faa1f22c225702a5c1d11d5c9", + }}, + */ + /* + {"https://github.com/spinnaker/spinnaker.git", "master", "b32b2aecae2cfca4840dd480f8082da206a538da", "config/spinnaker.yml", []string{ + "ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", + "caf6d62e8285d4681514dd8027356fb019bc97ff", + "eaf7614cad81e8ab5c813dd4821129d0c04ea449", + "5a2a845bc08974a36d599a4a4b7e25be833823b0", + "41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", + "974b775a8978b120ff710cac93a21c7387b914c9", + "ed887f6547d7cd2b2d741184a06f97a0a704152b", + "d4553dac205023fa77652308af1a2d1cf52138fb", + "a596972a661d9a7deca8abd18b52ce1a39516e89", + "66ac94f0b4442707fb6f695fbed91d62b3bd9d4a", + "079e42e7c979541b6fab7343838f7b9fd4a360cd", + }}, + */ +} + +func (s *SuiteCommon) TestRevList(c *C) { + for _, t := range revListTests { + repo, ok := s.repos[t.repo] + c.Assert(ok, Equals, true) + + commit, err := repo.Commit(core.NewHash(t.commit)) + c.Assert(err, IsNil) + + revs, err := New(repo, commit, t.path) + c.Assert(err, IsNil) + + 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())) + 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) { + 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)) + } + } + } + fmt.Printf("OK repo=%s, branch=%s, commit=%s, path=%s\n", + t.repo, t.branch, t.commit, t.path) + } +} + +// same length is assumed +func compareSideBySide(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() +} + +var cherryPicks = [...][]string{ + // repo, path, commit a, commit b + []string{"https://github.com/jamesob/desk.git", "desk", "094d0e7d5d69141c98a606910ba64786c5565da0", "3f34438d54f4a1ca86db8c0f03ed8eb38f20e22c"}, +} + +// should detect cherry picks +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])) + } +} + +// 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 +} @@ -50,7 +50,7 @@ func (t *Tree) walkEntries(base string, ch chan *File) { blob := &Blob{} blob.Decode(obj) - ch <- &File{Name: filepath.Join(base, entry.Name), Reader: blob.Reader()} + ch <- &File{Name: filepath.Join(base, entry.Name), Reader: blob.Reader(), Hash: entry.Hash} } } @@ -119,4 +119,5 @@ func (i *TreeIter) Next() (*Tree, error) { type File struct { Name string io.Reader + Hash core.Hash } |