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

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

Changed files
+46 -13
knotserver
+24 -12
knotserver/git/diff.go
··· 86 return &nd, nil 87 } 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 - 100 tree1, err := commit1.Tree() 101 if err != nil { 102 return nil, err ··· 130 }, nil 131 } 132 133 - func (g *GitRepo) resolveRevision(revStr string) (*object.Commit, error) { 134 rev, err := g.r.ResolveRevision(plumbing.Revision(revStr)) 135 if err != nil { 136 return nil, fmt.Errorf("resolving revision %s: %w", revStr, err)
··· 86 return &nd, nil 87 } 88 89 + func (g *GitRepo) DiffTree(commit1, commit2 *object.Commit) (*types.DiffTree, error) { 90 tree1, err := commit1.Tree() 91 if err != nil { 92 return nil, err ··· 120 }, nil 121 } 122 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) { 146 rev, err := g.r.ResolveRevision(plumbing.Revision(revStr)) 147 if err != nil { 148 return nil, fmt.Errorf("resolving revision %s: %w", revStr, err)
+22 -1
knotserver/routes.go
··· 775 return 776 } 777 778 - difftree, err := gr.DiffTree(rev1, rev2) 779 if err != nil { 780 l.Error("error comparing revisions", "msg", err.Error()) 781 writeError(w, "error comparing revisions", http.StatusBadRequest)
··· 775 return 776 } 777 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) 800 if err != nil { 801 l.Error("error comparing revisions", "msg", err.Error()) 802 writeError(w, "error comparing revisions", http.StatusBadRequest)