diff --git a/dev/devcam/devcam.go b/dev/devcam/devcam.go index 41a11c5c6..7cbd2a99b 100644 --- a/dev/devcam/devcam.go +++ b/dev/devcam/devcam.go @@ -26,6 +26,7 @@ import ( "os/signal" pathpkg "path" "path/filepath" + "runtime" "strconv" "strings" "syscall" @@ -153,9 +154,9 @@ func handleSignals(camliProc *os.Process) { func checkCamliSrcRoot() { args := flag.Args() // TODO(mpl): we should probably get rid of that limitation someday. - if len(args) > 0 && args[0] == "review" || + if len(args) > 0 && (args[0] == "review" || args[0] == "hook" || - args[0] == "fixv" { + args[0] == "fixv") { // exception for devcam review, which does its own check. return } @@ -172,6 +173,26 @@ func checkCamliSrcRoot() { camliSrcRoot = cwd } +func repoRoot() (string, error) { + dir, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("could not get current directory: %v", err) + } + rootlen := 1 + if runtime.GOOS == "windows" { + rootlen += len(filepath.VolumeName(dir)) + } + for { + if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil { + return dir, nil + } + if len(dir) == rootlen && dir[rootlen-1] == filepath.Separator { + return "", fmt.Errorf(".git not found. Rerun from within the Camlistore source tree.") + } + dir = filepath.Dir(dir) + } +} + func selfModTime() (time.Time, error) { var modTime time.Time devcamBin, err := osutil.SelfPath() diff --git a/dev/devcam/fixvendored.go b/dev/devcam/fixvendored.go index b56ae76f3..1ead3f947 100644 --- a/dev/devcam/fixvendored.go +++ b/dev/devcam/fixvendored.go @@ -59,7 +59,18 @@ func init() { } func (c *fixvCmd) Usage() { - fmt.Fprintf(cmdmain.Stderr, "Usage: devcam [globalopts] fixv [args...]\n") + cmdmain.Errorf("Usage: devcam [globalopts] fixv [args...]\n") +} + +func (c *fixvCmd) Describe() string { + return "Check, and optionally fix, import statements in vendored files." +} + +func (c *fixvCmd) Examples() []string { + return []string{ + "-w # automatically fix the imports in the vendored files from the git staging area", + "/foo/bar.go # assume /foo/bar.go is vendored, and check if it needs to have its import fixed", + } } func (c *fixvCmd) RunCommand(args []string) error { @@ -72,12 +83,19 @@ func (c *fixvCmd) run(args []string) (tofix []string, err error) { if len(args) != 0 { vendoredFiles = args } else { - repo := repoRoot() + repo, err := repoRoot() + if err != nil { + return nil, err + } if !strings.HasSuffix(repo, string(filepath.Separator)) { repo += string(filepath.Separator) } - vendoredFiles = addRoot(repo, filter(isVendored, nonBlankLines(cmdOutput("git", "diff-index", "--name-only", "--diff-filter=ACM", "--cached", "HEAD", "--")))) + out, err := cmdOutputDirErr(".", "git", "diff-index", "--name-only", "--diff-filter=ACM", "--cached", "HEAD", "--") + if err != nil { + return nil, err + } + vendoredFiles = addRoot(repo, filter(isVendored, nonBlankLines(out))) if len(vendoredFiles) == 0 { return nil, nil } @@ -92,7 +110,7 @@ func (c *fixvCmd) run(args []string) (tofix []string, err error) { continue } if !c.fix { - fmt.Fprintf(cmdmain.Stderr, "%v imports need fixing\n", filename) + cmdmain.Errorf("%v imports need fixing\n", filename) tofix = append(tofix, filename) continue } @@ -103,7 +121,7 @@ func (c *fixvCmd) run(args []string) (tofix []string, err error) { if err := ioutil.WriteFile(filename, data, 0600); err != nil { return nil, fmt.Errorf("failed to write modified file %v: %v", filename, err) } - fmt.Fprintf(cmdmain.Stderr, "%v imports now fixed\n", filename) + cmdmain.Errorf("%v imports now fixed\n", filename) } if !c.fix && len(tofix) > 0 { return tofix, errImportsNeedsFixing diff --git a/dev/devcam/hook.go b/dev/devcam/hook.go index c7ef0acda..9e6824e94 100644 --- a/dev/devcam/hook.go +++ b/dev/devcam/hook.go @@ -26,7 +26,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "sort" "strings" @@ -39,8 +38,12 @@ var hookFiles = []string{ } func (c *hookCmd) installHook() error { + root, err := repoRoot() + if err != nil { + return err + } for _, hookFile := range hookFiles { - filename := filepath.Join(repoRoot(), hookPath+hookFile) + filename := filepath.Join(root, hookPath+hookFile) hookContent := fmt.Sprintf(hookScript, hookFile) // If hook file exists, assume it is okay. _, err := os.Stat(filename) @@ -73,27 +76,32 @@ exec devcam hook %s "$@" type hookCmd struct { verbose bool - fix bool // disabled for now - debug bool } func init() { cmdmain.RegisterCommand("hook", func(flags *flag.FlagSet) cmdmain.CommandRunner { cmd := &hookCmd{} flags.BoolVar(&cmd.verbose, "verbose", false, "Be verbose.") - flags.BoolVar(&cmd.debug, "debug", false, "Arguments after the hook name are files that will be used as input to the hook, instead of the hook using the staging area.") // TODO(mpl): "-w" flag to run gofmt -w and devcam fixv -w. for now just print instruction. - // flags.BoolVar(&cmd.fix, "w", false, "Perform appropriate fixes, for hooks like pre-commit.") return cmd }) } -// TODO(mpl): more docs, examples. Also doc in website to tell ppl to use it. - func (c *hookCmd) Usage() { printf("Usage: devcam [globalopts] hook [[hook-name] [args...]]\n") } +func (c *hookCmd) Examples() []string { + return []string{ + "# install the hooks (if needed)", + "pre-commit # install the hooks (if needed), then run the pre-commit hook", + } +} + +func (c *hookCmd) Describe() string { + return "Install git hooks for Camlistore, and if given, run the hook given as argument. Currently available hooks are: " + strings.TrimSuffix(strings.Join(hookFiles, ", "), ",") + "." +} + func (c *hookCmd) RunCommand(args []string) error { if err := c.installHook(); err != nil { return err @@ -105,9 +113,8 @@ func (c *hookCmd) RunCommand(args []string) error { case "pre-commit": if err := c.hookPreCommit(args[1:]); err != nil { printf("You can override these checks with 'git commit --no-verify'\n") - // TODO(mpl): make sure that by exiting "early" we're not skipping some post-RunCommand - // stuff controlled by cmdmain.Main - os.Exit(1) + cmdmain.ExitWithFailure = true + return err } } return nil @@ -140,7 +147,7 @@ func (c *hookCmd) hookGofmt() error { files, err := c.runGofmt() if err != nil { - printf("gofmt reported errors:\n\t%v\n", strings.Replace(strings.TrimSpace(err.Error()), "\n", "\n\t", -1)) + printf("gofmt hook reported errors:\n\t%v\n", strings.Replace(strings.TrimSpace(err.Error()), "\n", "\n\t", -1)) return errors.New("gofmt errors") } if len(files) == 0 { @@ -167,7 +174,7 @@ func (c *hookCmd) hookTrailingSpace() error { func (c *hookCmd) hookVendoredImports(args []string) error { tofix, err := (&fixvCmd{ verbose: c.verbose, - fix: c.fix, + fix: false, }).run(args) if err != nil { if err == errImportsNeedsFixing { @@ -183,12 +190,19 @@ func (c *hookCmd) hookVendoredImports(args []string) error { // runGofmt runs the external gofmt command over the local version of staged files. // It returns the files that need gofmting. func (c *hookCmd) runGofmt() (files []string, err error) { - repo := repoRoot() + repo, err := repoRoot() + if err != nil { + return nil, err + } if !strings.HasSuffix(repo, string(filepath.Separator)) { repo += string(filepath.Separator) } - indexFiles := addRoot(repo, filter(gofmtRequired, nonBlankLines(cmdOutput("git", "diff-index", "--name-only", "--diff-filter=ACM", "--cached", "HEAD", "--")))) + out, err := cmdOutputDirErr(".", "git", "diff-index", "--name-only", "--diff-filter=ACM", "--cached", "HEAD", "--") + if err != nil { + return nil, err + } + indexFiles := addRoot(repo, filter(gofmtRequired, nonBlankLines(out))) if len(indexFiles) == 0 { return } @@ -225,32 +239,7 @@ func (c *hookCmd) runGofmt() (files []string, err error) { } func printf(format string, args ...interface{}) { - fmt.Fprintf(cmdmain.Stderr, format, args...) -} - -func dief(format string, args ...interface{}) { - printf(format, args...) - os.Exit(1) -} - -func repoRoot() string { - dir, err := os.Getwd() - if err != nil { - dief("could not get current directory: %v", err) - } - rootlen := 1 - if runtime.GOOS == "windows" { - rootlen += len(filepath.VolumeName(dir)) - } - for { - if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil { - return dir - } - if len(dir) == rootlen && dir[rootlen-1] == filepath.Separator { - dief(".git not found. Rerun from within the Camlistore source tree.") - } - dir = filepath.Dir(dir) - } + cmdmain.Errorf(format, args...) } func addRoot(root string, list []string) []string { @@ -315,23 +304,6 @@ func (c *hookCmd) verbosef(format string, args ...interface{}) { } } -// cmdOutput runs the command line, returning its output. -// If the command cannot be run or does not exit successfully, -// cmdOutput dies. -// -// NOTE: cmdOutput must be used only to run commands that read state, -// not for commands that make changes. Commands that make changes -// should be run using runDirErr so that the -v and -n flags apply to them. -func cmdOutput(command string, args ...string) string { - out, err := cmdOutputDirErr(".", command, args...) - if err != nil { - printf("%v\n", err) - // TODO(mpl): maybe not die. see other comment about cmdmain.Main. - os.Exit(1) - } - return out -} - // cmdOutputDirErr runs the command line in dir, returning its output // and any error results. //