v0.36.0 1fb2421

changed.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package gitcli
4
5import (
6	"bufio"
7	"context"
8	"fmt"
9
10	"bnema.dev/ermite/internal/domain"
11	"bnema.dev/ermite/internal/ports"
12)
13
14var _ ports.CIGit = (*Git)(nil)
15
16// emptyTree is the id of the empty tree of a SHA-1 repository, what a diff
17// from "nothing" starts at.
18const emptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
19
20// ChangedPaths runs `git diff-tree -r --name-only -z` from base to head, with
21// renames not detected (a rename lists both paths, as GitHub's push filters
22// see it), and stops reading after max paths: truncated tells there were more.
23// A zero base diffs against the empty tree.
24func (g *Git) ChangedPaths(ctx context.Context, repo domain.RepoName, base, head domain.ObjectID, limit int) ([]string, bool, error) {
25	if limit < 1 || head.IsZero() {
26		return nil, false, fmt.Errorf("changed paths: %w: limit %d", domain.ErrInvalid, limit)
27	}
28	dir, err := g.repoDir(repo)
29	if err != nil {
30		return nil, false, err
31	}
32	from := emptyTree
33	if !base.IsZero() {
34		from = base.String()
35	}
36	args := readArgs(dir, "diff-tree", "-r", "--name-only", "-z", "--no-renames", "--no-commit-id", "--end-of-options", from, head.String())
37	var (
38		paths     []string
39		truncated bool
40	)
41	err = g.pipe(ctx, args, func(r *bufio.Reader) error {
42		for {
43			name, err := r.ReadString(0)
44			if err != nil {
45				return nil // the end of the output; a failing exit status is reported by pipe
46			}
47			if len(paths) == limit {
48				truncated = true
49				return errStop
50			}
51			paths = append(paths, name[:len(name)-1])
52		}
53	})
54	if err != nil {
55		return nil, false, fmt.Errorf("changed paths %s..%s in %q: %w", from, head, repo, err)
56	}
57	return paths, truncated, nil
58}
59
60// CommitsOffBranches lists the commits reachable from head and from no branch
61// (refs/heads/*), newest first, at most limit. It runs
62// `git log --not --branches --not <head>`: tags do not count, so the commit of
63// another tag that no branch has is listed.
64func (g *Git) CommitsOffBranches(ctx context.Context, repo domain.RepoName, head domain.ObjectID, limit int) ([]domain.Commit, bool, error) {
65	if head.IsZero() {
66		return nil, false, fmt.Errorf("commits off branches: %w: no head", domain.ErrInvalid)
67	}
68	// "--not --branches" excludes every branch, the second "--not" turns the
69	// negation off again for the head that follows.
70	return g.logCommits(ctx, repo, head, limit, []string{"--not", "--branches", "--not"}, nil)
71}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 45

↑↓ moveenter openesc close