git.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. # Copyright 2019 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import os
  15. import six
  16. import subprocess
  17. from lib import command_trace
  18. from lib import source
  19. def find_changed_or_files(all, rev_or_files, patterns):
  20. """Finds files.
  21. Args:
  22. all: Force finding all files.
  23. rev_or_files: A single revision, a list of files, or empty.
  24. patterns: A list of git matching patterns
  25. Returns:
  26. Files that match.
  27. If rev_or_files is a single revision, the result is all files that match
  28. the patterns that have changed since the revision.
  29. If rev_or_files is a list of files, the result is all the files that match
  30. that list of files. The files can be patterns.
  31. If rev_or_files is empty, the result is all the files that match patterns.
  32. """
  33. if all:
  34. return find_files(patterns)
  35. if not rev_or_files:
  36. return find_changed('origin/master', patterns)
  37. if len(rev_or_files) == 1 and is_revision(rev_or_files[0]):
  38. return find_changed(rev_or_files[0], patterns)
  39. else:
  40. return find_files(rev_or_files)
  41. def is_revision(word):
  42. """Returns true if the given word is a revision name according to git."""
  43. command = ['git', 'rev-parse', word, '--']
  44. with open(os.devnull, 'w') as dev_null:
  45. command_trace.log(command)
  46. rc = subprocess.call(command, stdout=dev_null, stderr=dev_null)
  47. return rc == 0
  48. def find_changed(revision, patterns):
  49. """Finds files changed since a revision."""
  50. # Always include -- indicate that revision is known to be a revision, even
  51. # if no patterns follow.
  52. command = ['git', 'diff', '-z', '--name-only', '--diff-filter=ACMR',
  53. revision, '--']
  54. command.extend(patterns)
  55. command.extend(standard_exclusions())
  56. return _null_split_output(command)
  57. def find_files(patterns=None):
  58. """Finds files matching the given patterns using git ls-files."""
  59. command = ['git', 'ls-files', '-z', '--']
  60. if patterns:
  61. command.extend(patterns)
  62. command.extend(standard_exclusions())
  63. return _null_split_output(command)
  64. def find_lines_matching(pattern, sources=None):
  65. command = [
  66. 'git', 'grep',
  67. '-n', # show line numbers
  68. '-I', # exclude binary files
  69. pattern,
  70. '--'
  71. ]
  72. if sources:
  73. command.extend(sources)
  74. command.extend(standard_exclusions())
  75. command_trace.log(command)
  76. bufsize = 4096
  77. proc = subprocess.Popen(command, bufsize=bufsize, stdout=subprocess.PIPE)
  78. result = []
  79. try:
  80. while proc.poll() is None:
  81. result.append(proc.stdout.read(bufsize))
  82. except KeyboardInterrupt:
  83. proc.terminate()
  84. proc.wait()
  85. return six.ensure_text(b''.join(result))
  86. def make_patterns(dirs):
  87. """Returns a list of git match patterns for the given directories."""
  88. return ['%s/**' % d for d in dirs]
  89. def make_exclusions(dirs):
  90. return [':(exclude)' + d for d in dirs]
  91. def standard_exclusions():
  92. result = make_exclusions(source.IGNORE)
  93. result.append(':(exclude)**/third_party/**')
  94. return result
  95. def is_within_repo():
  96. """Returns whether the current working directory is within a git repo."""
  97. try:
  98. subprocess.check_output(['git', 'status'])
  99. return True
  100. except subprocess.CalledProcessError:
  101. return False
  102. def get_repo_root():
  103. """Returns the absolute path to the root of the current git repo."""
  104. command = ['git', 'rev-parse', '--show-toplevel']
  105. return six.ensure_text(subprocess.check_output(command).rstrip())
  106. def _null_split_output(command):
  107. """Runs the given command and splits its output on the null byte."""
  108. command_trace.log(command)
  109. result = six.ensure_text(subprocess.check_output(command))
  110. return [name for name in result.rstrip().split('\0') if name]