From 7fb565c3a2e83e72597da5a9f7df6fa5f78c14be Mon Sep 17 00:00:00 2001 From: Amolith Date: Sun, 15 Mar 2026 11:03:45 -0600 Subject: [PATCH] Add code generator for restic commands and flags Add cmd/gen-restic-cmds, a tool that parses restic's generated man pages and produces a Go source file mapping every subcommand to its accepted flags. Types and generated output live in internal/restic. --- cmd/gen-restic-cmds/main.go | 266 ++++++++++++++++++ internal/restic/command.go | 20 ++ internal/restic/commands_gen.go | 476 ++++++++++++++++++++++++++++++++ mise.toml | 16 ++ 4 files changed, 778 insertions(+) create mode 100644 cmd/gen-restic-cmds/main.go create mode 100644 internal/restic/command.go create mode 100644 internal/restic/commands_gen.go diff --git a/cmd/gen-restic-cmds/main.go b/cmd/gen-restic-cmds/main.go new file mode 100644 index 0000000000000000000000000000000000000000..374988f92ca83a7aa34f258c7a9a7e50379b64c7 --- /dev/null +++ b/cmd/gen-restic-cmds/main.go @@ -0,0 +1,266 @@ +// gen-restic-cmds parses restic's man pages and generates a Go source file +// containing every command and its accepted flags. Run via go generate: +// +// //go:generate go run git.secluded.site/keld/cmd/gen-restic-cmds +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "sort" + "strings" + "text/template" +) + +// command mirrors the runtime type but lives here so the generator has no +// import dependency on the package it writes into. +type command struct { + Name string + Description string + Options []option +} + +type option struct { + Name string + Alias string + Default string + Description string + Repeatable bool +} + +// Regexes for the small subset of troff macros cobra emits. +var ( + sectionStart = regexp.MustCompile(`^\.SH (.+)$`) + paragraphBreak = regexp.MustCompile(`^\.PP$`) + escapeSequence = regexp.MustCompile(`\\f[A-Z]|\\&|\\`) + strayBackticks = regexp.MustCompile("[`]{2,}") + filePattern = regexp.MustCompile(`^restic(-.*)?\.1$`) +) + +func main() { + manDir, err := os.MkdirTemp("", "keld-gen-*") + if err != nil { + fatalf("creating temp dir: %v", err) + } + defer func() { _ = os.RemoveAll(manDir) }() + + resticBin := os.Getenv("KELD_EXECUTABLE") + if resticBin == "" { + resticBin = "restic" + } + + // Capture the version string for the header comment. + versionOut, err := exec.Command(resticBin, "version").Output() + if err != nil { + fatalf("running %s version: %v", resticBin, err) + } + version := strings.TrimSpace(string(versionOut)) + + genCmd := exec.Command(resticBin, "generate", "--man", manDir) + genCmd.Stderr = os.Stderr + if err := genCmd.Run(); err != nil { + fatalf("running %s generate --man: %v", resticBin, err) + } + + entries, err := os.ReadDir(manDir) + if err != nil { + fatalf("reading man dir: %v", err) + } + + var commands []command + for _, entry := range entries { + if entry.IsDir() || !filePattern.MatchString(entry.Name()) { + continue + } + + cmd, err := parseManPage(filepath.Join(manDir, entry.Name())) + if err != nil { + fatalf("parsing %s: %v", entry.Name(), err) + } + if cmd != nil { + commands = append(commands, *cmd) + } + } + + sort.Slice(commands, func(i, j int) bool { + return commands[i].Name < commands[j].Name + }) + + outPath := "commands_gen.go" + if err := writeGoFile(outPath, version, commands); err != nil { + fatalf("writing %s: %v", outPath, err) + } + + fmt.Fprintf(os.Stderr, "generated %s (%d commands from %s)\n", outPath, len(commands), version) +} + +// parseManPage reads a single restic-*.1 man page and extracts the command +// name, description, and per-command options. Inherited (global) options are +// skipped for subcommand pages; they appear once via restic.1. +func parseManPage(path string) (*command, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + + base := filepath.Base(path) + m := filePattern.FindStringSubmatch(base) + if m == nil { + return nil, fmt.Errorf("unexpected filename %s", base) + } + + // restic.1 → "" → "global", restic-backup.1 → "-backup" → "backup" + cmdName := strings.TrimPrefix(m[1], "-") + if cmdName == "" { + cmdName = "global" + } + + cmd := &command{Name: cmdName} + var ( + section string + opt *option + ) + + flush := func() { + if opt != nil { + opt.Description = strings.TrimSpace(opt.Description) + cmd.Options = append(cmd.Options, *opt) + opt = nil + } + } + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + + // Handle troff macro lines. + if strings.HasPrefix(line, ".") { + if sm := sectionStart.FindStringSubmatch(line); sm != nil { + flush() + section = sm[1] + continue + } + if paragraphBreak.MatchString(line) { + flush() + } + continue + } + + // Strip all font/escape sequences and backtick artifacts from content lines. + line = escapeSequence.ReplaceAllString(line, "") + line = strayBackticks.ReplaceAllString(line, "") + + switch section { + case "NAME": + // "restic-backup - Create a new backup" → description after " - " + if idx := strings.Index(line, " - "); idx >= 0 { + cmd.Description = line[idx+3:] + } + + case "OPTIONS": + parseOptionLine(line, &opt, cmd) + + // Skip inherited options on subcommand pages; they duplicate + // what restic.1 provides as "global". + } + } + + flush() + return cmd, scanner.Err() +} + +// parseOptionLine handles a single content line within an OPTIONS section. +// It either starts a new option (when the line contains "--") or appends to +// the current option's description. +func parseOptionLine(line string, opt **option, cmd *command) { + if *opt == nil { + // Looking for a new option — must contain a long flag. + if !strings.Contains(line, "--") { + return + } + *opt = &option{} + + // Split on "=" to separate the flag spec from the default value. + // "-n, --dry-run[=false]" → ["-n, --dry-run[", "false]"] + // "--exclude=[]" → ["--exclude", "[]"] + parts := strings.SplitN(line, "=", 2) + flagSpec := parts[0] + + if len(parts) > 1 { + def := strings.Trim(parts[1], "[]\"") + (*opt).Default = def + (*opt).Repeatable = strings.Contains(parts[1], "[]") && def == "" + } + + // Split the flag spec on "," to handle "-n, --dry-run" form. + for _, param := range strings.Split(flagSpec, ",") { + param = strings.TrimSpace(param) + param = strings.Trim(param, "[]") + if strings.HasPrefix(param, "--") { + (*opt).Name = strings.TrimPrefix(param, "--") + } else if strings.HasPrefix(param, "-") { + (*opt).Alias = strings.TrimPrefix(param, "-") + } + } + } else { + // Continuation line — append to description. + desc := strings.TrimSpace(line) + if desc == "" { + return + } + if (*opt).Description != "" { + (*opt).Description += " " + } + (*opt).Description += desc + } +} + +func writeGoFile(path, version string, commands []command) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + return outputTmpl.Execute(f, struct { + Version string + Commands []command + }{ + Version: version, + Commands: commands, + }) +} + +var outputTmpl = template.Must(template.New("").Funcs(template.FuncMap{ + "quote": func(s string) string { return fmt.Sprintf("%q", s) }, +}).Parse(`// Code generated by gen-restic-cmds; DO NOT EDIT. +// Source: {{.Version}} + +package restic + +// Commands maps restic command names to their parsed man-page definitions. +// The "global" entry contains flags common to all commands. +var Commands = map[string]Command{ +{{- range .Commands}} + {{quote .Name}}: { + Name: {{quote .Name}}, + Description: {{quote .Description}}, + Options: []Option{ +{{- range .Options}} + {Name: {{quote .Name}}, Alias: {{quote .Alias}}, Default: {{quote .Default}}, Description: {{quote .Description}}, Repeatable: {{printf "%v" .Repeatable}}}, +{{- end}} + }, + }, +{{- end}} +} +`)) + +func fatalf(format string, args ...any) { + fmt.Fprintf(os.Stderr, "gen-restic-cmds: "+format+"\n", args...) + os.Exit(1) +} diff --git a/internal/restic/command.go b/internal/restic/command.go new file mode 100644 index 0000000000000000000000000000000000000000..327cffa76af6756011f1d1b45465d18407ad90f4 --- /dev/null +++ b/internal/restic/command.go @@ -0,0 +1,20 @@ +package restic + +//go:generate go run git.secluded.site/keld/cmd/gen-restic-cmds + +// Command describes a restic subcommand and its accepted flags, as parsed +// from restic's generated man pages. +type Command struct { + Name string + Description string + Options []Option +} + +// Option describes a single CLI flag accepted by a restic command. +type Option struct { + Name string // long flag name without dashes, e.g. "dry-run" + Alias string // single-char short flag, e.g. "n" + Default string // default value as printed in the man page + Description string + Repeatable bool // true when the flag accepts multiple values (default "[]") +} diff --git a/internal/restic/commands_gen.go b/internal/restic/commands_gen.go new file mode 100644 index 0000000000000000000000000000000000000000..b2f6ef66065d5d0c7fdd3aac872a1dcb5b7835c9 --- /dev/null +++ b/internal/restic/commands_gen.go @@ -0,0 +1,476 @@ +// Code generated by gen-restic-cmds; DO NOT EDIT. +// Source: restic 0.18.1 compiled with go1.25.6 X:nodwarf5 on linux/amd64 + +package restic + +// Commands maps restic command names to their parsed man-page definitions. +// The "global" entry contains flags common to all commands. +var Commands = map[string]Command{ + "backup": { + Name: "backup", + Description: "Create a new backup of files and/or directories", + Options: []Option{ + {Name: "dry-run", Alias: "n", Default: "false", Description: "do not upload or write any data, just show what would be done", Repeatable: false}, + {Name: "exclude", Alias: "e", Default: "", Description: "exclude a pattern (can be specified multiple times)", Repeatable: true}, + {Name: "exclude-caches", Alias: "", Default: "false", Description: "excludes cache directories that are marked with a CACHEDIR.TAG file. See https://bford.info/cachedir/ for the Cache Directory Tagging Standard", Repeatable: false}, + {Name: "exclude-file", Alias: "", Default: "", Description: "read exclude patterns from a file (can be specified multiple times)", Repeatable: true}, + {Name: "exclude-if-present", Alias: "", Default: "", Description: "takes filename[:header], exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)", Repeatable: true}, + {Name: "exclude-larger-than", Alias: "", Default: "", Description: "max size of the files to be backed up (allowed suffixes: k/K, m/M, g/G, t/T)", Repeatable: false}, + {Name: "files-from", Alias: "", Default: "", Description: "read the files to backup from file (can be combined with file args; can be specified multiple times)", Repeatable: true}, + {Name: "files-from-raw", Alias: "", Default: "", Description: "read the files to backup from file (can be combined with file args; can be specified multiple times)", Repeatable: true}, + {Name: "files-from-verbatim", Alias: "", Default: "", Description: "read the files to backup from file (can be combined with file args; can be specified multiple times)", Repeatable: true}, + {Name: "force", Alias: "f", Default: "false", Description: "force re-reading the source files/directories (overrides the \"parent\" flag)", Repeatable: false}, + {Name: "group-by", Alias: "g", Default: "host,paths", Description: "group snapshots by host, paths and/or tags, separated by comma (disable grouping with '')", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for backup", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "set the hostname for the snapshot manually (default: $RESTIC_HOST). To prevent an expensive rescan use the \"parent\" flag", Repeatable: false}, + {Name: "iexclude", Alias: "", Default: "", Description: "same as --exclude pattern but ignores the casing of filenames", Repeatable: true}, + {Name: "iexclude-file", Alias: "", Default: "", Description: "same as --exclude-file but ignores casing of filenames in patterns", Repeatable: true}, + {Name: "ignore-ctime", Alias: "", Default: "false", Description: "ignore ctime changes when checking for modified files", Repeatable: false}, + {Name: "ignore-inode", Alias: "", Default: "false", Description: "ignore inode number and ctime changes when checking for modified files", Repeatable: false}, + {Name: "no-scan", Alias: "", Default: "false", Description: "do not run scanner to estimate size of backup", Repeatable: false}, + {Name: "one-file-system", Alias: "x", Default: "false", Description: "exclude other file systems, don't cross filesystem boundaries and subvolumes", Repeatable: false}, + {Name: "parent", Alias: "", Default: "", Description: "use this parent snapshot (default: latest snapshot in the group determined by --group-by and not newer than the timestamp determined by --time)", Repeatable: false}, + {Name: "read-concurrency", Alias: "", Default: "0", Description: "read n files concurrently (default: $RESTIC_READ_CONCURRENCY or 2)", Repeatable: false}, + {Name: "skip-if-unchanged", Alias: "", Default: "false", Description: "skip snapshot creation if identical to parent snapshot", Repeatable: false}, + {Name: "stdin", Alias: "", Default: "false", Description: "read backup from stdin", Repeatable: false}, + {Name: "stdin-filename", Alias: "", Default: "stdin", Description: "filename to use when reading from stdin", Repeatable: false}, + {Name: "stdin-from-command", Alias: "", Default: "false", Description: "interpret arguments as command to execute and store its stdout", Repeatable: false}, + {Name: "tag", Alias: "", Default: "", Description: "add tags for the new snapshot in the format tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + {Name: "time", Alias: "", Default: "", Description: "time of the backup (ex. '2012-11-01 22:08:41') (default: now)", Repeatable: false}, + {Name: "with-atime", Alias: "", Default: "false", Description: "store the atime for all files and directories", Repeatable: false}, + }, + }, + "cache": { + Name: "cache", + Description: "Operate on local cache directories", + Options: []Option{ + {Name: "cleanup", Alias: "", Default: "false", Description: "remove old cache directories", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for cache", Repeatable: false}, + {Name: "max-age", Alias: "", Default: "30", Description: "max age in days for cache directories to be considered old", Repeatable: false}, + {Name: "no-size", Alias: "", Default: "false", Description: "do not output the size of the cache directories", Repeatable: false}, + }, + }, + "cat": { + Name: "cat", + Description: "Print internal objects to stdout", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for cat", Repeatable: false}, + }, + }, + "check": { + Name: "check", + Description: "Check the repository for errors", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for check", Repeatable: false}, + {Name: "read-data", Alias: "", Default: "false", Description: "read all data blobs", Repeatable: false}, + {Name: "read-data-subset", Alias: "", Default: "", Description: "read a subset of data packs, specified as 'n/t' for specific part, or either 'x%' or 'x.y%' or a size in bytes with suffixes k/K, m/M, g/G, t/T for a random subset", Repeatable: false}, + {Name: "with-cache", Alias: "", Default: "false", Description: "use existing cache, only read uncached data from repository", Repeatable: false}, + }, + }, + "copy": { + Name: "copy", + Description: "Copy snapshots from one repository to another", + Options: []Option{ + {Name: "from-insecure-no-password", Alias: "", Default: "false", Description: "use an empty password for the source repository (insecure)", Repeatable: false}, + {Name: "from-key-hint", Alias: "", Default: "", Description: "key ID of key to try decrypting the source repository first (default: $RESTIC_FROM_KEY_HINT)", Repeatable: false}, + {Name: "from-password-command", Alias: "", Default: "", Description: "shell command to obtain the source repository password from (default: $RESTIC_FROM_PASSWORD_COMMAND)", Repeatable: false}, + {Name: "from-password-file", Alias: "", Default: "", Description: "file to read the source repository password from (default: $RESTIC_FROM_PASSWORD_FILE)", Repeatable: false}, + {Name: "from-repo", Alias: "", Default: "", Description: "source repository to copy snapshots from (default: $RESTIC_FROM_REPOSITORY)", Repeatable: false}, + {Name: "from-repository-file", Alias: "", Default: "", Description: "file from which to read the source repository location to copy snapshots from (default: $RESTIC_FROM_REPOSITORY_FILE)", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for copy", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + }, + }, + "diff": { + Name: "diff", + Description: "Show differences between two snapshots", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for diff", Repeatable: false}, + {Name: "metadata", Alias: "", Default: "false", Description: "print changes in metadata", Repeatable: false}, + }, + }, + "dump": { + Name: "dump", + Description: "Print a backed-up file to stdout", + Options: []Option{ + {Name: "archive", Alias: "a", Default: "tar", Description: "set archive format as \"tar\" or \"zip\"", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for dump", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host, when snapshot ID \"latest\" is given (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path, when snapshot ID \"latest\" is given (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...], when snapshot ID \"latest\" is given (can be specified multiple times)", Repeatable: true}, + {Name: "target", Alias: "t", Default: "", Description: "write the output to target path", Repeatable: false}, + }, + }, + "features": { + Name: "features", + Description: "Print list of feature flags", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for features", Repeatable: false}, + }, + }, + "find": { + Name: "find", + Description: "Find a file, a directory or restic IDs", + Options: []Option{ + {Name: "blob", Alias: "", Default: "false", Description: "pattern is a blob-ID", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for find", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "human-readable", Alias: "", Default: "false", Description: "print sizes in human readable format", Repeatable: false}, + {Name: "ignore-case", Alias: "i", Default: "false", Description: "ignore case for pattern", Repeatable: false}, + {Name: "long", Alias: "l", Default: "false", Description: "use a long listing format showing size and mode", Repeatable: false}, + {Name: "newest", Alias: "N", Default: "", Description: "newest modification date/time", Repeatable: false}, + {Name: "oldest", Alias: "O", Default: "", Description: "oldest modification date/time", Repeatable: false}, + {Name: "pack", Alias: "", Default: "false", Description: "pattern is a pack-ID", Repeatable: false}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "reverse", Alias: "R", Default: "false", Description: "reverse sort order oldest to newest", Repeatable: false}, + {Name: "show-pack-id", Alias: "", Default: "false", Description: "display the pack-ID the blobs belong to (with --blob or --tree)", Repeatable: false}, + {Name: "snapshot", Alias: "s", Default: "", Description: "snapshot id to search in (can be given multiple times)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + {Name: "tree", Alias: "", Default: "false", Description: "pattern is a tree-ID", Repeatable: false}, + }, + }, + "forget": { + Name: "forget", + Description: "Remove snapshots from the repository", + Options: []Option{ + {Name: "keep-last", Alias: "l", Default: "0", Description: "keep the last n snapshots (use 'unlimited' to keep all snapshots)", Repeatable: false}, + {Name: "keep-hourly", Alias: "H", Default: "0", Description: "keep the last n hourly snapshots (use 'unlimited' to keep all hourly snapshots)", Repeatable: false}, + {Name: "keep-daily", Alias: "d", Default: "0", Description: "keep the last n daily snapshots (use 'unlimited' to keep all daily snapshots)", Repeatable: false}, + {Name: "keep-weekly", Alias: "w", Default: "0", Description: "keep the last n weekly snapshots (use 'unlimited' to keep all weekly snapshots)", Repeatable: false}, + {Name: "keep-monthly", Alias: "m", Default: "0", Description: "keep the last n monthly snapshots (use 'unlimited' to keep all monthly snapshots)", Repeatable: false}, + {Name: "keep-yearly", Alias: "y", Default: "0", Description: "keep the last n yearly snapshots (use 'unlimited' to keep all yearly snapshots)", Repeatable: false}, + {Name: "keep-within", Alias: "", Default: "", Description: "keep snapshots that are newer than duration (eg. 1y5m7d2h) relative to the latest snapshot", Repeatable: false}, + {Name: "keep-within-hourly", Alias: "", Default: "", Description: "keep hourly snapshots that are newer than duration (eg. 1y5m7d2h) relative to the latest snapshot", Repeatable: false}, + {Name: "keep-within-daily", Alias: "", Default: "", Description: "keep daily snapshots that are newer than duration (eg. 1y5m7d2h) relative to the latest snapshot", Repeatable: false}, + {Name: "keep-within-weekly", Alias: "", Default: "", Description: "keep weekly snapshots that are newer than duration (eg. 1y5m7d2h) relative to the latest snapshot", Repeatable: false}, + {Name: "keep-within-monthly", Alias: "", Default: "", Description: "keep monthly snapshots that are newer than duration (eg. 1y5m7d2h) relative to the latest snapshot", Repeatable: false}, + {Name: "keep-within-yearly", Alias: "", Default: "", Description: "keep yearly snapshots that are newer than duration (eg. 1y5m7d2h) relative to the latest snapshot", Repeatable: false}, + {Name: "keep-tag", Alias: "", Default: "", Description: "keep snapshots with this taglist (can be specified multiple times)", Repeatable: true}, + {Name: "unsafe-allow-remove-all", Alias: "", Default: "false", Description: "allow deleting all snapshots of a snapshot group", Repeatable: false}, + {Name: "host", Alias: "", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "compact", Alias: "c", Default: "false", Description: "use compact output format", Repeatable: false}, + {Name: "group-by", Alias: "g", Default: "host,paths", Description: "group snapshots by host, paths and/or tags, separated by comma (disable grouping with '')", Repeatable: false}, + {Name: "dry-run", Alias: "n", Default: "false", Description: "do not delete anything, just print what would be done", Repeatable: false}, + {Name: "prune", Alias: "", Default: "false", Description: "automatically run the 'prune' command if snapshots have been removed", Repeatable: false}, + {Name: "max-unused", Alias: "", Default: "5%", Description: "tolerate given limit of unused data (absolute value in bytes with suffixes k/K, m/M, g/G, t/T, a value in % or the word 'unlimited')", Repeatable: false}, + {Name: "max-repack-size", Alias: "", Default: "", Description: "stop after repacking this much data in total (allowed suffixes for size: k/K, m/M, g/G, t/T)", Repeatable: false}, + {Name: "repack-cacheable-only", Alias: "", Default: "false", Description: "only repack packs which are cacheable", Repeatable: false}, + {Name: "repack-small", Alias: "", Default: "false", Description: "repack pack files below 80% of target pack size", Repeatable: false}, + {Name: "repack-uncompressed", Alias: "", Default: "false", Description: "repack all uncompressed data", Repeatable: false}, + {Name: "repack-smaller-than", Alias: "", Default: "", Description: "pack below-limit packfiles (allowed suffixes: k/K, m/M)", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for forget", Repeatable: false}, + }, + }, + "generate": { + Name: "generate", + Description: "Generate manual pages and auto-completion files (bash, fish, zsh, powershell)", + Options: []Option{ + {Name: "bash-completion", Alias: "", Default: "", Description: "write bash completion file (- for stdout)", Repeatable: false}, + {Name: "fish-completion", Alias: "", Default: "", Description: "write fish completion file (- for stdout)", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for generate", Repeatable: false}, + {Name: "man", Alias: "", Default: "", Description: "write man pages to directory", Repeatable: false}, + {Name: "powershell-completion", Alias: "", Default: "", Description: "write powershell completion file (- for stdout)", Repeatable: false}, + {Name: "zsh-completion", Alias: "", Default: "", Description: "write zsh completion file (- for stdout)", Repeatable: false}, + }, + }, + "global": { + Name: "global", + Description: "Backup and restore files", + Options: []Option{ + {Name: "cacert", Alias: "", Default: "", Description: "file to load root certificates from (default: use system certificates or $RESTIC_CACERT)", Repeatable: true}, + {Name: "cache-dir", Alias: "", Default: "", Description: "set the cache directory. (default: use system default cache directory)", Repeatable: false}, + {Name: "cleanup-cache", Alias: "", Default: "false", Description: "auto remove old cache directories", Repeatable: false}, + {Name: "compression", Alias: "", Default: "auto", Description: "compression mode (only available for repository format version 2), one of (auto|off|max) (default: $RESTIC_COMPRESSION)", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for restic", Repeatable: false}, + {Name: "http-user-agent", Alias: "", Default: "", Description: "set a http user agent for outgoing http requests", Repeatable: false}, + {Name: "insecure-no-password", Alias: "", Default: "false", Description: "use an empty password for the repository, must be passed to every restic command (insecure)", Repeatable: false}, + {Name: "insecure-tls", Alias: "", Default: "false", Description: "skip TLS certificate verification when connecting to the repository (insecure)", Repeatable: false}, + {Name: "json", Alias: "", Default: "false", Description: "set output mode to JSON for commands that support it", Repeatable: false}, + {Name: "key-hint", Alias: "", Default: "", Description: "key ID of key to try decrypting first (default: $RESTIC_KEY_HINT)", Repeatable: false}, + {Name: "limit-download", Alias: "", Default: "0", Description: "limits downloads to a maximum rate in KiB/s. (default: unlimited)", Repeatable: false}, + {Name: "limit-upload", Alias: "", Default: "0", Description: "limits uploads to a maximum rate in KiB/s. (default: unlimited)", Repeatable: false}, + {Name: "no-cache", Alias: "", Default: "false", Description: "do not use a local cache", Repeatable: false}, + {Name: "no-extra-verify", Alias: "", Default: "false", Description: "skip additional verification of data before upload (see documentation)", Repeatable: false}, + {Name: "no-lock", Alias: "", Default: "false", Description: "do not lock the repository, this allows some operations on read-only repositories", Repeatable: false}, + {Name: "option", Alias: "o", Default: "", Description: "set extended option (key=value, can be specified multiple times)", Repeatable: true}, + {Name: "pack-size", Alias: "", Default: "0", Description: "set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)", Repeatable: false}, + {Name: "password-command", Alias: "", Default: "", Description: "shell command to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)", Repeatable: false}, + {Name: "password-file", Alias: "p", Default: "", Description: "file to read the repository password from (default: $RESTIC_PASSWORD_FILE)", Repeatable: false}, + {Name: "quiet", Alias: "q", Default: "false", Description: "do not output comprehensive progress report", Repeatable: false}, + {Name: "repo", Alias: "r", Default: "", Description: "repository to backup to or restore from (default: $RESTIC_REPOSITORY)", Repeatable: false}, + {Name: "repository-file", Alias: "", Default: "", Description: "file to read the repository location from (default: $RESTIC_REPOSITORY_FILE)", Repeatable: false}, + {Name: "retry-lock", Alias: "", Default: "0s", Description: "retry to lock the repository if it is already locked, takes a value like 5m or 2h (default: no retries)", Repeatable: false}, + {Name: "stuck-request-timeout", Alias: "", Default: "5m0s", Description: "duration after which to retry stuck requests", Repeatable: false}, + {Name: "tls-client-cert", Alias: "", Default: "", Description: "path to a file containing PEM encoded TLS client certificate and private key (default: $RESTIC_TLS_CLIENT_CERT)", Repeatable: false}, + {Name: "verbose", Alias: "v", Default: "0", Description: "be verbose (specify multiple times or a level using --verbose=n, max level/times is 2)", Repeatable: false}, + }, + }, + "init": { + Name: "init", + Description: "Initialize a new repository", + Options: []Option{ + {Name: "copy-chunker-params", Alias: "", Default: "false", Description: "copy chunker parameters from the secondary repository (useful with the copy command)", Repeatable: false}, + {Name: "from-insecure-no-password", Alias: "", Default: "false", Description: "use an empty password for the source repository (insecure)", Repeatable: false}, + {Name: "from-key-hint", Alias: "", Default: "", Description: "key ID of key to try decrypting the source repository first (default: $RESTIC_FROM_KEY_HINT)", Repeatable: false}, + {Name: "from-password-command", Alias: "", Default: "", Description: "shell command to obtain the source repository password from (default: $RESTIC_FROM_PASSWORD_COMMAND)", Repeatable: false}, + {Name: "from-password-file", Alias: "", Default: "", Description: "file to read the source repository password from (default: $RESTIC_FROM_PASSWORD_FILE)", Repeatable: false}, + {Name: "from-repo", Alias: "", Default: "", Description: "source repository to copy chunker parameters from (default: $RESTIC_FROM_REPOSITORY)", Repeatable: false}, + {Name: "from-repository-file", Alias: "", Default: "", Description: "file from which to read the source repository location to copy chunker parameters from (default: $RESTIC_FROM_REPOSITORY_FILE)", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for init", Repeatable: false}, + {Name: "repository-version", Alias: "", Default: "stable", Description: "repository format version to use, allowed values are a format version, 'latest' and 'stable'", Repeatable: false}, + }, + }, + "key": { + Name: "key", + Description: "Manage keys (passwords)", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for key", Repeatable: false}, + }, + }, + "key-add": { + Name: "key-add", + Description: "Add a new key (password) to the repository; returns the new key ID", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for add", Repeatable: false}, + {Name: "host", Alias: "", Default: "", Description: "the hostname for new key", Repeatable: false}, + {Name: "new-insecure-no-password", Alias: "", Default: "false", Description: "add an empty password for the repository (insecure)", Repeatable: false}, + {Name: "new-password-file", Alias: "", Default: "", Description: "file from which to read the new password", Repeatable: false}, + {Name: "user", Alias: "", Default: "", Description: "the username for new key", Repeatable: false}, + }, + }, + "key-list": { + Name: "key-list", + Description: "List keys (passwords)", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for list", Repeatable: false}, + }, + }, + "key-passwd": { + Name: "key-passwd", + Description: "Change key (password); creates a new key ID and removes the old key ID, returns new key ID", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for passwd", Repeatable: false}, + {Name: "host", Alias: "", Default: "", Description: "the hostname for new key", Repeatable: false}, + {Name: "new-insecure-no-password", Alias: "", Default: "false", Description: "add an empty password for the repository (insecure)", Repeatable: false}, + {Name: "new-password-file", Alias: "", Default: "", Description: "file from which to read the new password", Repeatable: false}, + {Name: "user", Alias: "", Default: "", Description: "the username for new key", Repeatable: false}, + }, + }, + "key-remove": { + Name: "key-remove", + Description: "Remove key ID (password) from the repository.", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for remove", Repeatable: false}, + }, + }, + "list": { + Name: "list", + Description: "List objects in the repository", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for list", Repeatable: false}, + }, + }, + "ls": { + Name: "ls", + Description: "List files in a snapshot", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for ls", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host, when snapshot ID \"latest\" is given (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "human-readable", Alias: "", Default: "false", Description: "print sizes in human readable format", Repeatable: false}, + {Name: "long", Alias: "l", Default: "false", Description: "use a long listing format showing size and mode", Repeatable: false}, + {Name: "ncdu", Alias: "", Default: "false", Description: "output NCDU export format (pipe into 'ncdu -f -')", Repeatable: false}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path, when snapshot ID \"latest\" is given (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "recursive", Alias: "", Default: "false", Description: "include files in subfolders of the listed directories", Repeatable: false}, + {Name: "reverse", Alias: "", Default: "false", Description: "reverse sorted output", Repeatable: false}, + {Name: "sort", Alias: "s", Default: "name", Description: "sort output by (name|size|time=mtime|atime|ctime|extension)", Repeatable: false}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...], when snapshot ID \"latest\" is given (can be specified multiple times)", Repeatable: true}, + }, + }, + "migrate": { + Name: "migrate", + Description: "Apply migrations", + Options: []Option{ + {Name: "force", Alias: "f", Default: "false", Description: "apply a migration a second time", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for migrate", Repeatable: false}, + }, + }, + "mount": { + Name: "mount", + Description: "Mount the repository", + Options: []Option{ + {Name: "allow-other", Alias: "", Default: "false", Description: "allow other users to access the data in the mounted directory", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for mount", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "no-default-permissions", Alias: "", Default: "false", Description: "for 'allow-other', ignore Unix permissions and allow users to read all snapshot files", Repeatable: false}, + {Name: "owner-root", Alias: "", Default: "false", Description: "use 'root' as the owner of files and dirs", Repeatable: false}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "path-template", Alias: "", Default: "", Description: "set template for path names (can be specified multiple times)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + {Name: "time-template", Alias: "", Default: "2006-01-02T15:04:05Z07:00", Description: "set template to use for times", Repeatable: false}, + }, + }, + "options": { + Name: "options", + Description: "Print list of extended options", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for options", Repeatable: false}, + }, + }, + "prune": { + Name: "prune", + Description: "Remove unneeded data from the repository", + Options: []Option{ + {Name: "dry-run", Alias: "n", Default: "false", Description: "do not modify the repository, just print what would be done", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for prune", Repeatable: false}, + {Name: "max-repack-size", Alias: "", Default: "", Description: "stop after repacking this much data in total (allowed suffixes for size: k/K, m/M, g/G, t/T)", Repeatable: false}, + {Name: "max-unused", Alias: "", Default: "5%", Description: "tolerate given limit of unused data (absolute value in bytes with suffixes k/K, m/M, g/G, t/T, a value in % or the word 'unlimited')", Repeatable: false}, + {Name: "repack-cacheable-only", Alias: "", Default: "false", Description: "only repack packs which are cacheable", Repeatable: false}, + {Name: "repack-small", Alias: "", Default: "false", Description: "repack pack files below 80% of target pack size", Repeatable: false}, + {Name: "repack-smaller-than", Alias: "", Default: "", Description: "pack below-limit packfiles (allowed suffixes: k/K, m/M)", Repeatable: false}, + {Name: "repack-uncompressed", Alias: "", Default: "false", Description: "repack all uncompressed data", Repeatable: false}, + {Name: "unsafe-recover-no-free-space", Alias: "", Default: "", Description: "UNSAFE, READ THE DOCUMENTATION BEFORE USING! Try to recover a repository stuck with no free space. Do not use without trying out 'prune --max-repack-size 0' first.", Repeatable: false}, + }, + }, + "recover": { + Name: "recover", + Description: "Recover data from the repository not referenced by snapshots", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for recover", Repeatable: false}, + }, + }, + "repair": { + Name: "repair", + Description: "Repair the repository", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for repair", Repeatable: false}, + }, + }, + "repair-index": { + Name: "repair-index", + Description: "Build a new index", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for index", Repeatable: false}, + {Name: "read-all-packs", Alias: "", Default: "false", Description: "read all pack files to generate new index from scratch", Repeatable: false}, + }, + }, + "repair-packs": { + Name: "repair-packs", + Description: "Salvage damaged pack files", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for packs", Repeatable: false}, + }, + }, + "repair-snapshots": { + Name: "repair-snapshots", + Description: "Repair snapshots", + Options: []Option{ + {Name: "dry-run", Alias: "n", Default: "false", Description: "do not do anything, just print what would be done", Repeatable: false}, + {Name: "forget", Alias: "", Default: "false", Description: "remove original snapshots after creating new ones", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for snapshots", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + }, + }, + "restore": { + Name: "restore", + Description: "Extract the data from a snapshot", + Options: []Option{ + {Name: "delete", Alias: "", Default: "false", Description: "delete files from target directory if they do not exist in snapshot. Use '--dry-run -vv' to check what would be deleted", Repeatable: false}, + {Name: "dry-run", Alias: "", Default: "false", Description: "do not write any data, just show what would be done", Repeatable: false}, + {Name: "exclude", Alias: "e", Default: "", Description: "exclude a pattern (can be specified multiple times)", Repeatable: true}, + {Name: "exclude-file", Alias: "", Default: "", Description: "read exclude patterns from a file (can be specified multiple times)", Repeatable: true}, + {Name: "exclude-xattr", Alias: "", Default: "", Description: "exclude xattr by pattern (can be specified multiple times)", Repeatable: true}, + {Name: "help", Alias: "h", Default: "false", Description: "help for restore", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host, when snapshot ID \"latest\" is given (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "iexclude", Alias: "", Default: "", Description: "same as --exclude pattern but ignores the casing of filenames", Repeatable: true}, + {Name: "iexclude-file", Alias: "", Default: "", Description: "same as --exclude-file but ignores casing of filenames in patterns", Repeatable: true}, + {Name: "iinclude", Alias: "", Default: "", Description: "same as --include pattern but ignores the casing of filenames", Repeatable: true}, + {Name: "iinclude-file", Alias: "", Default: "", Description: "same as --include-file but ignores casing of filenames in patterns", Repeatable: true}, + {Name: "include", Alias: "i", Default: "", Description: "include a pattern (can be specified multiple times)", Repeatable: true}, + {Name: "include-file", Alias: "", Default: "", Description: "read include patterns from a file (can be specified multiple times)", Repeatable: true}, + {Name: "include-xattr", Alias: "", Default: "", Description: "include xattr by pattern (can be specified multiple times)", Repeatable: true}, + {Name: "overwrite", Alias: "", Default: "always", Description: "overwrite behavior, one of (always|if-changed|if-newer|never)", Repeatable: false}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path, when snapshot ID \"latest\" is given (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "sparse", Alias: "", Default: "false", Description: "restore files as sparse", Repeatable: false}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...], when snapshot ID \"latest\" is given (can be specified multiple times)", Repeatable: true}, + {Name: "target", Alias: "t", Default: "", Description: "directory to extract data to", Repeatable: false}, + {Name: "verify", Alias: "", Default: "false", Description: "verify restored files content", Repeatable: false}, + }, + }, + "rewrite": { + Name: "rewrite", + Description: "Rewrite snapshots to exclude unwanted files", + Options: []Option{ + {Name: "dry-run", Alias: "n", Default: "false", Description: "do not do anything, just print what would be done", Repeatable: false}, + {Name: "exclude", Alias: "e", Default: "", Description: "exclude a pattern (can be specified multiple times)", Repeatable: true}, + {Name: "exclude-file", Alias: "", Default: "", Description: "read exclude patterns from a file (can be specified multiple times)", Repeatable: true}, + {Name: "forget", Alias: "", Default: "false", Description: "remove original snapshots after creating new ones", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for rewrite", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "iexclude", Alias: "", Default: "", Description: "same as --exclude pattern but ignores the casing of filenames", Repeatable: true}, + {Name: "iexclude-file", Alias: "", Default: "", Description: "same as --exclude-file but ignores casing of filenames in patterns", Repeatable: true}, + {Name: "new-host", Alias: "", Default: "", Description: "replace hostname", Repeatable: false}, + {Name: "new-time", Alias: "", Default: "", Description: "replace time of the backup", Repeatable: false}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "snapshot-summary", Alias: "s", Default: "false", Description: "create snapshot summary record if it does not exist", Repeatable: false}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + }, + }, + "snapshots": { + Name: "snapshots", + Description: "List all snapshots", + Options: []Option{ + {Name: "compact", Alias: "c", Default: "false", Description: "use compact output format", Repeatable: false}, + {Name: "group-by", Alias: "g", Default: "", Description: "group snapshots by host, paths and/or tags, separated by comma", Repeatable: false}, + {Name: "help", Alias: "h", Default: "false", Description: "help for snapshots", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "latest", Alias: "", Default: "0", Description: "only show the last n snapshots for each host and path", Repeatable: false}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + }, + }, + "stats": { + Name: "stats", + Description: "Scan the repository and show basic statistics", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for stats", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "mode", Alias: "", Default: "restore-size", Description: "counting mode: restore-size (default), files-by-contents, blobs-per-file or raw-data", Repeatable: false}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + }, + }, + "tag": { + Name: "tag", + Description: "Modify tags on snapshots", + Options: []Option{ + {Name: "add", Alias: "", Default: "", Description: "tags which will be added to the existing tags in the format tag[,tag,...] (can be given multiple times)", Repeatable: true}, + {Name: "help", Alias: "h", Default: "false", Description: "help for tag", Repeatable: false}, + {Name: "host", Alias: "H", Default: "", Description: "only consider snapshots for this host (can be specified multiple times) (default: $RESTIC_HOST)", Repeatable: true}, + {Name: "path", Alias: "", Default: "", Description: "only consider snapshots including this (absolute) path (can be specified multiple times, snapshots must include all specified paths)", Repeatable: true}, + {Name: "remove", Alias: "", Default: "", Description: "tags which will be removed from the existing tags in the format tag[,tag,...] (can be given multiple times)", Repeatable: true}, + {Name: "set", Alias: "", Default: "", Description: "tags which will replace the existing tags in the format tag[,tag,...] (can be given multiple times)", Repeatable: true}, + {Name: "tag", Alias: "", Default: "", Description: "only consider snapshots including tag[,tag,...] (can be specified multiple times)", Repeatable: true}, + }, + }, + "unlock": { + Name: "unlock", + Description: "Remove locks other processes created", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for unlock", Repeatable: false}, + {Name: "remove-all", Alias: "", Default: "false", Description: "remove all locks, even non-stale ones", Repeatable: false}, + }, + }, + "version": { + Name: "version", + Description: "Print version information", + Options: []Option{ + {Name: "help", Alias: "h", Default: "false", Description: "help for version", Repeatable: false}, + }, + }, +} diff --git a/mise.toml b/mise.toml index 891c98ace9695e002be7cc3c73ff2ae08a5cca85..5b5322a6506ccf55c209588a29fa5d353d1ba3cc 100644 --- a/mise.toml +++ b/mise.toml @@ -49,5 +49,21 @@ else fi """ +[tasks.sync] +run = "go generate ./internal/restic/..." + +[tasks."sync:check"] +run = """ +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT +cp internal/restic/commands_gen.go "$tmpdir/before.go" 2>/dev/null || true +go generate ./internal/restic/... +if ! diff -q internal/restic/commands_gen.go "$tmpdir/before.go" >/dev/null 2>&1; then + diff -u "$tmpdir/before.go" internal/restic/commands_gen.go || true + echo "restic commands out of sync; run 'mise run sync'" + exit 1 +fi +""" + [tasks.check] depends = ["fmt:check", "vet", "lint", "vuln", "build", "test:quiet"]