git.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package git
  2. import (
  3. "bufio"
  4. "io"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "strings"
  9. "time"
  10. "github.com/gitleaks/go-gitdiff/gitdiff"
  11. "github.com/rs/zerolog/log"
  12. )
  13. // GitLog returns a channel of gitdiff.File objects from the
  14. // git log -p command for the given source.
  15. func GitLog(source string, logOpts string) (<-chan *gitdiff.File, error) {
  16. sourceClean := filepath.Clean(source)
  17. var cmd *exec.Cmd
  18. if logOpts != "" {
  19. args := []string{"-C", sourceClean, "log", "-p", "-U0"}
  20. args = append(args, strings.Split(logOpts, " ")...)
  21. cmd = exec.Command("git", args...)
  22. } else {
  23. cmd = exec.Command("git", "-C", sourceClean, "log", "-p", "-U0",
  24. "--full-history", "--all")
  25. }
  26. log.Debug().Msgf("executing: %s", cmd.String())
  27. stdout, err := cmd.StdoutPipe()
  28. if err != nil {
  29. return nil, err
  30. }
  31. stderr, err := cmd.StderrPipe()
  32. if err != nil {
  33. return nil, err
  34. }
  35. if err := cmd.Start(); err != nil {
  36. return nil, err
  37. }
  38. go listenForStdErr(stderr)
  39. // HACK: to avoid https://github.com/zricethezav/gitleaks/issues/722
  40. time.Sleep(50 * time.Millisecond)
  41. return gitdiff.Parse(stdout)
  42. }
  43. // GitDiff returns a channel of gitdiff.File objects from
  44. // the git diff command for the given source.
  45. func GitDiff(source string, staged bool) (<-chan *gitdiff.File, error) {
  46. sourceClean := filepath.Clean(source)
  47. var cmd *exec.Cmd
  48. cmd = exec.Command("git", "-C", sourceClean, "diff", "-U0", ".")
  49. if staged {
  50. cmd = exec.Command("git", "-C", sourceClean, "diff", "-U0",
  51. "--staged", ".")
  52. }
  53. log.Debug().Msgf("executing: %s", cmd.String())
  54. stdout, err := cmd.StdoutPipe()
  55. if err != nil {
  56. return nil, err
  57. }
  58. stderr, err := cmd.StderrPipe()
  59. if err != nil {
  60. return nil, err
  61. }
  62. if err := cmd.Start(); err != nil {
  63. return nil, err
  64. }
  65. go listenForStdErr(stderr)
  66. // HACK: to avoid https://github.com/zricethezav/gitleaks/issues/722
  67. time.Sleep(50 * time.Millisecond)
  68. return gitdiff.Parse(stdout)
  69. }
  70. // listenForStdErr listens for stderr output from git and prints it to stdout
  71. // then exits with exit code 1
  72. func listenForStdErr(stderr io.ReadCloser) {
  73. scanner := bufio.NewScanner(stderr)
  74. errEncountered := false
  75. for scanner.Scan() {
  76. // if git throws one of the following errors:
  77. //
  78. // exhaustive rename detection was skipped due to too many files.
  79. // you may want to set your diff.renameLimit variable to at least
  80. // (some large number) and retry the command.
  81. //
  82. // inexact rename detection was skipped due to too many files.
  83. // you may want to set your diff.renameLimit variable to at least
  84. // (some large number) and retry the command.
  85. //
  86. // we skip exiting the program as git log -p/git diff will continue
  87. // to send data to stdout and finish executing. This next bit of
  88. // code prevents gitleaks from stopping mid scan if this error is
  89. // encountered
  90. if strings.Contains(scanner.Text(),
  91. "exhaustive rename detection was skipped") ||
  92. strings.Contains(scanner.Text(),
  93. "inexact rename detection was skipped") ||
  94. strings.Contains(scanner.Text(),
  95. "you may want to set your diff.renameLimit") {
  96. log.Warn().Msg(scanner.Text())
  97. } else {
  98. log.Error().Msg(scanner.Text())
  99. errEncountered = true
  100. }
  101. }
  102. if errEncountered {
  103. os.Exit(1)
  104. }
  105. }