Monorepo for Tangled tangled.org

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).

authored by oppi.li and committed by Tangled 70a9bc2d 6c27a747

Changed files
+46 -13
knotserver
+24 -12
knotserver/git/diff.go
··· 86 86 return &nd, nil 87 87 } 88 88 89 - func (g *GitRepo) DiffTree(rev1, rev2 string) (*types.DiffTree, error) { 90 - commit1, err := g.resolveRevision(rev1) 91 - if err != nil { 92 - return nil, fmt.Errorf("Invalid revision: %s", rev1) 93 - } 94 - 95 - commit2, err := g.resolveRevision(rev2) 96 - if err != nil { 97 - return nil, fmt.Errorf("Invalid revision: %s", rev2) 98 - } 99 - 89 + func (g *GitRepo) DiffTree(commit1, commit2 *object.Commit) (*types.DiffTree, error) { 100 90 tree1, err := commit1.Tree() 101 91 if err != nil { 102 92 return nil, err ··· 130 120 }, nil 131 121 } 132 122 133 - func (g *GitRepo) resolveRevision(revStr string) (*object.Commit, error) { 123 + func (g *GitRepo) MergeBase(commit1, commit2 *object.Commit) (*object.Commit, error) { 124 + isAncestor, err := commit1.IsAncestor(commit2) 125 + if err != nil { 126 + return nil, err 127 + } 128 + 129 + if isAncestor { 130 + return commit1, nil 131 + } 132 + 133 + mergeBase, err := commit1.MergeBase(commit2) 134 + if err != nil { 135 + return nil, err 136 + } 137 + 138 + if len(mergeBase) == 0 { 139 + return nil, fmt.Errorf("failed to find a merge-base") 140 + } 141 + 142 + return mergeBase[0], nil 143 + } 144 + 145 + func (g *GitRepo) ResolveRevision(revStr string) (*object.Commit, error) { 134 146 rev, err := g.r.ResolveRevision(plumbing.Revision(revStr)) 135 147 if err != nil { 136 148 return nil, fmt.Errorf("resolving revision %s: %w", revStr, err)
+22 -1
knotserver/routes.go
··· 775 775 return 776 776 } 777 777 778 - difftree, err := gr.DiffTree(rev1, rev2) 778 + commit1, err := gr.ResolveRevision(rev1) 779 + if err != nil { 780 + l.Error("error resolving revision 1", "msg", err.Error()) 781 + writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest) 782 + return 783 + } 784 + 785 + commit2, err := gr.ResolveRevision(rev2) 786 + if err != nil { 787 + l.Error("error resolving revision 2", "msg", err.Error()) 788 + writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest) 789 + return 790 + } 791 + 792 + mergeBase, err := gr.MergeBase(commit1, commit2) 793 + if err != nil { 794 + l.Error("failed to find merge-base", "msg", err.Error()) 795 + writeError(w, "failed to calculate diff", http.StatusBadRequest) 796 + return 797 + } 798 + 799 + difftree, err := gr.DiffTree(mergeBase, commit2) 779 800 if err != nil { 780 801 l.Error("error comparing revisions", "msg", err.Error()) 781 802 writeError(w, "error comparing revisions", http.StatusBadRequest)