package main import ( "bufio" "errors" "flag" "fmt" "io" "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 } 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 installFile(srcPath, dstPath string) error { fmt.Fprintf(os.Stderr, "cp %s %s && chmod +x %[2]s\n", srcPath, dstPath) src, err := os.Open(srcPath) if err != nil { return err } defer src.Close() dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { return err } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { return err } if err := dst.Close(); err != nil { return err } return nil } func install() error { const dir = "/nezuko/code/git-shell-commands" fmt.Fprintf(os.Stderr, "Install to %q? ", dir) scanner := bufio.NewScanner(os.Stdin) if !scanner.Scan() { return fmt.Errorf("exiting") } response := strings.TrimSpace(scanner.Text()) if !(response == "" || strings.EqualFold(response, "y") || strings.EqualFold(response, "yes")) { return fmt.Errorf("exiting") } selfPath := os.Args[0] if !strings.Contains(selfPath, "/") { var err error if selfPath, err = exec.LookPath(selfPath); err != nil { return err } } helpPath := filepath.Join(dir, "help") if err := installFile(selfPath, helpPath); err != nil { return err } for _, cmd := range []string{"create", "delete"} { cmdPath := filepath.Join(dir, cmd) fmt.Fprintf(os.Stderr, "ln -f %s %s\n", helpPath, cmdPath) if err := os.Remove(cmdPath); err != nil && !errors.Is(err, os.ErrNotExist) { return err } if err := os.Link(helpPath, cmdPath); 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() case "git-shell-commands": err = install() default: help() } if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }