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.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 59m ago
4b8c9e1d
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "fmt" |
| 6 | "os" |
| 7 | "os/exec" |
| 8 | "strings" |
| 9 | |
| 10 | "rodent.software/vole/client" |
| 11 | ) |
| 12 | |
| 13 | // releaseCLI creates the one thing a release is: an annotated tag. |
| 14 | // |
| 15 | // There is no release record to write and no upload to perform — the web derives |
| 16 | // the release page and the source tarball from the tag, so a clone that has the |
| 17 | // tag has the release. This command exists because `git tag -a` has three ways to |
| 18 | // get the annotation wrong (lightweight tag, empty message, wrong tagger), and |
| 19 | // saying so once is cheaper than teaching each user separately. |
| 20 | |
| 21 | func releaseUsage() string { |
| 22 | return `usage: vole release create <tag> (-m <text> | -F <file|->) |
| 23 | or: vole release attach <tag> <file>... [--remote <name>] |
| 24 | |
| 25 | A release is an annotated tag: its message is the release notes and its tree is |
| 26 | the source tarball, so both travel with any clone. This writes the tag in the |
| 27 | current repository. Push it with: |
| 28 | |
| 29 | git push --follow-tags` |
| 30 | } |
| 31 | |
| 32 | func cmdRelease(args []string) error { |
| 33 | if len(args) == 0 { |
| 34 | fmt.Fprintln(os.Stderr, releaseUsage()) |
| 35 | return usageErr{msg: "no release subcommand given"} |
| 36 | } |
| 37 | switch args[0] { |
| 38 | case "create": |
| 39 | return cmdReleaseCreate(args[1:]) |
| 40 | case "attach": |
| 41 | return cmdReleaseAttach(args[1:]) |
| 42 | case "-h", "--help", "help": |
| 43 | fmt.Println(releaseUsage()) |
| 44 | return nil |
| 45 | } |
| 46 | fmt.Fprintln(os.Stderr, releaseUsage()) |
| 47 | return usagef("unknown release subcommand %q", args[0]) |
| 48 | } |
| 49 | |
| 50 | func cmdReleaseCreate(args []string) error { |
| 51 | f, err := parseFlags(args, |
| 52 | flagSpec{long: "message", short: "m"}, |
| 53 | flagSpec{long: "file", short: "F"}, |
| 54 | flagSpec{long: "help", short: "h", boolean: true}, |
| 55 | ) |
| 56 | if err != nil { |
| 57 | return err |
| 58 | } |
| 59 | if f.help { |
| 60 | fmt.Println(releaseUsage()) |
| 61 | return nil |
| 62 | } |
| 63 | if len(f.rest) != 1 { |
| 64 | return usagef("usage: vole release create <tag> (-m <text> | -F <file|->)") |
| 65 | } |
| 66 | name := f.rest[0] |
| 67 | if strings.HasPrefix(name, "-") { |
| 68 | return usagef("invalid tag name %q", name) |
| 69 | } |
| 70 | |
| 71 | notes, err := readBody(f) |
| 72 | if err != nil { |
| 73 | return err |
| 74 | } |
| 75 | if strings.TrimSpace(notes) == "" { |
| 76 | return usagef("a release needs notes: pass -m <text> or -F <file>.\n"+ |
| 77 | "An annotated tag with an empty message is a bare tag; use git tag %s if that is what you want", name) |
| 78 | } |
| 79 | |
| 80 | dir, err := client.CurrentGitDir() |
| 81 | if err != nil { |
| 82 | return err |
| 83 | } |
| 84 | |
| 85 | // git refuses the tag too, but its message ("already exists") does not say |
| 86 | // that the existing tag is what the web is already serving. |
| 87 | if exec.Command("git", "-C", dir, "rev-parse", "--verify", "--quiet", |
| 88 | "refs/tags/"+name).Run() == nil { |
| 89 | return fmt.Errorf("tag %s already exists; a release is immutable once pushed. "+ |
| 90 | "Tag the new commit with a new name instead", name) |
| 91 | } |
| 92 | |
| 93 | cmd := exec.Command("git", "-C", dir, "tag", "-a", name, "-F", "-") |
| 94 | cmd.Stdin = bytes.NewReader([]byte(notes)) |
| 95 | var stderr bytes.Buffer |
| 96 | cmd.Stderr = &stderr |
| 97 | if err := cmd.Run(); err != nil { |
| 98 | return fmt.Errorf("git tag: %w: %s", err, strings.TrimSpace(stderr.String())) |
| 99 | } |
| 100 | |
| 101 | fmt.Printf("tagged %s (annotated)\n", name) |
| 102 | fmt.Printf("push it: git push --follow-tags\n") |
| 103 | return nil |
| 104 | } |
| 105 |