Repository: libgit2/git2go
Pull request #826 (2021-09-06 03:04:29)

Add support for Repository.ReachableFromAny()
This change exposes the binding for `git_graph_reachable_from_any()`.


Patch:
diff --git a/graph.go b/graph.go
index 688818c..05da423 100644
--- a/graph.go
+++ b/graph.go
@@ -40,3 +40,30 @@ func (repo *Repository) AheadBehind(local, upstream *Oid) (ahead, behind int, er
 
 	return int(aheadT), int(behindT), nil
 }
+
+// ReachableFromAny returns whether a commit is reachable from any of a list of
+// commits by following parent edges.
+func (repo *Repository) ReachableFromAny(commit *Oid, descendants []*Oid) (bool, error) {
+	if len(descendants) == 0 {
+		return false, nil
+	}
+
+	coids := make([]C.git_oid, len(descendants))
+	for i := 0; i < len(descendants); i++ {
+		coids[i] = *descendants[i].toC()
+	}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ret := C.git_graph_reachable_from_any(repo.ptr, commit.toC(), &coids[0], C.size_t(len(descendants)))
+	runtime.KeepAlive(repo)
+	runtime.KeepAlive(commit)
+	runtime.KeepAlive(coids)
+	runtime.KeepAlive(descendants)
+	if ret < 0 {
+		return false, MakeGitError(ret)
+	}
+
+	return (ret > 0), nil
+}


Test patch:
diff --git a/graph_test.go b/graph_test.go
new file mode 100644
index 0000000..da19f35
--- /dev/null
+++ b/graph_test.go
@@ -0,0 +1,75 @@
+package git
+
+import (
+	"testing"
+)
+
+func TestReachableFromAny(t *testing.T) {
+	repo, err := OpenRepository("testdata/TestGitRepository.git")
+	checkFatal(t, err)
+	defer repo.Free()
+
+	for name, tc := range map[string]struct {
+		reachable   bool
+		commit      string
+		descendants []string
+	}{
+		"empty": {
+			reachable: false,
+			commit:    "49322bb17d3acc9146f98c97d078513228bbf3c0",
+		},
+		"same": {
+			reachable:   true,
+			commit:      "49322bb17d3acc9146f98c97d078513228bbf3c0",
+			descendants: []string{"49322bb17d3acc9146f98c97d078513228bbf3c0"},
+		},
+		"unreachable": {
+			reachable:   false,
+			commit:      "ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
+			descendants: []string{"58be4659bb571194ed4562d04b359d26216f526e"},
+		},
+		"unreachable-reverse": {
+			reachable:   false,
+			commit:      "58be4659bb571194ed4562d04b359d26216f526e",
+			descendants: []string{"ac7e7e44c1885efb472ad54a78327d66bfc4ecef"},
+		},
+		"root": {
+			reachable: false,
+			commit:    "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1",
+			descendants: []string{
+				"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
+				"d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",
+				"f73b95671f326616d66b2afb3bdfcdbbce110b44",
+				"d0114ab8ac326bab30e3a657a0397578c5a1af88",
+			},
+		},
+		"head": {
+			reachable: false,
+			commit:    "49322bb17d3acc9146f98c97d078513228bbf3c0",
+			descendants: []string{
+				"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
+				"d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",
+				"f73b95671f326616d66b2afb3bdfcdbbce110b44",
+				"d0114ab8ac326bab30e3a657a0397578c5a1af88",
+			},
+		},
+	} {
+		tc := tc
+		t.Run(name, func(t *testing.T) {
+			commit, err := NewOid(tc.commit)
+			checkFatal(t, err)
+
+			descendants := make([]*Oid, len(tc.descendants))
+			for i, o := range tc.descendants {
+				descendants[i], err = NewOid(o)
+				checkFatal(t, err)
+			}
+			reachable, err := repo.ReachableFromAny(commit, descendants)
+			checkFatal(t, err)
+
+			if reachable != tc.reachable {
+				t.Errorf("ReachableFromAny(%s, %v) = %v, wanted %v", tc.commit, tc.descendants, reachable, tc.reachable)
+			}
+		})
+	}
+}