package main import ( "bufio" "errors" "flag" "fmt" "io/fs" "os" "os/exec" "path/filepath" "strings" ) const codeDir = "/nezuko/code/" func checkRepoName(name string) (string, error) { if strings.Contains(name, "/") { return "", fmt.Errorf("invalid name %q: contains slashes", name) } if !strings.HasSuffix(name, ".git") { name += ".git" } return name, nil } func createRepo(name string) error { dir := filepath.Join(codeDir, name) fmt.Fprintf(os.Stderr, "mkdir %s\n", dir) if err := os.Mkdir(dir, 0755); err != nil { return err } fmt.Fprintf(os.Stderr, "cd %s && git init --bare\n", dir) git := exec.Command("git", "init", "--bare") git.Dir = dir git.Stdout = os.Stdout git.Stderr = os.Stderr if err := git.Run(); err != nil { return err } hook := filepath.Join(dir, "hooks/post-update") hookSample := hook + ".sample" fmt.Fprintf(os.Stderr, "mv %s %s\n", hookSample, hook) if err := os.Rename(hookSample, hook); err != nil { return err } return nil } func create() error { flag.Usage = func() { fmt.Fprintf(os.Stderr, `Usage: create [REPO]... Create a new git repo named REPO. `) } flag.Parse() args := flag.Args() if len(args) == 0 { return fmt.Errorf("missing operand\nUse create -help for usage info") } names := make([]string, len(args)) var errs []error for i, arg := range args { var err error names[i], err = checkRepoName(arg) if err != nil { errs = append(errs, err) } } if len(errs) > 0 { return errors.Join(errs...) } failed := false for _, name := range names { if err := createRepo(name); err != nil { fmt.Fprintln(os.Stderr, err) failed = true } } if failed { return fmt.Errorf("failed to create repo") } return nil } func fileExists(path string) bool { _, err := os.Stat(path) return !errors.Is(err, fs.ErrNotExist) } func delete() error { flag.Usage = func() { fmt.Fprintf(os.Stderr, `Usage: delete [REPO] Delete the git repo named REPO. `) } flag.Parse() args := flag.Args() if len(args) == 0 { return fmt.Errorf("missing operand\nUse delete -help for usage info") } if len(args) > 1 { return fmt.Errorf("too many positional arguments\nUse delete -help for usage info") } name, err := checkRepoName(args[0]) if err != nil { return err } dir := filepath.Join(codeDir, name) shortName := strings.TrimSuffix(name, ".git") fmt.Fprintf(os.Stderr, "This will irreversibly delete repo %q.\nType %q to confirm: ", name, shortName) scanner := bufio.NewScanner(os.Stdin) if !scanner.Scan() || scanner.Text() != shortName { return fmt.Errorf("name does not match") } fmt.Fprintf(os.Stderr, "rm -rf %s\n", dir) if !fileExists(dir) { return fmt.Errorf("repo %q does not exist", name) } if err := os.RemoveAll(dir); err != nil { return err } return nil } func help() { fmt.Printf(`Available commands: create create a new git repo delete delete a git repo help print this message `) } func main() { var err error switch filepath.Base(os.Args[0]) { case "create": err = create() case "delete": err = delete() default: help() } if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }