14507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper#!/usr/bin/python 24507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# 34507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper#===- git-clang-format - ClangFormat Git Integration ---------*- python -*--===# 44507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# 54507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# The LLVM Compiler Infrastructure 64507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# 74507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# This file is distributed under the University of Illinois Open Source 84507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# License. See LICENSE.TXT for details. 94507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# 104507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper#===------------------------------------------------------------------------===# 114507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 124507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperr""" 134507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperclang-format git integration 144507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper============================ 154507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 164507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel JasperThis file provides a clang-format integration for git. Put it somewhere in your 174507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperpath and ensure that it is executable. Then, "git clang-format" will invoke 184507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperclang-format on the changes in current files or a specific commit. 194507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 204507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel JasperFor further details, run: 214507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jaspergit clang-format -h 224507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 234507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel JasperRequires Python 2.7 244507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper""" 254507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 264507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperimport argparse 274507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperimport collections 284507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperimport contextlib 294507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperimport errno 304507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperimport os 314507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperimport re 324507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperimport subprocess 334507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperimport sys 344507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 354507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperusage = 'git clang-format [OPTIONS] [<commit>] [--] [<file>...]' 364507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 374507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdesc = ''' 384507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel JasperRun clang-format on all lines that differ between the working directory 394507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperand <commit>, which defaults to HEAD. Changes are only applied to the working 404507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdirectory. 414507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 424507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel JasperThe following git-config settings set the default of the corresponding option: 434507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clangFormat.binary 444507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clangFormat.commit 454507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clangFormat.extension 464507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clangFormat.style 474507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper''' 484507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 494507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# Name of the temporary index file in which save the output of clang-format. 504507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper# This file is created within the .git directory. 514507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jaspertemp_index_basename = 'clang-format-index' 524507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 534507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 544507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel JasperRange = collections.namedtuple('Range', 'start, count') 554507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 564507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 574507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef main(): 584507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper config = load_git_config() 594507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 604507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # In order to keep '--' yet allow options after positionals, we need to 614507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # check for '--' ourselves. (Setting nargs='*' throws away the '--', while 624507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # nargs=argparse.REMAINDER disallows options after positionals.) 634507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper argv = sys.argv[1:] 644507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper try: 654507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper idx = argv.index('--') 664507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper except ValueError: 674507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper dash_dash = [] 684507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 694507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper dash_dash = argv[idx:] 704507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper argv = argv[:idx] 714507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 724507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper default_extensions = ','.join([ 734507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # From clang/lib/Frontend/FrontendOptions.cpp, all lower case 744507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 'c', 'h', # C 754507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 'm', # ObjC 764507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 'mm', # ObjC++ 774507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 'cc', 'cp', 'cpp', 'c++', 'cxx', 'hpp', # C++ 784507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper ]) 794507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 804507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p = argparse.ArgumentParser( 814507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper usage=usage, formatter_class=argparse.RawDescriptionHelpFormatter, 824507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper description=desc) 834507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('--binary', 844507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper default=config.get('clangformat.binary', 'clang-format'), 854507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='path to clang-format'), 864507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('--commit', 874507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper default=config.get('clangformat.commit', 'HEAD'), 884507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='default commit to use if none is specified'), 894507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('--diff', action='store_true', 904507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='print a diff instead of applying the changes') 914507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('--extensions', 924507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper default=config.get('clangformat.extensions', 934507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper default_extensions), 944507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help=('comma-separated list of file extensions to format, ' 954507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 'excluding the period and case-insensitive')), 964507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('-f', '--force', action='store_true', 974507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='allow changes to unstaged files') 984507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('-p', '--patch', action='store_true', 994507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='select hunks interactively') 1004507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('-q', '--quiet', action='count', default=0, 1014507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='print less information') 1024507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('--style', 1034507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper default=config.get('clangformat.style', None), 1044507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='passed to clang-format'), 1054507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('-v', '--verbose', action='count', default=0, 1064507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='print extra information') 1074507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # We gather all the remaining positional arguments into 'args' since we need 1084507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # to use some heuristics to determine whether or not <commit> was present. 1094507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # However, to print pretty messages, we make use of metavar and help. 1104507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('args', nargs='*', metavar='<commit>', 1114507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='revision from which to compute the diff') 1124507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.add_argument('ignored', nargs='*', metavar='<file>...', 1134507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper help='if specified, only consider differences in these files') 1144507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper opts = p.parse_args(argv) 1154507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1164507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper opts.verbose -= opts.quiet 1174507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper del opts.quiet 1184507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1194507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper commit, files = interpret_args(opts.args, dash_dash, opts.commit) 1204507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper changed_lines = compute_diff_and_extract_lines(commit, files) 1214507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if opts.verbose >= 1: 1224507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper ignored_files = set(changed_lines) 1234507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper filter_by_extension(changed_lines, opts.extensions.lower().split(',')) 1244507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if opts.verbose >= 1: 1254507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper ignored_files.difference_update(changed_lines) 1264507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if ignored_files: 1274507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print 'Ignoring changes in the following files (wrong extension):' 1284507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper for filename in ignored_files: 1294507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print ' ', filename 1304507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if changed_lines: 1314507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print 'Running clang-format on the following files:' 1324507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper for filename in changed_lines: 1334507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print ' ', filename 1344507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if not changed_lines: 1354507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print 'no modified files to format' 1364507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return 1374507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # The computed diff outputs absolute paths, so we must cd before accessing 1384507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # those files. 1394507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper cd_to_toplevel() 14065e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper old_tree = create_tree_from_workdir(changed_lines) 14165e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper new_tree = run_clang_format_and_save_to_tree(changed_lines, 1424507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper binary=opts.binary, 1434507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper style=opts.style) 1444507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if opts.verbose >= 1: 1454507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print 'old tree:', old_tree 1464507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print 'new tree:', new_tree 1474507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if old_tree == new_tree: 1484507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if opts.verbose >= 0: 1494507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print 'clang-format did not modify any files' 1504507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper elif opts.diff: 1514507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print_diff(old_tree, new_tree) 1524507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 1534507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper changed_files = apply_changes(old_tree, new_tree, force=opts.force, 1544507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper patch_mode=opts.patch) 1554507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1: 1564507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print 'changed files:' 1574507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper for filename in changed_files: 1584507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print ' ', filename 1594507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1604507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1614507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef load_git_config(non_string_options=None): 1624507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Return the git configuration as a dictionary. 1634507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1644507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper All options are assumed to be strings unless in `non_string_options`, in which 1654507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper is a dictionary mapping option name (in lower case) to either "--bool" or 1664507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper "--int".""" 1674507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if non_string_options is None: 1684507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper non_string_options = {} 1694507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper out = {} 1704507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper for entry in run('git', 'config', '--list', '--null').split('\0'): 1714507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if entry: 1724507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper name, value = entry.split('\n', 1) 1734507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if name in non_string_options: 1744507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper value = run('git', 'config', non_string_options[name], name) 1754507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper out[name] = value 1764507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return out 1774507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1784507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1794507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef interpret_args(args, dash_dash, default_commit): 1804507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Interpret `args` as "[commit] [--] [files...]" and return (commit, files). 1814507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1824507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper It is assumed that "--" and everything that follows has been removed from 1834507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper args and placed in `dash_dash`. 1844507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 1854507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper If "--" is present (i.e., `dash_dash` is non-empty), the argument to its 1864507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper left (if present) is taken as commit. Otherwise, the first argument is 1874507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper checked if it is a commit or a file. If commit is not given, 1884507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper `default_commit` is used.""" 1894507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if dash_dash: 1904507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if len(args) == 0: 1914507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper commit = default_commit 1924507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper elif len(args) > 1: 1934507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper die('at most one commit allowed; %d given' % len(args)) 1944507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 1954507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper commit = args[0] 1964507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper object_type = get_object_type(commit) 1974507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if object_type not in ('commit', 'tag'): 1984507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if object_type is None: 1994507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper die("'%s' is not a commit" % commit) 2004507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 2014507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper die("'%s' is a %s, but a commit was expected" % (commit, object_type)) 2024507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper files = dash_dash[1:] 2034507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper elif args: 2044507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if disambiguate_revision(args[0]): 2054507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper commit = args[0] 2064507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper files = args[1:] 2074507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 2084507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper commit = default_commit 2094507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper files = args 2104507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 2114507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper commit = default_commit 2124507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper files = [] 2134507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return commit, files 2144507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2154507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2164507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef disambiguate_revision(value): 2174507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Returns True if `value` is a revision, False if it is a file, or dies.""" 2184507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # If `value` is ambiguous (neither a commit nor a file), the following 2194507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # command will die with an appropriate error message. 2204507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper run('git', 'rev-parse', value, verbose=False) 2214507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper object_type = get_object_type(value) 2224507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if object_type is None: 2234507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return False 2244507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if object_type in ('commit', 'tag'): 2254507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return True 2264507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper die('`%s` is a %s, but a commit or filename was expected' % 2274507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper (value, object_type)) 2284507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2294507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2304507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef get_object_type(value): 2314507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Returns a string description of an object's type, or None if it is not 2324507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper a valid git object.""" 2334507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper cmd = ['git', 'cat-file', '-t', value] 2344507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 2354507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper stdout, stderr = p.communicate() 2364507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if p.returncode != 0: 2374507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return None 2384507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return stdout.strip() 2394507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2404507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2414507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef compute_diff_and_extract_lines(commit, files): 2424507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Calls compute_diff() followed by extract_lines().""" 2434507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper diff_process = compute_diff(commit, files) 2444507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper changed_lines = extract_lines(diff_process.stdout) 2454507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper diff_process.stdout.close() 2464507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper diff_process.wait() 2474507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if diff_process.returncode != 0: 2484507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # Assume error was already printed to stderr. 2494507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper sys.exit(2) 2504507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return changed_lines 2514507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2524507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2534507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef compute_diff(commit, files): 2544507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Return a subprocess object producing the diff from `commit`. 2554507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2564507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper The return value's `stdin` file object will produce a patch with the 2574507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper differences between the working directory and `commit`, filtered on `files` 2584507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper (if non-empty). Zero context lines are used in the patch.""" 2594507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper cmd = ['git', 'diff-index', '-p', '-U0', commit, '--'] 2604507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper cmd.extend(files) 2614507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 2624507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.stdin.close() 2634507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return p 2644507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2654507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2664507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef extract_lines(patch_file): 2674507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Extract the changed lines in `patch_file`. 2684507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 26965e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper The return value is a dictionary mapping filename to a list of (start_line, 27065e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper line_count) pairs. 27165e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper 2724507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper The input must have been produced with ``-U0``, meaning unidiff format with 2734507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper zero lines of context. The return value is a dict mapping filename to a 2744507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper list of line `Range`s.""" 2754507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper matches = {} 2764507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper for line in patch_file: 2774507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper match = re.search(r'^\+\+\+\ [^/]+/(.*)', line) 2784507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if match: 2794507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper filename = match.group(1).rstrip('\r\n') 2804507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper match = re.search(r'^@@ -[0-9,]+ \+(\d+)(,(\d+))?', line) 2814507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if match: 2824507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper start_line = int(match.group(1)) 2834507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper line_count = 1 2844507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if match.group(3): 2854507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper line_count = int(match.group(3)) 2864507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if line_count > 0: 2874507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper matches.setdefault(filename, []).append(Range(start_line, line_count)) 2884507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return matches 2894507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2904507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2914507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef filter_by_extension(dictionary, allowed_extensions): 2924507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Delete every key in `dictionary` that doesn't have an allowed extension. 2934507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 2944507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper `allowed_extensions` must be a collection of lowercase file extensions, 2954507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper excluding the period.""" 2964507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper allowed_extensions = frozenset(allowed_extensions) 2974507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper for filename in dictionary.keys(): 2984507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper base_ext = filename.rsplit('.', 1) 2994507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if len(base_ext) == 1 or base_ext[1].lower() not in allowed_extensions: 3004507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper del dictionary[filename] 3014507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3024507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3034507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef cd_to_toplevel(): 3044507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Change to the top level of the git repository.""" 3054507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper toplevel = run('git', 'rev-parse', '--show-toplevel') 3064507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper os.chdir(toplevel) 3074507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3084507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3094507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef create_tree_from_workdir(filenames): 3104507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Create a new git tree with the given files from the working directory. 3114507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3124507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper Returns the object ID (SHA-1) of the created tree.""" 3134507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return create_tree(filenames, '--stdin') 3144507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3154507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 31665e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasperdef run_clang_format_and_save_to_tree(changed_lines, binary='clang-format', 3174507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper style=None): 3184507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Run clang-format on each file and save the result to a git tree. 3194507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3204507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper Returns the object ID (SHA-1) of the created tree.""" 3214507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper def index_info_generator(): 32265e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper for filename, line_ranges in changed_lines.iteritems(): 3234507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper mode = oct(os.stat(filename).st_mode) 32465e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper blob_id = clang_format_to_blob(filename, line_ranges, binary=binary, 3254507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper style=style) 3264507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper yield '%s %s\t%s' % (mode, blob_id, filename) 3274507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return create_tree(index_info_generator(), '--index-info') 3284507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3294507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3304507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef create_tree(input_lines, mode): 3314507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Create a tree object from the given input. 3324507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3334507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper If mode is '--stdin', it must be a list of filenames. If mode is 3344507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper '--index-info' is must be a list of values suitable for "git update-index 3354507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper --index-info", such as "<mode> <SP> <sha1> <TAB> <filename>". Any other mode 3364507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper is invalid.""" 3374507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper assert mode in ('--stdin', '--index-info') 3384507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper cmd = ['git', 'update-index', '--add', '-z', mode] 3394507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper with temporary_index_file(): 3404507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p = subprocess.Popen(cmd, stdin=subprocess.PIPE) 3414507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper for line in input_lines: 3424507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.stdin.write('%s\0' % line) 3434507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p.stdin.close() 3444507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if p.wait() != 0: 3454507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper die('`%s` failed' % ' '.join(cmd)) 3464507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper tree_id = run('git', 'write-tree') 3474507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return tree_id 3484507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3494507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 35065e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasperdef clang_format_to_blob(filename, line_ranges, binary='clang-format', 3514507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper style=None): 3524507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Run clang-format on the given file and save the result to a git blob. 3534507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3544507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper Returns the object ID (SHA-1) of the created blob.""" 3554507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clang_format_cmd = [binary, filename] 3564507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if style: 3574507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clang_format_cmd.extend(['-style='+style]) 35865e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper clang_format_cmd.extend([ 35965e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper '-lines=%s:%s' % (start_line, start_line+line_count-1) 36065e2b74344de606145c0bc5aa6209d375db9e5ebDaniel Jasper for start_line, line_count in line_ranges]) 3614507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper try: 3624507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clang_format = subprocess.Popen(clang_format_cmd, stdin=subprocess.PIPE, 3634507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper stdout=subprocess.PIPE) 3644507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper except OSError as e: 3654507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if e.errno == errno.ENOENT: 3664507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper die('cannot find executable "%s"' % binary) 3674507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 3684507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper raise 3694507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clang_format.stdin.close() 3704507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper hash_object_cmd = ['git', 'hash-object', '-w', '--path='+filename, '--stdin'] 3714507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper hash_object = subprocess.Popen(hash_object_cmd, stdin=clang_format.stdout, 3724507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper stdout=subprocess.PIPE) 3734507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper clang_format.stdout.close() 3744507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper stdout = hash_object.communicate()[0] 3754507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if hash_object.returncode != 0: 3764507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper die('`%s` failed' % ' '.join(hash_object_cmd)) 3774507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if clang_format.wait() != 0: 3784507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper die('`%s` failed' % ' '.join(clang_format_cmd)) 3794507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return stdout.rstrip('\r\n') 3804507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3814507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3824507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper@contextlib.contextmanager 3834507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef temporary_index_file(tree=None): 3844507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Context manager for setting GIT_INDEX_FILE to a temporary file and deleting 3854507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper the file afterward.""" 3864507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper index_path = create_temporary_index(tree) 3874507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper old_index_path = os.environ.get('GIT_INDEX_FILE') 3884507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper os.environ['GIT_INDEX_FILE'] = index_path 3894507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper try: 3904507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper yield 3914507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper finally: 3924507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if old_index_path is None: 3934507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper del os.environ['GIT_INDEX_FILE'] 3944507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 3954507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper os.environ['GIT_INDEX_FILE'] = old_index_path 3964507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper os.remove(index_path) 3974507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3984507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 3994507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef create_temporary_index(tree=None): 4004507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Create a temporary index file and return the created file's path. 4014507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4024507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper If `tree` is not None, use that as the tree to read in. Otherwise, an 4034507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper empty index is created.""" 4044507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper gitdir = run('git', 'rev-parse', '--git-dir') 4054507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper path = os.path.join(gitdir, temp_index_basename) 4064507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if tree is None: 4074507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper tree = '--empty' 4084507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper run('git', 'read-tree', '--index-output='+path, tree) 4094507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return path 4104507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4114507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4124507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef print_diff(old_tree, new_tree): 4134507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Print the diff between the two trees to stdout.""" 4144507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # We use the porcelain 'diff' and not plumbing 'diff-tree' because the output 4154507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # is expected to be viewed by the user, and only the former does nice things 4164507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # like color and pagination. 4174507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper subprocess.check_call(['git', 'diff', old_tree, new_tree, '--']) 4184507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4194507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4204507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef apply_changes(old_tree, new_tree, force=False, patch_mode=False): 4214507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper """Apply the changes in `new_tree` to the working directory. 4224507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4234507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper Bails if there are local changes in those files and not `force`. If 4244507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper `patch_mode`, runs `git checkout --patch` to select hunks interactively.""" 4254507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper changed_files = run('git', 'diff-tree', '-r', '-z', '--name-only', old_tree, 4264507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper new_tree).rstrip('\0').split('\0') 4274507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if not force: 4284507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper unstaged_files = run('git', 'diff-files', '--name-status', *changed_files) 4294507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if unstaged_files: 4304507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print >>sys.stderr, ('The following files would be modified but ' 4314507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 'have unstaged changes:') 4324507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print >>sys.stderr, unstaged_files 4334507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print >>sys.stderr, 'Please commit, stage, or stash them first.' 4344507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper sys.exit(2) 4354507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if patch_mode: 4364507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # In patch mode, we could just as well create an index from the new tree 4374507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # and checkout from that, but then the user will be presented with a 4384507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # message saying "Discard ... from worktree". Instead, we use the old 4394507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # tree as the index and checkout from new_tree, which gives the slightly 4404507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # better message, "Apply ... to index and worktree". This is not quite 4414507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper # right, since it won't be applied to the user's index, but oh well. 4424507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper with temporary_index_file(old_tree): 4434507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper subprocess.check_call(['git', 'checkout', '--patch', new_tree]) 4444507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper index_tree = old_tree 4454507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper else: 4464507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper with temporary_index_file(new_tree): 4474507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper run('git', 'checkout-index', '-a', '-f') 4484507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return changed_files 4494507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4504507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4514507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef run(*args, **kwargs): 4524507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper stdin = kwargs.pop('stdin', '') 4534507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper verbose = kwargs.pop('verbose', True) 4544507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper strip = kwargs.pop('strip', True) 4554507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper for name in kwargs: 4564507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper raise TypeError("run() got an unexpected keyword argument '%s'" % name) 4574507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 4584507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper stdin=subprocess.PIPE) 4594507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper stdout, stderr = p.communicate(input=stdin) 4604507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if p.returncode == 0: 4614507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if stderr: 4624507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if verbose: 4634507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print >>sys.stderr, '`%s` printed to stderr:' % ' '.join(args) 4644507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print >>sys.stderr, stderr.rstrip() 4654507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if strip: 4664507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper stdout = stdout.rstrip('\r\n') 4674507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper return stdout 4684507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if verbose: 4694507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print >>sys.stderr, '`%s` returned %s' % (' '.join(args), p.returncode) 4704507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper if stderr: 4714507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print >>sys.stderr, stderr.rstrip() 4724507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper sys.exit(2) 4734507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4744507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4754507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperdef die(message): 4764507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper print >>sys.stderr, 'error:', message 4774507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper sys.exit(2) 4784507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4794507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper 4804507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasperif __name__ == '__main__': 4814507a2cc6f65c8891c574cf0d80c1cad2d3cffaaDaniel Jasper main() 482