Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Phase 7 slice 1: review on the web, so approving does not start with a clone

78912785b00227cffe3f8fb26b057cd60159a6b4 · Keenan Nemetz · 18d ago · parent 9bea7f11

Phase 7 slice 1: review on the web, so approving does not start with a clone

Phase 7 is re-scoped: signing is deferred (the SSH handshake already establishes
who pushed, and there is no third-party verifier here), and the phase is now about
the experience of using the forge.

- GET /r/<repo>/commit/<sha>: a commit's metadata and the patch it introduced,
  per-file hunks with line numbers; the commit list links to it
- change requests show the same diff, computed three-dot: a review is what the
  branch adds since it diverged, not every commit main gained meanwhile
- diffs are capped at 512 KiB and labelled when cut, binary files are named
  rather than faked, and every line is escaped (a <script> line is a test)
- the repository README renders on its home page and markdown blobs render as
  markdown, through the sanitiser issues already use

internal/gitx/gitx.go +38

@@ -660,6 +660,44 @@ func (r *Repo) TreeBlobs(treeish string) (map[string]Blob, error) {
660 660 return blobs, nil
661 661 }
662 662
663 // Diff returns the unified diff git prints between two revisions, capped at
664 // maxBytes. A range may be written with git's own syntax ("main...feature"), so
665 // the caller decides between two-dot and merge-base semantics.
666 func (r *Repo) Diff(rangeOrFrom, to string, maxBytes int) (string, bool, error) {
667 args := []string{"diff", "--no-color", "--no-ext-diff", "--find-renames", "--unified=3"}
668 if to == "" {
669 args = append(args, rangeOrFrom)
670 } else {
671 args = append(args, rangeOrFrom, to)
672 }
673 out, errs, err := r.runHead(maxBytes, args...)
674 if err != nil {
675 return "", false, fmt.Errorf("diff: %w: %s", err, errs)
676 }
677 return string(out), len(out) == maxBytes, nil
678 }
679
680 // ShowPatch is the patch of one commit against its parent (or the empty tree for
681 // a root commit). `git show` gets that case right, which is why it is used here
682 // instead of assembling a range by hand.
683 func (r *Repo) ShowPatch(rev string, maxBytes int) (string, bool, error) {
684 out, errs, err := r.runHead(maxBytes,
685 "show", "--format=", "--patch", "--no-color", "--no-ext-diff", "--find-renames", "--unified=3", rev)
686 if err != nil {
687 return "", false, fmt.Errorf("show %s: %w: %s", rev, err, errs)
688 }
689 return string(out), len(out) == maxBytes, nil
690 }
691
692 // CommitBody is a commit's full message, for the commit page.
693 func (r *Repo) CommitBody(rev string) (string, error) {
694 out, errs, err := r.run(nil, "show", "-s", "--format=%B", rev)
695 if err != nil {
696 return "", fmt.Errorf("show -s %s: %w: %s", rev, err, errs)
697 }
698 return strings.TrimSpace(string(out)), nil
699 }
700
663 701 // AllBlobs lists every blob reachable from any ref, with its size. It is the
664 702 // mark phase of garbage collection, so it walks history rather than tips: an
665 703 // old commit still serves its files.