vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
refactor
c5902451905acdaa478f8476844ccd4a510d9ca9 · Keenan Nemetz · 16d ago · parent ec2aba4b
internal/hook/hook.go → server/hook/hook.go +51−60
| @@ -11,17 +11,15 @@ import ( | |||
| 11 | 11 | "strings" | |
| 12 | 12 | "time" | |
| 13 | 13 | ||
| 14 | "github.com/rodent-software/vole/internal/access" | ||
| 15 | "github.com/rodent-software/vole/internal/acl" | ||
| 16 | "github.com/rodent-software/vole/internal/gitx" | ||
| 17 | "github.com/rodent-software/vole/internal/identity" | ||
| 18 | "github.com/rodent-software/vole/internal/issues" | ||
| 19 | "github.com/rodent-software/vole/internal/lfs" | ||
| 20 | "github.com/rodent-software/vole/internal/locks" | ||
| 21 | "github.com/rodent-software/vole/internal/repo" | ||
| 22 | "github.com/rodent-software/vole/internal/requests" | ||
| 23 | "github.com/rodent-software/vole/internal/runs" | ||
| 24 | "github.com/rodent-software/vole/internal/workflow" | ||
| 14 | "github.com/rodent-software/vole/ci" | ||
| 15 | "github.com/rodent-software/vole/ci/runs" | ||
| 16 | "github.com/rodent-software/vole/core/git" | ||
| 17 | "github.com/rodent-software/vole/core/lfs" | ||
| 18 | "github.com/rodent-software/vole/forge/access" | ||
| 19 | "github.com/rodent-software/vole/forge/identity" | ||
| 20 | "github.com/rodent-software/vole/forge/repo" | ||
| 21 | "github.com/rodent-software/vole/internal/format" | ||
| 22 | "github.com/rodent-software/vole/metadata" | ||
| 25 | 23 | ) | |
| 26 | 24 | ||
| 27 | 25 | type Config struct { | |
| @@ -69,7 +67,7 @@ func PreReceive(cfg Config) int { | |||
| 69 | 67 | // assume a directory is a repository. | |
| 70 | 68 | var ( | |
| 71 | 69 | acls *access.Store | |
| 72 | lockTable *locks.Store | ||
| 70 | lockTable *lfs.LockStore | ||
| 73 | 71 | err error | |
| 74 | 72 | ) | |
| 75 | 73 | if cfg.UsersDB != "" { | |
| @@ -81,7 +79,7 @@ func PreReceive(cfg Config) int { | |||
| 81 | 79 | defer acls.Close() | |
| 82 | 80 | // The lock table shares that database, so the push path learns what is held | |
| 83 | 81 | // without a second endpoint and without the client's cooperation. | |
| 84 | lockTable, err = locks.Open(cfg.UsersDB) | ||
| 82 | lockTable, err = lfs.OpenLocks(cfg.UsersDB) | ||
| 85 | 83 | if err != nil { | |
| 86 | 84 | reject(cfg.Out, "cannot open the lock table: %v", err) | |
| 87 | 85 | return 1 | |
| @@ -123,7 +121,7 @@ func PreReceive(cfg Config) int { | |||
| 123 | 121 | who := func(accountID string) string { return names(accountID) } | |
| 124 | 122 | ||
| 125 | 123 | role := a.RoleFor(actorID) | |
| 126 | if !role.AtLeast(acl.Write) { | ||
| 124 | if !role.AtLeast(access.Write) { | ||
| 127 | 125 | reject(cfg.Out, "%s does not have write access to %s (role: %s)", actor, m.Name, role) | |
| 128 | 126 | return 1 | |
| 129 | 127 | } | |
| @@ -137,7 +135,7 @@ func PreReceive(cfg Config) int { | |||
| 137 | 135 | return 0 | |
| 138 | 136 | } | |
| 139 | 137 | ||
| 140 | g := gitx.Open(cfg.RepoDir) | ||
| 138 | g := git.Open(cfg.RepoDir) | ||
| 141 | 139 | // receive-pack reveals the pushed objects through a quarantine: it points | |
| 142 | 140 | // GIT_OBJECT_DIRECTORY at the temporary object directory and lists the real | |
| 143 | 141 | // one in the alternates. gitx scrubs those variables so commands against other | |
| @@ -163,7 +161,7 @@ func PreReceive(cfg Config) int { | |||
| 163 | 161 | // The metadata branch is append-only in shape: linear history, no merge | |
| 164 | 162 | // commits. A merge node in metadata says nothing and makes the log harder | |
| 165 | 163 | // to read than the change it describes. | |
| 166 | if c.ref == issues.Ref { | ||
| 164 | if c.ref == metadata.Ref { | ||
| 167 | 165 | if merges, err := g.CountMerges(c.old, c.new); err != nil { | |
| 168 | 166 | problems = append(problems, fmt.Sprintf("%s: cannot check history shape: %v", c.ref, err)) | |
| 169 | 167 | } else if merges > 0 { | |
| @@ -174,36 +172,36 @@ func PreReceive(cfg Config) int { | |||
| 174 | 172 | ||
| 175 | 173 | // A rewrite of the metadata branch is allowed, because discussion content | |
| 176 | 174 | // sometimes has to be redacted, but only by an admin and never silently. | |
| 177 | if c.old != "" && c.old != gitx.ZeroSHA && c.new != gitx.ZeroSHA { | ||
| 175 | if c.old != "" && c.old != git.ZeroSHA && c.new != git.ZeroSHA { | ||
| 178 | 176 | ff, err := g.IsAncestor(c.old, c.new) | |
| 179 | 177 | if err != nil { | |
| 180 | 178 | problems = append(problems, fmt.Sprintf("%s: cannot check fast-forward: %v", c.ref, err)) | |
| 181 | 179 | } else if !ff { | |
| 182 | if !role.AtLeast(acl.Admin) { | ||
| 180 | if !role.AtLeast(access.Admin) { | ||
| 183 | 181 | problems = append(problems, fmt.Sprintf( | |
| 184 | 182 | "%s: rewriting history requires admin access; %s is %s", c.ref, actor, role)) | |
| 185 | 183 | } else { | |
| 186 | 184 | auditLine(cfg.AuditPath, fmt.Sprintf("force-push %s %s -> %s by %s in %s", | |
| 187 | c.ref, short(c.old), short(c.new), actor, m.Name)) | ||
| 185 | c.ref, format.SHA(c.old), format.SHA(c.new), actor, m.Name)) | ||
| 188 | 186 | } | |
| 189 | 187 | } | |
| 190 | 188 | } | |
| 191 | 189 | ||
| 192 | 190 | // Workflows are executable policy for the CI phase, so the version being | |
| 193 | 191 | // pushed is validated here rather than when a runner picks it up. | |
| 194 | if strings.HasPrefix(c.ref, "refs/heads/") && c.new != "" && c.new != gitx.ZeroSHA { | ||
| 192 | if strings.HasPrefix(c.ref, "refs/heads/") && c.new != "" && c.new != git.ZeroSHA { | ||
| 195 | 193 | touches, err := touchesWorkflows(g, c) | |
| 196 | 194 | if err != nil { | |
| 197 | problems = append(problems, fmt.Sprintf("%s: cannot check %s: %v", c.ref, workflow.Dir, err)) | ||
| 195 | problems = append(problems, fmt.Sprintf("%s: cannot check %s: %v", c.ref, ci.Dir, err)) | ||
| 198 | 196 | } else if touches { | |
| 199 | problems = append(problems, workflow.ValidateTree(g, c.new)...) | ||
| 197 | problems = append(problems, ci.ValidateTree(g, c.new)...) | ||
| 200 | 198 | } | |
| 201 | 199 | } | |
| 202 | 200 | ||
| 203 | 201 | // Integrity checks run before the protected-branch gate, because an admin's | |
| 204 | 202 | // approval bypass is about review, not about whether the repository works: | |
| 205 | 203 | // a pointer with no object behind it is broken no matter who pushed it. | |
| 206 | if cfg.MaxBlobBytes > 0 && c.new != gitx.ZeroSHA { | ||
| 204 | if cfg.MaxBlobBytes > 0 && c.new != git.ZeroSHA { | ||
| 207 | 205 | overs, err := oversizedBlobs(g, c, cfg.MaxBlobBytes) | |
| 208 | 206 | if err != nil { | |
| 209 | 207 | problems = append(problems, fmt.Sprintf("%s: cannot check object sizes: %v", c.ref, err)) | |
| @@ -213,7 +211,7 @@ func PreReceive(cfg Config) int { | |||
| 213 | 211 | } | |
| 214 | 212 | // LFS objects live outside the pack, so receive-pack cannot notice that a | |
| 215 | 213 | // pointer names nothing. This is the only place that can. | |
| 216 | if cfg.LFS != nil && c.new != gitx.ZeroSHA { | ||
| 214 | if cfg.LFS != nil && c.new != git.ZeroSHA { | ||
| 217 | 215 | lfsProblems, warnings, err := lfsFindings(g, m.Name, c.new, cfg.LFS, cfg.LFSWarnBytes) | |
| 218 | 216 | if err != nil { | |
| 219 | 217 | problems = append(problems, fmt.Sprintf("%s: cannot check LFS pointers: %v", c.ref, err)) | |
| @@ -235,7 +233,7 @@ func PreReceive(cfg Config) int { | |||
| 235 | 233 | } else if lockProblems, err := lockedPathFindings(g, c, held, actorID, who); err != nil { | |
| 236 | 234 | problems = append(problems, fmt.Sprintf("%s: cannot check locks: %v", c.ref, err)) | |
| 237 | 235 | } else if len(lockProblems) > 0 { | |
| 238 | if role.AtLeast(acl.Admin) { | ||
| 236 | if role.AtLeast(access.Admin) { | ||
| 239 | 237 | for _, p := range lockProblems { | |
| 240 | 238 | auditLine(cfg.AuditPath, fmt.Sprintf("lock override %s by %s in %s: %s", | |
| 241 | 239 | c.ref, actor, m.Name, p)) | |
| @@ -254,7 +252,7 @@ func PreReceive(cfg Config) int { | |||
| 254 | 252 | // mistyped tag is a real problem and the alternative is a repository | |
| 255 | 253 | // nobody can repair. | |
| 256 | 254 | if relProblems := releaseFindings(g, c); len(relProblems) > 0 { | |
| 257 | if role.AtLeast(acl.Admin) { | ||
| 255 | if role.AtLeast(access.Admin) { | ||
| 258 | 256 | for _, p := range relProblems { | |
| 259 | 257 | auditLine(cfg.AuditPath, fmt.Sprintf("release rewrite %s by %s in %s: %s", | |
| 260 | 258 | c.ref, actor, m.Name, p)) | |
| @@ -268,7 +266,7 @@ func PreReceive(cfg Config) int { | |||
| 268 | 266 | if a.IsProtected(c.ref) { | |
| 269 | 267 | // Admins keep the bypass they always had. Everyone else needs an | |
| 270 | 268 | // approved request that names the exact commit they are landing. | |
| 271 | if role.AtLeast(acl.Admin) { | ||
| 269 | if role.AtLeast(access.Admin) { | ||
| 272 | 270 | continue | |
| 273 | 271 | } | |
| 274 | 272 | if a.RequiredApprovals() <= 0 { | |
| @@ -277,14 +275,14 @@ func PreReceive(cfg Config) int { | |||
| 277 | 275 | c.ref, role)) | |
| 278 | 276 | continue | |
| 279 | 277 | } | |
| 280 | reqs, err := requests.Load(g, issues.Ref) | ||
| 278 | reqs, err := metadata.LoadRequests(g, metadata.Ref) | ||
| 281 | 279 | if err != nil { | |
| 282 | 280 | problems = append(problems, fmt.Sprintf( | |
| 283 | 281 | "%s: cannot read change requests, refusing the push: %v", c.ref, err)) | |
| 284 | 282 | continue | |
| 285 | 283 | } | |
| 286 | 284 | branch := strings.TrimPrefix(c.ref, "refs/heads/") | |
| 287 | m, err := requests.FindApprovedMatch(g, reqs, requests.ApprovalRequirement{ | ||
| 285 | m, err := metadata.FindApprovedMatch(g, reqs, metadata.ApprovalRequirement{ | ||
| 288 | 286 | Branch: branch, | |
| 289 | 287 | NewTip: c.new, | |
| 290 | 288 | OldTip: c.old, | |
| @@ -298,7 +296,7 @@ func PreReceive(cfg Config) int { | |||
| 298 | 296 | problems = append(problems, fmt.Sprintf( | |
| 299 | 297 | "%s is protected: %s needs %d approval(s) on an open change request whose head is exactly %s. "+ | |
| 300 | 298 | "Add requests/<ulid>/request.md with base=%q and head=%s, then have an approver commit approvals/<their-account-id>.md naming that same head.", | |
| 301 | c.ref, actor, a.RequiredApprovals(), short(c.new), branch, short(c.new))) | ||
| 299 | c.ref, actor, a.RequiredApprovals(), format.SHA(c.new), branch, format.SHA(c.new))) | ||
| 302 | 300 | continue | |
| 303 | 301 | } | |
| 304 | 302 | // Approvals say a person read the change; checks say it works. Both are | |
| @@ -311,12 +309,12 @@ func PreReceive(cfg Config) int { | |||
| 311 | 309 | } | |
| 312 | 310 | if len(gaps) > 0 { | |
| 313 | 311 | problems = append(problems, fmt.Sprintf("%s: checks have not passed for %s:\n %s", | |
| 314 | c.ref, short(m.Request.Head), strings.Join(gaps, "\n "))) | ||
| 312 | c.ref, format.SHA(m.Request.Head), strings.Join(gaps, "\n "))) | ||
| 315 | 313 | continue | |
| 316 | 314 | } | |
| 317 | 315 | } | |
| 318 | 316 | auditLine(cfg.AuditPath, fmt.Sprintf("merge %s via request %s (head %s) approved by %s", | |
| 319 | c.ref, m.Request.ID, short(m.Request.Head), strings.Join(m.Approvers, ","))) | ||
| 317 | c.ref, m.Request.ID, format.SHA(m.Request.Head), strings.Join(m.Approvers, ","))) | ||
| 320 | 318 | } | |
| 321 | 319 | // The issues branch is user-authored content, so it is validated at the | |
| 322 | 320 | // boundary: bad data never reaches the tree the web reads. | |
| @@ -324,15 +322,15 @@ func PreReceive(cfg Config) int { | |||
| 324 | 322 | // Run records share the branch but not the authorship: they are written by | |
| 325 | 323 | // the runner that ran the job, and the gate's question about them is who was | |
| 326 | 324 | // allowed to. See records.go. | |
| 327 | if c.ref == issues.Ref { | ||
| 325 | if c.ref == metadata.Ref { | ||
| 328 | 326 | problems = append(problems, recordFindings(g, c, a, actor, actorID, | |
| 329 | 327 | m.Name, cfg.UsersDB, cfg.RunsDB, who)...) | |
| 330 | 328 | } | |
| 331 | if c.ref == issues.Ref && c.new != gitx.ZeroSHA { | ||
| 332 | for _, prob := range issues.Validate(g, c.new) { | ||
| 329 | if c.ref == metadata.Ref && c.new != git.ZeroSHA { | ||
| 330 | for _, prob := range metadata.ValidateIssues(g, c.new) { | ||
| 333 | 331 | problems = append(problems, "issues: "+prob) | |
| 334 | 332 | } | |
| 335 | for _, prob := range requests.Validate(g, c.new) { | ||
| 333 | for _, prob := range metadata.ValidateRequests(g, c.new) { | ||
| 336 | 334 | problems = append(problems, "requests: "+prob) | |
| 337 | 335 | } | |
| 338 | 336 | // An approval is a statement about the actor's own review, so the file | |
| @@ -361,7 +359,7 @@ func PreReceive(cfg Config) int { | |||
| 361 | 359 | return 1 | |
| 362 | 360 | } | |
| 363 | 361 | ||
| 364 | func oversizedBlobs(g *gitx.Repo, c change, max int64) ([]string, error) { | ||
| 362 | func oversizedBlobs(g *git.Repo, c change, max int64) ([]string, error) { | ||
| 365 | 363 | sizes, err := g.NewBlobSizes(c.old, c.new) | |
| 366 | 364 | if err != nil { | |
| 367 | 365 | return nil, err | |
| @@ -407,18 +405,18 @@ func readChanges(r io.Reader) ([]change, error) { | |||
| 407 | 405 | // Every write counts: added, modified, renamed, retyped. Checking only additions | |
| 408 | 406 | // is not enough, because a file that already exists can simply be edited to name | |
| 409 | 407 | // a different commit. | |
| 410 | func approvalOwnership(g *gitx.Repo, c change, actor, actorID string, role acl.Role, auditPath string, who func(string) string) []string { | ||
| 408 | func approvalOwnership(g *git.Repo, c change, actor, actorID string, role access.Role, auditPath string, who func(string) string) []string { | ||
| 411 | 409 | // On the first push the branch has no old commit, so every approval in the | |
| 412 | 410 | // tree is new and must be checked. Skipping that would let whoever creates the | |
| 413 | 411 | // branch seed approvals in other people's names. | |
| 414 | if c.old == "" || c.old == gitx.ZeroSHA { | ||
| 412 | if c.old == "" || c.old == git.ZeroSHA { | ||
| 415 | 413 | blobs, err := g.LsTreeR(c.new) | |
| 416 | 414 | if err != nil { | |
| 417 | 415 | return []string{fmt.Sprintf("requests: cannot verify approvals in this push: %v", err)} | |
| 418 | 416 | } | |
| 419 | 417 | var problems []string | |
| 420 | 418 | for _, b := range blobs { | |
| 421 | if owner := requests.ApprovalPath(b.Name); owner != "" && owner != actorID { | ||
| 419 | if owner := metadata.ApprovalPath(b.Name); owner != "" && owner != actorID { | ||
| 422 | 420 | problems = append(problems, fmt.Sprintf( | |
| 423 | 421 | "requests: %s: %s may not create an approval for %s", b.Name, actor, who(owner))) | |
| 424 | 422 | } | |
| @@ -432,7 +430,7 @@ func approvalOwnership(g *gitx.Repo, c change, actor, actorID string, role acl.R | |||
| 432 | 430 | } | |
| 433 | 431 | var problems []string | |
| 434 | 432 | for path, st := range status { | |
| 435 | owner := requests.ApprovalPath(path) | ||
| 433 | owner := metadata.ApprovalPath(path) | ||
| 436 | 434 | if owner == "" || owner == actorID { | |
| 437 | 435 | continue | |
| 438 | 436 | } | |
| @@ -441,7 +439,7 @@ func approvalOwnership(g *gitx.Repo, c change, actor, actorID string, role acl.R | |||
| 441 | 439 | // statement. The owner may withdraw; an admin may clean up, because the | |
| 442 | 440 | // alternative (force-push) is heavier for a one-line fix. Neither is | |
| 443 | 441 | // silent: the admin path is audited. | |
| 444 | if !role.AtLeast(acl.Admin) { | ||
| 442 | if !role.AtLeast(access.Admin) { | ||
| 445 | 443 | problems = append(problems, fmt.Sprintf( | |
| 446 | 444 | "requests: %s: %s may not delete %s's approval; only %s can withdraw it", | |
| 447 | 445 | path, actor, who(owner), who(owner))) | |
| @@ -476,8 +474,8 @@ func approvalOwnership(g *gitx.Repo, c change, actor, actorID string, role acl.R | |||
| 476 | 474 | // - A request's own file is not covered either, for the same reason: its state, | |
| 477 | 475 | // head and base are shared, and updating them is the author's normal edit. | |
| 478 | 476 | // - A first push has nothing to compare against, so it is skipped. | |
| 479 | func commentOwnership(g *gitx.Repo, c change, actor, actorID string, role acl.Role, auditPath, usersDB string, out io.Writer) []string { | ||
| 480 | if c.old == "" || c.old == gitx.ZeroSHA { | ||
| 477 | func commentOwnership(g *git.Repo, c change, actor, actorID string, role access.Role, auditPath, usersDB string, out io.Writer) []string { | ||
| 478 | if c.old == "" || c.old == git.ZeroSHA { | ||
| 481 | 479 | return nil | |
| 482 | 480 | } | |
| 483 | 481 | status, err := g.DiffStatus(c.old, c.new) | |
| @@ -507,7 +505,7 @@ func commentOwnership(g *gitx.Repo, c change, actor, actorID string, role acl.Ro | |||
| 507 | 505 | for path, st := range status { | |
| 508 | 506 | // An issue comment and a review comment on a change request are the same | |
| 509 | 507 | // file shape, so one rule covers both. | |
| 510 | if !issues.IsCommentPath(path) && !requests.IsCommentPath(path) { | ||
| 508 | if !metadata.IsIssueCommentPath(path) && !metadata.IsRequestCommentPath(path) { | ||
| 511 | 509 | continue | |
| 512 | 510 | } | |
| 513 | 511 | // A rename has no blob at the destination, so its author cannot be | |
| @@ -521,7 +519,7 @@ func commentOwnership(g *gitx.Repo, c change, actor, actorID string, role acl.Ro | |||
| 521 | 519 | "issues: %s: cannot establish whose comment this is, refusing the push: %v", path, err)) | |
| 522 | 520 | continue | |
| 523 | 521 | } | |
| 524 | author, err := issues.AuthorOf(raw) | ||
| 522 | author, err := metadata.AuthorOf(raw) | ||
| 525 | 523 | if err != nil { | |
| 526 | 524 | problems = append(problems, fmt.Sprintf( | |
| 527 | 525 | "issues: %s: cannot establish whose comment this is, refusing the push: %v", path, err)) | |
| @@ -537,7 +535,7 @@ func commentOwnership(g *gitx.Repo, c change, actor, actorID string, role acl.Ro | |||
| 537 | 535 | // An admin may repair a thread — a comment by somebody who has left | |
| 538 | 536 | // cannot be fixed by them — but never silently: the override is audited, | |
| 539 | 537 | // like a force-push and a release rewrite. | |
| 540 | if role.AtLeast(acl.Admin) { | ||
| 538 | if role.AtLeast(access.Admin) { | ||
| 541 | 539 | auditLine(auditPath, fmt.Sprintf("comment-%s %s (author %s) by admin %s", st, path, author, actor)) | |
| 542 | 540 | reject(out, "admin override, audited: %s %s %s's comment", actor, verb, author) | |
| 543 | 541 | continue | |
| @@ -573,14 +571,14 @@ func ownsAuthor(roster *identity.Store, author, actor, actorID string) bool { | |||
| 573 | 571 | ||
| 574 | 572 | // touchesWorkflows reports whether this push changes anything under the workflow | |
| 575 | 573 | // directory, so repositories without CI pay one diff and no more. | |
| 576 | func touchesWorkflows(g *gitx.Repo, c change) (bool, error) { | ||
| 577 | if c.old == "" || c.old == gitx.ZeroSHA { | ||
| 574 | func touchesWorkflows(g *git.Repo, c change) (bool, error) { | ||
| 575 | if c.old == "" || c.old == git.ZeroSHA { | ||
| 578 | 576 | blobs, err := g.LsTreeR(c.new) | |
| 579 | 577 | if err != nil { | |
| 580 | 578 | return false, err | |
| 581 | 579 | } | |
| 582 | 580 | for _, b := range blobs { | |
| 583 | if strings.HasPrefix(b.Name, workflow.Dir+"/") { | ||
| 581 | if strings.HasPrefix(b.Name, ci.Dir+"/") { | ||
| 584 | 582 | return true, nil | |
| 585 | 583 | } | |
| 586 | 584 | } | |
| @@ -591,7 +589,7 @@ func touchesWorkflows(g *gitx.Repo, c change) (bool, error) { | |||
| 591 | 589 | return false, err | |
| 592 | 590 | } | |
| 593 | 591 | for p := range status { | |
| 594 | if strings.HasPrefix(p, workflow.Dir+"/") { | ||
| 592 | if strings.HasPrefix(p, ci.Dir+"/") { | ||
| 595 | 593 | return true, nil | |
| 596 | 594 | } | |
| 597 | 595 | } | |
| @@ -637,13 +635,6 @@ func auditLine(path, line string) { | |||
| 637 | 635 | f.Close() | |
| 638 | 636 | } | |
| 639 | 637 | ||
| 640 | func short(sha string) string { | ||
| 641 | if len(sha) > 8 { | ||
| 642 | return sha[:8] | ||
| 643 | } | ||
| 644 | return sha | ||
| 645 | } | ||
| 646 | |||
| 647 | 638 | func reject(w io.Writer, format string, args ...any) { | |
| 648 | 639 | fmt.Fprintf(w, "vole: "+format+"\n", args...) | |
| 649 | 640 | } | |
| @@ -708,8 +699,8 @@ func PostReceive(cfg Config) int { | |||
| 708 | 699 | // shortOrNone renders a sha for the log, naming the two states that are not a commit: | |
| 709 | 700 | // a ref being created and one being deleted. | |
| 710 | 701 | func shortOrNone(sha string) string { | |
| 711 | if sha == "" || sha == gitx.ZeroSHA { | ||
| 702 | if sha == "" || sha == git.ZeroSHA { | ||
| 712 | 703 | return "none" | |
| 713 | 704 | } | |
| 714 | return short(sha) | ||
| 705 | return format.SHA(sha) | ||
| 715 | 706 | } | |