From a4ae68783d92a46b8d8bbd7e2aab3700bde10597 Mon Sep 17 00:00:00 2001
From: Jochen Hilgers <jochen.hilgers@antwerpes.de>
Date: Wed, 26 Nov 2014 17:22:15 +0100
Subject: [PATCH] Integrated git_diff_find_similar

---
 diff.go      | 71 +++++++++++++++++++++++++++++++++++++++++++++++++
 diff_test.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 140 insertions(+), 6 deletions(-)

diff --git a/diff.go b/diff.go
index f762a56..661a9a7 100644
--- a/diff.go
+++ b/diff.go
@@ -164,6 +164,29 @@ func (diff *Diff) Free() error {
 	return nil
 }
 
+func (diff *Diff) FindSimilar(opts *DiffFindOptions) error {
+
+	var copts *C.git_diff_find_options
+	if opts != nil {
+		copts = &C.git_diff_find_options{
+			version:                       C.GIT_DIFF_FIND_OPTIONS_VERSION,
+			flags:                         C.uint32_t(opts.Flags),
+			rename_threshold:              C.uint16_t(opts.RenameThreshold),
+			copy_threshold:                C.uint16_t(opts.CopyThreshold),
+			rename_from_rewrite_threshold: C.uint16_t(opts.RenameFromRewriteThreshold),
+			break_rewrite_threshold:       C.uint16_t(opts.BreakRewriteThreshold),
+			rename_limit:                  C.size_t(opts.RenameLimit),
+		}
+	}
+
+	ecode := C.git_diff_find_similar(diff.ptr, copts)
+	if ecode < 0 {
+		return MakeGitError(ecode)
+	}
+
+	return nil
+}
+
 type diffForEachData struct {
 	FileCallback DiffForEachFileCallback
 	HunkCallback DiffForEachHunkCallback
@@ -341,6 +364,54 @@ func DefaultDiffOptions() (DiffOptions, error) {
 	}, nil
 }
 
+type DiffFindOptionsFlag int
+
+const (
+	DiffFindByConfig                    DiffFindOptionsFlag = C.GIT_DIFF_FIND_BY_CONFIG
+	DiffFindRenames                     DiffFindOptionsFlag = C.GIT_DIFF_FIND_RENAMES
+	DiffFindRenamesFromRewrites         DiffFindOptionsFlag = C.GIT_DIFF_FIND_RENAMES_FROM_REWRITES
+	DiffFindCopies                      DiffFindOptionsFlag = C.GIT_DIFF_FIND_COPIES
+	DiffFindCopiesFromUnmodified        DiffFindOptionsFlag = C.GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED
+	DiffFindRewrites                    DiffFindOptionsFlag = C.GIT_DIFF_FIND_REWRITES
+	DiffFindBreakRewrites               DiffFindOptionsFlag = C.GIT_DIFF_BREAK_REWRITES
+	DiffFindAndBreakRewrites            DiffFindOptionsFlag = C.GIT_DIFF_FIND_AND_BREAK_REWRITES
+	DiffFindForUntracked                DiffFindOptionsFlag = C.GIT_DIFF_FIND_FOR_UNTRACKED
+	DiffFindAll                         DiffFindOptionsFlag = C.GIT_DIFF_FIND_ALL
+	DiffFindIgnoreLeadingWhitespace     DiffFindOptionsFlag = C.GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE
+	DiffFindIgnoreWhitespace            DiffFindOptionsFlag = C.GIT_DIFF_FIND_IGNORE_WHITESPACE
+	DiffFindDontIgnoreWhitespace        DiffFindOptionsFlag = C.GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE
+	DiffFindExactMatchOnly              DiffFindOptionsFlag = C.GIT_DIFF_FIND_EXACT_MATCH_ONLY
+	DiffFindBreakRewritesForRenamesOnly DiffFindOptionsFlag = C.GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY
+	DiffFindRemoveUnmodified            DiffFindOptionsFlag = C.GIT_DIFF_FIND_REMOVE_UNMODIFIED
+)
+
+//TODO implement git_diff_similarity_metric
+type DiffFindOptions struct {
+	Flags                      DiffFindOptionsFlag
+	RenameThreshold            uint16
+	CopyThreshold              uint16
+	RenameFromRewriteThreshold uint16
+	BreakRewriteThreshold      uint16
+	RenameLimit                uint
+}
+
+func DefaultDiffFindOptions() (DiffFindOptions, error) {
+	opts := C.git_diff_find_options{}
+	ecode := C.git_diff_find_init_options(&opts, C.GIT_DIFF_FIND_OPTIONS_VERSION)
+	if ecode < 0 {
+		return DiffFindOptions{}, MakeGitError(ecode)
+	}
+
+	return DiffFindOptions{
+		Flags:                      DiffFindOptionsFlag(opts.flags),
+		RenameThreshold:            uint16(opts.rename_threshold),
+		CopyThreshold:              uint16(opts.copy_threshold),
+		RenameFromRewriteThreshold: uint16(opts.rename_from_rewrite_threshold),
+		BreakRewriteThreshold:      uint16(opts.break_rewrite_threshold),
+		RenameLimit:                uint(opts.rename_limit),
+	}, nil
+}
+
 var (
 	ErrDeltaSkip = errors.New("Skip delta")
 )
diff --git a/diff_test.go b/diff_test.go
index b688294..84d72db 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -6,20 +6,68 @@ import (
 	"testing"
 )
 
-func TestDiffTreeToTree(t *testing.T) {
+func TestFindSimilar(t *testing.T) {
 	repo := createTestRepo(t)
 	defer repo.Free()
 	defer os.RemoveAll(repo.Workdir())
 
-	_, originalTreeId := seedTestRepo(t, repo)
-	originalTree, err := repo.LookupTree(originalTreeId)
+	originalTree, newTree := createTestTrees(t, repo)
 
+	diffOpt, _ := DefaultDiffOptions()
+
+	diff, err := repo.DiffTreeToTree(originalTree, newTree, &diffOpt)
+	checkFatal(t, err)
+	if diff == nil {
+		t.Fatal("no diff returned")
+	}
+
+	findOpts, err := DefaultDiffFindOptions()
+	checkFatal(t, err)
+	findOpts.Flags = DiffFindBreakRewrites
+
+	err = diff.FindSimilar(&findOpts)
 	checkFatal(t, err)
 
-	_, newTreeId := updateReadme(t, repo, "file changed\n")
+	numDiffs := 0
+	numAdded := 0
+	numDeleted := 0
 
-	newTree, err := repo.LookupTree(newTreeId)
-	checkFatal(t, err)
+	err = diff.ForEach(func(file DiffDelta, progress float64) (DiffForEachHunkCallback, error) {
+		numDiffs++
+
+		switch file.Status {
+		case DeltaAdded:
+			numAdded++
+		case DeltaDeleted:
+			numDeleted++
+		}
+
+		return func(hunk DiffHunk) (DiffForEachLineCallback, error) {
+			return func(line DiffLine) error {
+				return nil
+			}, nil
+		}, nil
+	}, DiffDetailLines)
+
+	if numDiffs != 2 {
+		t.Fatal("Incorrect number of files in diff")
+	}
+	if numAdded != 1 {
+		t.Fatal("Incorrect number of new files in diff")
+	}
+	if numDeleted != 1 {
+		t.Fatal("Incorrect number of deleted files in diff")
+	}
+
+}
+
+func TestDiffTreeToTree(t *testing.T) {
+
+	repo := createTestRepo(t)
+	defer repo.Free()
+	defer os.RemoveAll(repo.Workdir())
+
+	originalTree, newTree := createTestTrees(t, repo)
 
 	callbackInvoked := false
 	opts := DiffOptions{
@@ -94,3 +142,18 @@ func TestDiffTreeToTree(t *testing.T) {
 	}
 
 }
+
+func createTestTrees(t *testing.T, repo *Repository) (originalTree *Tree, newTree *Tree) {
+	var err error
+	_, originalTreeId := seedTestRepo(t, repo)
+	originalTree, err = repo.LookupTree(originalTreeId)
+
+	checkFatal(t, err)
+
+	_, newTreeId := updateReadme(t, repo, "file changed\n")
+
+	newTree, err = repo.LookupTree(newTreeId)
+	checkFatal(t, err)
+
+	return originalTree, newTree
+}