coverage~bigbes/sr-ht-spec64cae3afgitx/errors.go

Coverage
16.7% 1/6 statements
Δ
+0.0
Blob
25a0dd7
Uncovered L111-L121
1 package gitx
2
3 import (
4 "errors"
5 "fmt"
6
7 "github.com/go-git/go-git/v5/plumbing"
8 )
9
10 // Sentinel errors, one per failure class. Callers compare with errors.Is and
11 // map the class to a status code: ErrNotFound to 404, ErrStale to 409,
12 // ErrRefRejected to a hook rejection message, ErrTooLarge to 413.
13 //
14 // core's sentinels (ErrInvalidName, ErrInvalidPath, ErrInvalidDocID,
15 // ErrMalformedFrontmatter, ...) are reused verbatim wherever the failure is a
16 // domain-rule violation rather than a git one; this package adds only the
17 // classes core cannot know about.
18 var (
19 // ErrNotFound marks a missing space, revision, ref, path or object.
20 // Invalid names resolve here too, so a crafted name cannot distinguish
21 // "malformed" from "absent" by probing.
22 ErrNotFound = errors.New("not found")
23
24 // ErrExists marks a create that would clobber something: a space whose
25 // directory is already present, or a proposal branch already in use.
26 ErrExists = errors.New("already exists")
27
28 // ErrBadRev marks a revision string that is not a usable ref name or hex
29 // object id. It is distinct from ErrNotFound: this is "cannot be a
30 // revision", not "is not in this repository".
31 ErrBadRev = errors.New("invalid revision")
32
33 // ErrTooLarge marks a blob or a tree walk that exceeded its byte or entry
34 // budget. Nothing is truncated: a half-read markdown document would be
35 // indexed and served as if it were whole, so the read fails instead.
36 ErrTooLarge = errors.New("too large")
37
38 // ErrStale marks a merge whose base moved under it. Match with errors.Is
39 // and type-assert to *StaleError for the current approved head, which is
40 // what the caller returns in the 409.
41 ErrStale = errors.New("stale base")
42
43 // ErrRefRejected marks a ref update the refs rule forbids. This is what the
44 // update hook reports back to the pushing client.
45 ErrRefRejected = errors.New("ref update rejected")
46
47 // ErrRefRace marks a compare-and-swap ref update that lost to a concurrent
48 // writer (in practice a native receive-pack push) more times than the retry
49 // budget allows. The operation had no effect.
50 ErrRefRace = errors.New("ref changed concurrently")
51
52 // ErrUnsupportedEntry marks a tree entry the document model has no meaning
53 // for: a submodule, or a symlink occupying a document path. Skipping it
54 // silently would drop a document out of the index with no trace, which is
55 // the exact failure the service exists to prevent.
56 ErrUnsupportedEntry = errors.New("unsupported tree entry")
57
58 // ErrDuplicateDocID marks two documents carrying the same id in one tree.
59 // Global uniqueness is the registry's job, but a tree that already violates
60 // it makes the id-keyed merge ambiguous, so it is refused here too.
61 ErrDuplicateDocID = errors.New("duplicate document id")
62
63 // ErrUnsupportedChange marks a proposal carrying a change the merge model
64 // cannot express: a deletion, a rename, or an edit to a non-document path.
65 // Deletion and rename are human-push-only by design.
66 ErrUnsupportedChange = errors.New("unsupported proposal change")
67 )
68
69 // StaleReason names which of the staleness cases fired. It is carried on
70 // StaleError so the caller can explain the 409 rather than just returning it.
71 type StaleReason string
72
73 const (
74 // StaleBaseDetached means the proposal's base is no longer an ancestor of
75 // the approved head — the approved branch was rewritten under it.
76 StaleBaseDetached StaleReason = "base is not an ancestor of the approved head"
77
78 // StaleDocChanged means the document changed on the approved branch since
79 // the proposal's base.
80 StaleDocChanged StaleReason = "document changed on the approved branch since the base"
81
82 // StaleDocRemoved means the document existed at the base and no longer
83 // exists on the approved head.
84 StaleDocRemoved StaleReason = "document was removed from the approved branch since the base"
85
86 // StaleDocAppeared means a document with this id appeared on the approved
87 // branch after the base, so the proposal would silently overwrite it.
88 StaleDocAppeared StaleReason = "a document with this id appeared on the approved branch after the base"
89
90 // StalePathTaken means the proposal's new document targets a path already
91 // occupied on the approved head by a different document.
92 StalePathTaken StaleReason = "path is occupied on the approved branch by a different document"
93 )
94
95 // StaleError reports that a merge cannot proceed because the approved branch
96 // moved under the proposal. Head is the current approved head, which the caller
97 // hands back in the 409 so the agent can refetch and re-propose against it.
98 type StaleError struct {
99 Reason StaleReason
100 // DocID is the document that went stale. Empty for StaleBaseDetached,
101 // which is about the proposal as a whole.
102 DocID string
103 // Path is where the document sits on the approved head, or the contested
104 // path for StalePathTaken. Empty when the document is not on the head.
105 Path string
106 // Base is the proposal's base revision, Head the current approved head.
107 Base plumbing.Hash
108 Head plumbing.Hash
109 }
110
111 0 func (e *StaleError) Error() string {
112 0 if e.DocID == "" {
113 0 return fmt.Sprintf("gitx: stale base %s (approved head is %s): %s",
114 0 e.Base, e.Head, e.Reason)
115 0 }
116 0 if e.Path == "" {
117 0 return fmt.Sprintf("gitx: stale base %s for %s (approved head is %s): %s",
118 0 e.Base, e.DocID, e.Head, e.Reason)
119 0 }
120 0 return fmt.Sprintf("gitx: stale base %s for %s at %q (approved head is %s): %s",
121 0 e.Base, e.DocID, e.Path, e.Head, e.Reason)
122 }
123
124 // Is makes errors.Is(err, ErrStale) true for every StaleError, so callers can
125 // branch on the class and only type-assert when they need the head.
126 1 func (e *StaleError) Is(target error) bool { return target == ErrStale }