v0.25.1 7fabed9

githubraw.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3// Package githubraw downloads a reusable workflow file from
4// raw.githubusercontent.com at a commit (ports.WorkflowFetcher). It sends no
5// credentials, bounds the time and the size of the download, and follows no
6// redirect to another host.
7package githubraw
8
9import (
10	"context"
11	"fmt"
12	"io"
13	"net/http"
14	"net/url"
15	"strings"
16	"time"
17
18	"bnema.dev/ermite/internal/domain"
19	"bnema.dev/ermite/internal/ports"
20)
21
22// DefaultBaseURL is where the files are.
23const DefaultBaseURL = "https://raw.githubusercontent.com"
24
25// DefaultTimeout bounds one download.
26const DefaultTimeout = 20 * time.Second
27
28// Config configures a Fetcher; tests replace BaseURL and Client.
29type Config struct {
30	BaseURL string
31	Timeout time.Duration
32	Client  *http.Client
33}
34
35// Fetcher implements ports.WorkflowFetcher over HTTPS.
36type Fetcher struct {
37	base   string
38	client *http.Client
39}
40
41var _ ports.WorkflowFetcher = (*Fetcher)(nil)
42
43// New builds a Fetcher.
44func New(cfg Config) *Fetcher {
45	if cfg.BaseURL == "" {
46		cfg.BaseURL = DefaultBaseURL
47	}
48	if cfg.Timeout <= 0 {
49		cfg.Timeout = DefaultTimeout
50	}
51	c := cfg.Client
52	if c == nil {
53		c = &http.Client{Transport: http.DefaultTransport}
54	}
55	cc := *c
56	cc.Timeout = cfg.Timeout
57	cc.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }
58	return &Fetcher{base: strings.TrimRight(cfg.BaseURL, "/"), client: &cc}
59}
60
61// FetchWorkflow downloads path of owner/repo at sha.
62func (f *Fetcher) FetchWorkflow(ctx context.Context, owner, repo, sha, path string, maxBytes int64) ([]byte, error) {
63	segs := []string{owner, repo, sha}
64	segs = append(segs, strings.Split(path, "/")...)
65	for _, s := range segs {
66		if s == "" || s == "." || s == ".." {
67			return nil, fmt.Errorf("%w: bad workflow location", domain.ErrInvalid)
68		}
69	}
70	esc := make([]string, len(segs))
71	for i, s := range segs {
72		esc[i] = url.PathEscape(s)
73	}
74	req, err := http.NewRequestWithContext(ctx, http.MethodGet, f.base+"/"+strings.Join(esc, "/"), nil)
75	if err != nil {
76		return nil, err
77	}
78	res, err := f.client.Do(req)
79	if err != nil {
80		if ctx.Err() != nil {
81			return nil, ctx.Err()
82		}
83		return nil, fmt.Errorf("%w: download the reusable workflow: %w", domain.ErrUnavailable, err)
84	}
85	defer func() { _ = res.Body.Close() }()
86	switch res.StatusCode {
87	case http.StatusOK:
88	case http.StatusNotFound, http.StatusUnauthorized, http.StatusForbidden:
89		return nil, domain.ErrNotFound
90	default:
91		return nil, fmt.Errorf("%w: the download answered %d", domain.ErrUnavailable, res.StatusCode)
92	}
93	data, err := io.ReadAll(io.LimitReader(res.Body, maxBytes+1))
94	if err != nil {
95		return nil, fmt.Errorf("%w: download the reusable workflow: %w", domain.ErrUnavailable, err)
96	}
97	if int64(len(data)) > maxBytes {
98		return nil, fmt.Errorf("%w: the reusable workflow is larger than %d bytes", domain.ErrResourceExhausted, maxBytes)
99	}
100	return data, nil
101}

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 2

Tags 45

↑↓ moveenter openesc close