diff --git a/flake.nix b/flake.nix index c5b7c080..43ce9813 100644 --- a/flake.nix +++ b/flake.nix @@ -49,7 +49,7 @@ inherit (gitignore.lib) gitignoreSource; in { overlays.default = final: prev: let - goModHash = "sha256-zcfTNo7QsiihzLa4qHEX8uGGtbcmBn8TlSm0YHBRNw8="; + goModHash = "sha256-TwlPge7vhVGmtNvYkHFFnZjJs2DWPUwPhCSBTCUYCtc="; buildCmdPackage = name: final.buildGoModule { pname = name; diff --git a/go.mod b/go.mod index 489528f5..9a0bf456 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/gliderlabs/ssh v0.3.8 github.com/go-chi/chi/v5 v5.2.0 - github.com/go-git/go-git/v5 v5.14.0 + github.com/go-git/go-git/v5 v5.0.0-00010101000000-000000000000 github.com/google/uuid v1.6.0 github.com/gorilla/sessions v1.4.0 github.com/haileyok/atproto-oauth-golang v0.0.2 @@ -124,5 +124,7 @@ replace github.com/sergi/go-diff => github.com/sergi/go-diff v1.1.0 replace github.com/go-git/go-git/v5 => /home/op/code/go-git +replace github.com/bluekeyes/go-gitdiff => /home/op/code/go-gitdiff + // from bluesky-social/indigo replace github.com/gocql/gocql => github.com/scylladb/gocql v1.14.4 diff --git a/knotserver/git/diff.go b/knotserver/git/diff.go index dffa54c0..597c73fb 100644 --- a/knotserver/git/diff.go +++ b/knotserver/git/diff.go @@ -6,6 +6,7 @@ import ( "log" "os" "os/exec" + "slices" "strings" "github.com/bluekeyes/go-gitdiff/gitdiff" @@ -126,16 +127,19 @@ func (g *GitRepo) DiffTree(commit1, commit2 *object.Commit) (*types.DiffTree, er // FormatPatch generates a git-format-patch output between two commits, // and returns the raw format-patch series, a parsed FormatPatch and an error. -func (g *GitRepo) FormatPatch(base, commit2 *object.Commit) (string, []patchutil.FormatPatch, error) { +func (g *GitRepo) formatSinglePatch(base, commit2 plumbing.Hash, extraArgs ...string) (string, *patchutil.FormatPatch, error) { var stdout bytes.Buffer - cmd := exec.Command( - "git", + + args := []string{ "-C", g.path, "format-patch", - fmt.Sprintf("%s..%s", base.Hash.String(), commit2.Hash.String()), + fmt.Sprintf("%s..%s", base.String(), commit2.String()), "--stdout", - ) + } + args = append(args, extraArgs...) + + cmd := exec.Command("git", args...) cmd.Stdout = &stdout cmd.Stderr = os.Stderr err := cmd.Run() @@ -148,7 +152,11 @@ func (g *GitRepo) FormatPatch(base, commit2 *object.Commit) (string, []patchutil return "", nil, err } - return stdout.String(), formatPatch, nil + if len(formatPatch) > 1 { + return "", nil, fmt.Errorf("running format-patch on single commit produced more than on patch") + } + + return stdout.String(), &formatPatch[0], nil } func (g *GitRepo) MergeBase(commit1, commit2 *object.Commit) (*object.Commit, error) { @@ -187,3 +195,74 @@ func (g *GitRepo) ResolveRevision(revStr string) (*object.Commit, error) { return commit, nil } + +func (g *GitRepo) commitsBetween(newCommit, oldCommit *object.Commit) ([]*object.Commit, error) { + var commits []*object.Commit + current := newCommit + + for { + if current.Hash == oldCommit.Hash { + break + } + + commits = append(commits, current) + + if len(current.ParentHashes) == 0 { + return nil, fmt.Errorf("old commit %s not found in history of new commit %s", oldCommit.Hash, newCommit.Hash) + } + + parent, err := current.Parents().Next() + if err != nil { + return nil, fmt.Errorf("error getting parent: %w", err) + } + + current = parent + } + + return commits, nil +} + +func (g *GitRepo) FormatPatch(base, commit2 *object.Commit) (string, []patchutil.FormatPatch, error) { + // get list of commits between commir2 and base + commits, err := g.commitsBetween(commit2, base) + if err != nil { + return "", nil, fmt.Errorf("failed to get commits: %w", err) + } + + // reverse the list so we start from the oldest one and go up to the most recent one + slices.Reverse(commits) + + var allPatchesContent strings.Builder + var allPatches []patchutil.FormatPatch + + for _, commit := range commits { + changeId := "" + if val, ok := commit.ExtraHeaders["change-id"]; ok { + changeId = string(val) + } + + var parentHash plumbing.Hash + if len(commit.ParentHashes) > 0 { + parentHash = commit.ParentHashes[0] + } else { + parentHash = plumbing.NewHash("4b825dc642cb6eb9a060e54bf8d69288fbee4904") // git empty tree hash + } + + var additionalArgs []string + if changeId != "" { + additionalArgs = append(additionalArgs, "--add-header", fmt.Sprintf("Change-Id: %s", changeId)) + } + + stdout, patch, err := g.formatSinglePatch(parentHash, commit.Hash, additionalArgs...) + if err != nil { + return "", nil, fmt.Errorf("failed to format patch for commit %s: %w", commit.Hash.String(), err) + } + + allPatchesContent.WriteString(stdout) + allPatchesContent.WriteString("\n") + + allPatches = append(allPatches, *patch) + } + + return allPatchesContent.String(), allPatches, nil +} diff --git a/knotserver/routes.go b/knotserver/routes.go index 8664a6bf..36b7f8bb 100644 --- a/knotserver/routes.go +++ b/knotserver/routes.go @@ -1006,6 +1006,7 @@ func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) { Rev1: commit1.Hash.String(), Rev2: commit2.Hash.String(), FormatPatch: formatPatch, + MergeBase: mergeBase.Hash.String(), Patch: rawPatch, }) return diff --git a/types/repo.go b/types/repo.go index ad0ebfd5..ff84370f 100644 --- a/types/repo.go +++ b/types/repo.go @@ -37,6 +37,7 @@ type RepoFormatPatchResponse struct { Rev1 string `json:"rev1,omitempty"` Rev2 string `json:"rev2,omitempty"` FormatPatch []patchutil.FormatPatch `json:"format_patch,omitempty"` + MergeBase string `json:"merge_base,omitempty"` Patch string `json:"patch,omitempty"` }