blob: 27a96bd40869145d5635643b6f06b1286b51238c [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"os"
"path/filepath"
"fuchsia.googlesource.com/jiri"
"fuchsia.googlesource.com/jiri/cmdline"
"fuchsia.googlesource.com/jiri/gitutil"
"fuchsia.googlesource.com/jiri/project"
)
var cmdGrep = &cmdline.Command{
Runner: jiri.RunnerFunc(runGrep),
Name: "grep",
Short: "Search across projects.",
Long: `
Run git grep across all projects.
`,
ArgsName: "<query> [--] [<pathspec>...]",
}
var grepFlags struct {
cwdRel bool
n bool
h bool
i bool
e string
l bool
L bool
w bool
}
func init() {
flags := &cmdGrep.Flags
flags.BoolVar(&grepFlags.n, "n", false, "Prefix the line number to matching lines")
flags.StringVar(&grepFlags.e, "e", "", "The next parameter is the pattern. This option has to be used for patterns starting with -")
flags.BoolVar(&grepFlags.h, "H", true, "Does nothing. Just makes this git grep compatible")
flags.BoolVar(&grepFlags.i, "i", false, "Ignore case differences between the patterns and the files")
flags.BoolVar(&grepFlags.l, "l", false, "Instead of showing every matched line, show only the names of files that contain matches")
flags.BoolVar(&grepFlags.w, "w", false, "Match the pattern only at word boundary")
flags.BoolVar(&grepFlags.l, "name-only", false, "same as -l")
flags.BoolVar(&grepFlags.l, "files-with-matches", false, "same as -l")
flags.BoolVar(&grepFlags.L, "L", false, "Instead of showing every matched line, show only the names of files that do not contain matches")
flags.BoolVar(&grepFlags.L, "files-without-match", false, "same as -L")
flags.BoolVar(&grepFlags.cwdRel, "cwd-rel", false, "Output paths relative to the current working directory (if available)")
}
func buildFlags() []string {
var args []string
if grepFlags.n {
args = append(args, "-n")
}
if grepFlags.e != "" {
args = append(args, "-e", grepFlags.e)
}
if grepFlags.i {
args = append(args, "-i")
}
if grepFlags.l {
args = append(args, "-l")
}
if grepFlags.L {
args = append(args, "-L")
}
if grepFlags.w {
args = append(args, "-w")
}
return args
}
func doGrep(jirix *jiri.X, args []string) ([]string, error) {
var pathSpecs []string
lenArgs := len(args)
if lenArgs > 0 {
for i, a := range os.Args {
if a == "--" {
pathSpecs = os.Args[i+1:]
break
}
}
// we will not find -- if user uses something like jiri grep -- a b,
// as flag.Parse() removes '--' in that case, so set args length
lenArgs = len(args) - len(pathSpecs)
for i, a := range args {
if a == "--" {
args = args[0:i]
// reset length
lenArgs = len(args)
break
}
}
}
if grepFlags.e != "" && lenArgs > 0 {
return nil, jirix.UsageErrorf("No additional argument allowed with flag -e")
} else if grepFlags.e == "" && lenArgs != 1 {
return nil, jirix.UsageErrorf("grep requires one argument")
}
projects, err := project.LocalProjects(jirix, project.FastScan)
if err != nil {
return nil, err
}
// TODO(ianloic): run in parallel rather than serially.
// TODO(ianloic): only run grep on projects under the cwd.
var results []string
flags := buildFlags()
if jirix.Color.Enabled() {
flags = append(flags, "--color=always")
}
query := ""
if lenArgs == 1 {
query = args[0]
}
cwd := jirix.Root
if grepFlags.cwdRel {
if wd, err := os.Getwd(); err == nil {
cwd = wd
}
}
for _, project := range projects {
relpath, err := filepath.Rel(cwd, project.Path)
if err != nil {
return nil, err
}
git := gitutil.New(jirix, gitutil.RootDirOpt(project.Path))
lines, err := git.Grep(query, pathSpecs, flags...)
if err != nil {
continue
}
for _, line := range lines {
// TODO(ianloic): higlight the project path part like `repo grep`.
results = append(results, relpath+"/"+line)
}
}
// TODO(ianloic): fail if all of the sub-greps fail
return results, nil
}
func runGrep(jirix *jiri.X, args []string) error {
lines, err := doGrep(jirix, args)
if err != nil {
return err
}
for _, line := range lines {
fmt.Println(line)
}
return nil
}