Skip to content

Commit

Permalink
[clang-format] git-clang-format --staged should format the index
Browse files Browse the repository at this point in the history
When --staged (or --cached) use the index for formatting as well, not just
for the line numbers to format. Without this change git-clang-format gets
the changed line numbers based on the index, but then formats these lines on
the working tree version of the file.

This is a problem when the working tree and index differ. One common case
would be (and is the motivation behind this patch) when applying the
suggested changes git-clang-format --staged, then forgetting to add the
applied changes. When git-clang-format --staged --diff is used in a
pre-commit hook in this scenario, then the hook would allow committing the
improperly formatted changes, as the file is correctly formatted in the work
tree.

Fixes #56797.

Differential Revision: https://reviews.llvm.org/D130108
  • Loading branch information
Maetveis authored and owenca committed Aug 11, 2022
1 parent 05b3374 commit 3f801e0
Showing 1 changed file with 43 additions and 17 deletions.
60 changes: 43 additions & 17 deletions clang/tools/clang-format/git-clang-format
Expand Up @@ -179,15 +179,17 @@ def main():

if len(commits) > 1:
old_tree = commits[1]
new_tree = run_clang_format_and_save_to_tree(changed_lines,
revision=commits[1],
binary=opts.binary,
style=opts.style)
revision = old_tree
elif opts.staged:
old_tree = create_tree_from_index(changed_lines)
revision = ''
else:
old_tree = create_tree_from_workdir(changed_lines)
new_tree = run_clang_format_and_save_to_tree(changed_lines,
binary=opts.binary,
style=opts.style)
revision = None
new_tree = run_clang_format_and_save_to_tree(changed_lines,
revision,
binary=opts.binary,
style=opts.style)
if opts.verbose >= 1:
print('old tree: %s' % old_tree)
print('new tree: %s' % new_tree)
Expand Down Expand Up @@ -394,23 +396,45 @@ def create_tree_from_workdir(filenames):
return create_tree(filenames, '--stdin')


def create_tree_from_index(filenames):
# Copy the environment, because the files have to be read from the original
# index.
env = os.environ.copy()
def index_contents_generator():
for filename in filenames:
git_ls_files_cmd = ['git', 'ls-files', '--stage', '-z', '--', filename]
git_ls_files = subprocess.Popen(git_ls_files_cmd, env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
stdout = git_ls_files.communicate()[0]
yield convert_string(stdout.split(b'\0')[0])
return create_tree(index_contents_generator(), '--index-info')


def run_clang_format_and_save_to_tree(changed_lines, revision=None,
binary='clang-format', style=None):
"""Run clang-format on each file and save the result to a git tree.
Returns the object ID (SHA-1) of the created tree."""
# Copy the environment when formatting the files in the index, because the
# files have to be read from the original index.
env = os.environ.copy() if revision == '' else None
def iteritems(container):
try:
return container.iteritems() # Python 2
except AttributeError:
return container.items() # Python 3
def index_info_generator():
for filename, line_ranges in iteritems(changed_lines):
if revision:
git_metadata_cmd = ['git', 'ls-tree',
'%s:%s' % (revision, os.path.dirname(filename)),
os.path.basename(filename)]
git_metadata = subprocess.Popen(git_metadata_cmd, stdin=subprocess.PIPE,
if revision is not None:
if len(revision) > 0:
git_metadata_cmd = ['git', 'ls-tree',
'%s:%s' % (revision, os.path.dirname(filename)),
os.path.basename(filename)]
else:
git_metadata_cmd = ['git', 'ls-files', '--stage', '--', filename]
git_metadata = subprocess.Popen(git_metadata_cmd, env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
stdout = git_metadata.communicate()[0]
mode = oct(int(stdout.split()[0], 8))
Expand All @@ -422,7 +446,8 @@ def run_clang_format_and_save_to_tree(changed_lines, revision=None,
blob_id = clang_format_to_blob(filename, line_ranges,
revision=revision,
binary=binary,
style=style)
style=style,
env=env)
yield '%s %s\t%s' % (mode, blob_id, filename)
return create_tree(index_info_generator(), '--index-info')

Expand All @@ -448,11 +473,12 @@ def create_tree(input_lines, mode):


def clang_format_to_blob(filename, line_ranges, revision=None,
binary='clang-format', style=None):
binary='clang-format', style=None, env=None):
"""Run clang-format on the given file and save the result to a git blob.
Runs on the file in `revision` if not None, or on the file in the working
directory if `revision` is None.
directory if `revision` is None. Revision can be set to an empty string to run
clang-format on the file in the index.
Returns the object ID (SHA-1) of the created blob."""
clang_format_cmd = [binary]
Expand All @@ -461,10 +487,10 @@ def clang_format_to_blob(filename, line_ranges, revision=None,
clang_format_cmd.extend([
'-lines=%s:%s' % (start_line, start_line+line_count-1)
for start_line, line_count in line_ranges])
if revision:
if revision is not None:
clang_format_cmd.extend(['-assume-filename='+filename])
git_show_cmd = ['git', 'cat-file', 'blob', '%s:%s' % (revision, filename)]
git_show = subprocess.Popen(git_show_cmd, stdin=subprocess.PIPE,
git_show = subprocess.Popen(git_show_cmd, env=env, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
git_show.stdin.close()
clang_format_stdin = git_show.stdout
Expand Down

0 comments on commit 3f801e0

Please sign in to comment.