Index: tools/clang-format/git-clang-format =================================================================== --- tools/clang-format/git-clang-format +++ tools/clang-format/git-clang-format @@ -35,9 +35,9 @@ usage = 'git clang-format [OPTIONS] [] [--] [...]' desc = ''' -Run clang-format on all lines that differ between the working directory -and , which defaults to HEAD. Changes are only applied to the working -directory. +Run clang-format on all lines that differ between the working directory and + (which defaults to HEAD), or all lines that changed in a specific +commit. Changes are only applied to the working directory. The following git-config settings set the default of the corresponding option: clangFormat.binary @@ -90,6 +90,9 @@ p.add_argument('--commit', default=config.get('clangformat.commit', 'HEAD'), help='default commit to use if none is specified'), + p.add_argument('--single-commit', action='store_true', + help=('run clang-format on a single commit instead of against ' + 'the working tree')), p.add_argument('--diff', action='store_true', help='print a diff instead of applying the changes') p.add_argument('--extensions', @@ -121,7 +124,8 @@ del opts.quiet commit, files = interpret_args(opts.args, dash_dash, opts.commit) - changed_lines = compute_diff_and_extract_lines(commit, files) + changed_lines = compute_diff_and_extract_lines(commit, files, + opts.single_commit) if opts.verbose >= 1: ignored_files = set(changed_lines) filter_by_extension(changed_lines, opts.extensions.lower().split(',')) @@ -141,7 +145,10 @@ # The computed diff outputs absolute paths, so we must cd before accessing # those files. cd_to_toplevel() - old_tree = create_tree_from_workdir(changed_lines) + if opts.single_commit: + old_tree = create_tree_from_commit(commit, changed_lines) + 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) @@ -242,9 +249,9 @@ return stdout.strip() -def compute_diff_and_extract_lines(commit, files): +def compute_diff_and_extract_lines(commit, files, single_commit): """Calls compute_diff() followed by extract_lines().""" - diff_process = compute_diff(commit, files) + diff_process = compute_diff(commit, files, single_commit) changed_lines = extract_lines(diff_process.stdout) diff_process.stdout.close() diff_process.wait() @@ -254,13 +261,16 @@ return changed_lines -def compute_diff(commit, files): +def compute_diff(commit, files, single_commit): """Return a subprocess object producing the diff from `commit`. The return value's `stdin` file object will produce a patch with the differences between the working directory and `commit`, filtered on `files` (if non-empty). Zero context lines are used in the patch.""" - cmd = ['git', 'diff-index', '-p', '-U0', commit, '--'] + git_tool = 'diff-index' + if single_commit: + git_tool = 'diff-tree' + cmd = ['git', git_tool, '-p', '-U0', commit, '--'] cmd.extend(files) p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) p.stdin.close() @@ -310,6 +320,32 @@ os.chdir(toplevel) +def create_tree_from_commit(commit, filenames): + """Create a new git tree with the given files from `commit`. + + This reduces the size of the generated temporary index in case the project has + a large number of files. + + Returns the object ID (SHA-1) of the created tree.""" + def index_info_generator(lines, filenames): + for line in lines: + match = re.match(r'^(\d+) blob ([0-9a-f]+)\t(.*)', line) + if not match: + continue + mode, blob_id, filename = match.groups() + if filename not in filenames: + continue + yield '%s %s\t%s' % (mode, blob_id, filename) + cmd = ['git', 'ls-tree', '-r', commit] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + stdout = p.communicate()[0] + if p.returncode != 0: + die('`%s` failed' % ' '.join(cmd)) + + return create_tree(index_info_generator(stdout.splitlines(), set(filenames)), + '--index-info') + + def create_tree_from_workdir(filenames): """Create a new git tree with the given files from the working directory.