forked from tangled.org/core
this repo has no description

knotserver: use common ancestor to calculate diffs

the existing diff calculation approach directly compares branches, this forces linear history for merge. this patch updates the diff logic to use the common ancestor commit to calculate diffs.

```
P Q
o---o topic
/
/
o---o---o---o master
A X Y
```

when diffing topic and master directly with difftree (Q and Y), the commits introduced in master since the creation of the branch, namely, X and Y, are considered "removed" from the generated patch. however, when diffing with the common ancestor A, only changes introduced by P and Q are included in the produced patch.

this calculation can be retired in favor of format-patch soon enough (i think).

Changed files
+46 -13
knotserver
+24 -12
knotserver/git/diff.go
···
return &nd, nil
}
-
func (g *GitRepo) DiffTree(rev1, rev2 string) (*types.DiffTree, error) {
-
commit1, err := g.resolveRevision(rev1)
-
if err != nil {
-
return nil, fmt.Errorf("Invalid revision: %s", rev1)
-
}
-
-
commit2, err := g.resolveRevision(rev2)
-
if err != nil {
-
return nil, fmt.Errorf("Invalid revision: %s", rev2)
-
}
-
+
func (g *GitRepo) DiffTree(commit1, commit2 *object.Commit) (*types.DiffTree, error) {
tree1, err := commit1.Tree()
if err != nil {
return nil, err
···
}, nil
}
-
func (g *GitRepo) resolveRevision(revStr string) (*object.Commit, error) {
+
func (g *GitRepo) MergeBase(commit1, commit2 *object.Commit) (*object.Commit, error) {
+
isAncestor, err := commit1.IsAncestor(commit2)
+
if err != nil {
+
return nil, err
+
}
+
+
if isAncestor {
+
return commit1, nil
+
}
+
+
mergeBase, err := commit1.MergeBase(commit2)
+
if err != nil {
+
return nil, err
+
}
+
+
if len(mergeBase) == 0 {
+
return nil, fmt.Errorf("failed to find a merge-base")
+
}
+
+
return mergeBase[0], nil
+
}
+
+
func (g *GitRepo) ResolveRevision(revStr string) (*object.Commit, error) {
rev, err := g.r.ResolveRevision(plumbing.Revision(revStr))
if err != nil {
return nil, fmt.Errorf("resolving revision %s: %w", revStr, err)
+22 -1
knotserver/routes.go
···
return
}
-
difftree, err := gr.DiffTree(rev1, rev2)
+
commit1, err := gr.ResolveRevision(rev1)
+
if err != nil {
+
l.Error("error resolving revision 1", "msg", err.Error())
+
writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest)
+
return
+
}
+
+
commit2, err := gr.ResolveRevision(rev2)
+
if err != nil {
+
l.Error("error resolving revision 2", "msg", err.Error())
+
writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest)
+
return
+
}
+
+
mergeBase, err := gr.MergeBase(commit1, commit2)
+
if err != nil {
+
l.Error("failed to find merge-base", "msg", err.Error())
+
writeError(w, "failed to calculate diff", http.StatusBadRequest)
+
return
+
}
+
+
difftree, err := gr.DiffTree(mergeBase, commit2)
if err != nil {
l.Error("error comparing revisions", "msg", err.Error())
writeError(w, "error comparing revisions", http.StatusBadRequest)