func patch(c *object.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, err := file.Contents() if err != nil { return nil, err } // 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 } file, err = parent.File(path) if err != nil { contentParent = "" } else { contentParent, err = file.Contents() if err != nil { return nil, err } } // compare the contents of parent and child return diff.Do(content, contentParent), nil }
// iterateCommitTrees iterate all reachable trees from the given commit func iterateCommitTrees( s storer.EncodedObjectStorer, commit *object.Commit, cb func(h plumbing.Hash) error) error { tree, err := commit.Tree() if err != nil { return err } if err := cb(tree.Hash); err != nil { return err } treeWalker := object.NewTreeWalker(tree, true) for { _, e, err := treeWalker.Next() if err == io.EOF { break } if err != nil { return err } if err := cb(e.Hash); err != nil { return err } } return nil }
// blobHash returns the hash of a path in a commit func blobHash(path string, commit *object.Commit) (hash plumbing.Hash, found bool) { file, err := commit.File(path) if err != nil { var empty plumbing.Hash return empty, found } return file.Hash, true }
//export c_Commit_Decode func c_Commit_Decode(o uint64) (uint64, int, *C.char) { commit := object.Commit{} obj, ok := GetObject(Handle(o)) if !ok { return IH, ErrorCodeNotFound, C.CString(MessageNotFound) } cobj := obj.(*plumbing.EncodedObject) err := commit.Decode(*cobj) if err != nil { return IH, ErrorCodeInternal, C.CString(err.Error()) } return uint64(RegisterObject(&commit)), ErrorCodeSuccess, nil }
// TODO: benchmark this making git.object.Commit.parent public instead of using // an iterator func parentsContainingPath(path string, c *object.Commit) []*object.Commit { var result []*object.Commit iter := c.Parents() for { parent, err := iter.Next() if err != nil { if err == io.EOF { return result } panic("unreachable") } if _, err := parent.File(path); err == nil { result = append(result, parent) } } }
// Recursive traversal of the commit graph, generating a linear history of the // path. func walkGraph(result *[]*object.Commit, seen *map[plumbing.Hash]struct{}, current *object.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 _, 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) { // 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: *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 { *result = append(*result, current) } // in any case, walk the parent return walkGraph(result, seen, 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, p, path) if err != nil { return err } } } return nil }
// Equivalent commits are commits whose patch is the same. func equivalent(path string, a, b *object.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, nil } diffsA, err := patch(a, path) if err != nil { return false, err } diffsB, err := patch(b, path) if err != nil { return false, err } return sameDiffs(diffsA, diffsB), nil }