Improve clang-format detection & enforcement (#3462)
* Improve the detection of non-clang-format-compliant commits/diffs * Fix formatting * Add .clang-format to version control; have it consistent and fixed across environments
This commit is contained in:
		
					parent
					
						
							
								61b3347e75
							
						
					
				
			
			
				commit
				
					
						8ecbb1d251
					
				
			
		
					 8 changed files with 18 additions and 700 deletions
				
			
		|  | @ -34,7 +34,6 @@ BraceWrapping: | |||
|   AfterClass: true | ||||
|   BeforeCatch: true | ||||
|   BeforeElse: true | ||||
|   BeforeLambdaBody: true | ||||
|   SplitEmptyRecord: true | ||||
|   SplitEmptyNamespace: true | ||||
| SortIncludes: true | ||||
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -82,6 +82,3 @@ Testing | |||
| 
 | ||||
| # Autogenerated Build Prep artifacts | ||||
| util/build_prep/*/prep.sh | ||||
| 
 | ||||
| # Autogenerated Tooling | ||||
| .clang-format | ||||
|  |  | |||
|  | @ -2,19 +2,21 @@ | |||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| source "$(dirname "$BASH_SOURCE")/detect-clang-format.sh" | ||||
| if [[ ! -z $(git status --untracked-files=no --porcelain) ]]; then | ||||
|     echo "Unable to run script: working directory not clean (see git status)" | ||||
|     exit 1 | ||||
| fi | ||||
| 
 | ||||
| source "$(dirname "$BASH_SOURCE")/common.sh" | ||||
| 
 | ||||
| "$REPO_ROOT/ci/update-clang-format" | ||||
| "$REPO_ROOT/ci/clang-format-all.sh" | ||||
| 
 | ||||
| clang_format_result=$(python "$REPO_ROOT/ci/git-clang-format.py" --diff --extensions "hpp,cpp") | ||||
| if [[ $clang_format_result != "no modified files to format" ]] && | ||||
|    [[ $clang_format_result != "clang-format did not modify any files" ]]; then | ||||
|     python "$REPO_ROOT/ci/git-clang-format.py" --diff --extensions "hpp,cpp" | ||||
|     echo | ||||
| if [[ ! -z $(git status --untracked-files=no --porcelain) ]]; then | ||||
|     echo "Code formatting differs from expected - please run ci/clang-format-all.sh" | ||||
|     git diff | ||||
|     git reset --hard HEAD > /dev/null | ||||
|     exit 1 | ||||
| else | ||||
|     echo "clang-format passed" | ||||
|     exit 0 | ||||
| fi | ||||
| 
 | ||||
| echo "clang-format passed" | ||||
| exit 0 | ||||
|  |  | |||
|  | @ -2,9 +2,7 @@ | |||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| source $(dirname $BASH_SOURCE)/detect-clang-format.sh | ||||
| source $(dirname $BASH_SOURCE)/common.sh | ||||
| source "$(dirname "$BASH_SOURCE")/detect-clang-format.sh" | ||||
| source "$(dirname "$BASH_SOURCE")/common.sh" | ||||
| 
 | ||||
| cd "$REPO_ROOT" | ||||
| ./ci/update-clang-format | ||||
| find nano -iname '*.h' -o -iname '*.hpp' -o -iname '*.cpp' | xargs "$CLANG_FORMAT" -i | ||||
| find "$REPO_ROOT/nano" -iname '*.h' -o -iname '*.hpp' -o -iname '*.cpp' | xargs "$CLANG_FORMAT" -i -style=file | ||||
|  |  | |||
|  | @ -39,3 +39,5 @@ if [[ -z $CLANG_FORMAT ]]; then | |||
|     echo "No 'clang-format' of version '$CLANG_FORMAT_VERSION' could be detected in your PATH." | ||||
|     exit 1 | ||||
| fi | ||||
| 
 | ||||
| echo "Using '$CLANG_FORMAT' version '$CLANG_FORMAT_VERSION'" | ||||
|  |  | |||
|  | @ -1,641 +0,0 @@ | |||
| # ============================================================================== | ||||
| # LLVM Release License | ||||
| # ============================================================================== | ||||
| # University of Illinois/NCSA | ||||
| # Open Source License | ||||
| 
 | ||||
| # Copyright (c) 2007-2016 University of Illinois at Urbana-Champaign. | ||||
| # All rights reserved. | ||||
| 
 | ||||
| # Developed by: | ||||
| 
 | ||||
| #     LLVM Team | ||||
| 
 | ||||
| #     University of Illinois at Urbana-Champaign | ||||
| 
 | ||||
| #     http://llvm.org | ||||
| 
 | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| # this software and associated documentation files (the "Software"), to deal with | ||||
| # the Software without restriction, including without limitation the rights to | ||||
| # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||||
| # of the Software, and to permit persons to whom the Software is furnished to do | ||||
| # so, subject to the following conditions: | ||||
| 
 | ||||
| #     * Redistributions of source code must retain the above copyright notice, | ||||
| #       this list of conditions and the following disclaimers. | ||||
| 
 | ||||
| #     * Redistributions in binary form must reproduce the above copyright notice, | ||||
| #       this list of conditions and the following disclaimers in the | ||||
| #       documentation and/or other materials provided with the distribution. | ||||
| 
 | ||||
| #     * Neither the names of the LLVM Team, University of Illinois at | ||||
| #       Urbana-Champaign, nor the names of its contributors may be used to | ||||
| #       endorse or promote products derived from this Software without specific | ||||
| #       prior written permission. | ||||
| 
 | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||
| # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE | ||||
| # CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE | ||||
| # SOFTWARE. | ||||
| 
 | ||||
| # ============================================================================== | ||||
| # The LLVM software contains code written by third parties.  Such software will | ||||
| # have its own individual LICENSE.TXT file in the directory in which it appears. | ||||
| # This file will describe the copyrights, license, and restrictions which apply | ||||
| # to that code. | ||||
| 
 | ||||
| # The disclaimer of warranty in the University of Illinois Open Source License | ||||
| # applies to all code in the LLVM Distribution, and nothing in any of the | ||||
| # other licenses gives permission to use the names of the LLVM Team or the | ||||
| # University of Illinois to endorse or promote products derived from this | ||||
| # Software. | ||||
| 
 | ||||
| # The following pieces of software have additional or alternate copyrights, | ||||
| # licenses, and/or restrictions: | ||||
| 
 | ||||
| # Program             Directory | ||||
| # -------             --------- | ||||
| # <none yet> | ||||
| 
 | ||||
| r"""                                                                              | ||||
| clang-format git integration                                                      | ||||
| ============================                                                      | ||||
|                                                                                   | ||||
| This file provides a clang-format integration for git. Put it somewhere in your   | ||||
| path and ensure that it is executable. Then, "git clang-format" will invoke       | ||||
| clang-format on the changes in current files or a specific commit.                | ||||
|                                                                                   | ||||
| For further details, run:                                                         | ||||
| git clang-format -h                                                               | ||||
|                                                                                   | ||||
| Requires Python 2.7 or Python 3                                                   | ||||
| """ | ||||
| 
 | ||||
| from __future__ import print_function | ||||
| import argparse | ||||
| import collections | ||||
| import contextlib | ||||
| import errno | ||||
| import os | ||||
| import re | ||||
| import subprocess | ||||
| import sys | ||||
| 
 | ||||
| usage = 'git clang-format [OPTIONS] [<commit>] [<commit>] [--] [<file>...]' | ||||
| 
 | ||||
| desc = ''' | ||||
| If zero or one commits are given, run clang-format on all lines that differ | ||||
| between the working directory and <commit>, which defaults to HEAD.  Changes are | ||||
| only applied to the working directory. | ||||
| 
 | ||||
| If two commits are given (requires --diff), run clang-format on all lines in the | ||||
| second <commit> that differ from the first <commit>. | ||||
| 
 | ||||
| The following git-config settings set the default of the corresponding option: | ||||
|   clangFormat.binary | ||||
|   clangFormat.commit | ||||
|   clangFormat.extension | ||||
|   clangFormat.style | ||||
| ''' | ||||
| 
 | ||||
| # Name of the temporary index file in which save the output of clang-format. | ||||
| # This file is created within the .git directory. | ||||
| temp_index_basename = 'clang-format-index' | ||||
| 
 | ||||
| 
 | ||||
| Range = collections.namedtuple('Range', 'start, count') | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     config = load_git_config() | ||||
| 
 | ||||
|     # In order to keep '--' yet allow options after positionals, we need to | ||||
|     # check for '--' ourselves.  (Setting nargs='*' throws away the '--', while | ||||
|     # nargs=argparse.REMAINDER disallows options after positionals.) | ||||
|     argv = sys.argv[1:] | ||||
|     try: | ||||
|         idx = argv.index('--') | ||||
|     except ValueError: | ||||
|         dash_dash = [] | ||||
|     else: | ||||
|         dash_dash = argv[idx:] | ||||
|         argv = argv[:idx] | ||||
| 
 | ||||
|     default_extensions = ','.join([ | ||||
|         # From clang/lib/Frontend/FrontendOptions.cpp, all lower case | ||||
|         'c', 'h',  # C | ||||
|         'm',  # ObjC | ||||
|         'mm',  # ObjC++ | ||||
|         'cc', 'cp', 'cpp', 'c++', 'cxx', 'hpp',  # C++ | ||||
|         # Other languages that clang-format supports | ||||
|         'proto', 'protodevel',  # Protocol Buffers | ||||
|         'java',  # Java | ||||
|         'js',  # JavaScript | ||||
|         'ts',  # TypeScript | ||||
|     ]) | ||||
| 
 | ||||
|     p = argparse.ArgumentParser( | ||||
|         usage=usage, formatter_class=argparse.RawDescriptionHelpFormatter, | ||||
|         description=desc) | ||||
|     p.add_argument('--binary', | ||||
|                    default=config.get('clangformat.binary', 'clang-format'), | ||||
|                    help='path to clang-format'), | ||||
|     p.add_argument('--commit', | ||||
|                    default=config.get('clangformat.commit', 'HEAD'), | ||||
|                    help='default commit to use if none is specified'), | ||||
|     p.add_argument('--diff', action='store_true', | ||||
|                    help='print a diff instead of applying the changes') | ||||
|     p.add_argument('--extensions', | ||||
|                    default=config.get('clangformat.extensions', | ||||
|                                       default_extensions), | ||||
|                    help=('comma-separated list of file extensions to format, ' | ||||
|                          'excluding the period and case-insensitive')), | ||||
|     p.add_argument('-f', '--force', action='store_true', | ||||
|                    help='allow changes to unstaged files') | ||||
|     p.add_argument('-p', '--patch', action='store_true', | ||||
|                    help='select hunks interactively') | ||||
|     p.add_argument('-q', '--quiet', action='count', default=0, | ||||
|                    help='print less information') | ||||
|     p.add_argument('--style', | ||||
|                    default=config.get('clangformat.style', None), | ||||
|                    help='passed to clang-format'), | ||||
|     p.add_argument('-v', '--verbose', action='count', default=0, | ||||
|                    help='print extra information') | ||||
|     # We gather all the remaining positional arguments into 'args' since we need | ||||
|     # to use some heuristics to determine whether or not <commit> was present. | ||||
|     # However, to print pretty messages, we make use of metavar and help. | ||||
|     p.add_argument('args', nargs='*', metavar='<commit>', | ||||
|                    help='revision from which to compute the diff') | ||||
|     p.add_argument('ignored', nargs='*', metavar='<file>...', | ||||
|                    help='if specified, only consider differences in these files') | ||||
|     opts = p.parse_args(argv) | ||||
| 
 | ||||
|     opts.verbose -= opts.quiet | ||||
|     del opts.quiet | ||||
| 
 | ||||
|     commits, files = interpret_args(opts.args, dash_dash, opts.commit) | ||||
|     if len(commits) > 1: | ||||
|         if not opts.diff: | ||||
|             die('--diff is required when two commits are given') | ||||
|     else: | ||||
|         if len(commits) > 2: | ||||
|             die('at most two commits allowed; %d given' % len(commits)) | ||||
|     changed_lines = compute_diff_and_extract_lines(commits, files) | ||||
|     if opts.verbose >= 1: | ||||
|         ignored_files = set(changed_lines) | ||||
|     filter_by_extension(changed_lines, opts.extensions.lower().split(',')) | ||||
|     if opts.verbose >= 1: | ||||
|         ignored_files.difference_update(changed_lines) | ||||
|         if ignored_files: | ||||
|             print('Ignoring changes in the following files (wrong extension):') | ||||
|             for filename in ignored_files: | ||||
|                 print('    %s' % filename) | ||||
|         if changed_lines: | ||||
|             print('Running clang-format on the following files:') | ||||
|             for filename in changed_lines: | ||||
|                 print('    %s' % filename) | ||||
|     if not changed_lines: | ||||
|         print('no modified files to format') | ||||
|         return | ||||
|     # The computed diff outputs absolute paths, so we must cd before accessing | ||||
|     # those files. | ||||
|     cd_to_toplevel() | ||||
|     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) | ||||
|     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) | ||||
|     if opts.verbose >= 1: | ||||
|         print('old tree: %s' % old_tree) | ||||
|         print('new tree: %s' % new_tree) | ||||
|     if old_tree == new_tree: | ||||
|         if opts.verbose >= 0: | ||||
|             print('clang-format did not modify any files') | ||||
|     elif opts.diff: | ||||
|         print_diff(old_tree, new_tree) | ||||
|     else: | ||||
|         changed_files = apply_changes(old_tree, new_tree, force=opts.force, | ||||
|                                       patch_mode=opts.patch) | ||||
|         if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1: | ||||
|             print('changed files:') | ||||
|             for filename in changed_files: | ||||
|                 print('    %s' % filename) | ||||
| 
 | ||||
| 
 | ||||
| def load_git_config(non_string_options=None): | ||||
|     """Return the git configuration as a dictionary. | ||||
| 
 | ||||
|     All options are assumed to be strings unless in `non_string_options`, in which | ||||
|     is a dictionary mapping option name (in lower case) to either "--bool" or | ||||
|     "--int".""" | ||||
|     if non_string_options is None: | ||||
|         non_string_options = {} | ||||
|     out = {} | ||||
|     for entry in run('git', 'config', '--list', '--null').split('\0'): | ||||
|         if entry: | ||||
|             name, value = entry.split('\n', 1) | ||||
|             if name in non_string_options: | ||||
|                 value = run('git', 'config', non_string_options[name], name) | ||||
|             out[name] = value | ||||
|     return out | ||||
| 
 | ||||
| 
 | ||||
| def interpret_args(args, dash_dash, default_commit): | ||||
|     """Interpret `args` as "[commits] [--] [files]" and return (commits, files). | ||||
| 
 | ||||
|     It is assumed that "--" and everything that follows has been removed from | ||||
|     args and placed in `dash_dash`. | ||||
| 
 | ||||
|     If "--" is present (i.e., `dash_dash` is non-empty), the arguments to its | ||||
|     left (if present) are taken as commits.  Otherwise, the arguments are checked | ||||
|     from left to right if they are commits or files.  If commits are not given, | ||||
|     a list with `default_commit` is used.""" | ||||
|     if dash_dash: | ||||
|         if len(args) == 0: | ||||
|             commits = [default_commit] | ||||
|         else: | ||||
|             commits = args | ||||
|         for commit in commits: | ||||
|             object_type = get_object_type(commit) | ||||
|             if object_type not in ('commit', 'tag'): | ||||
|                 if object_type is None: | ||||
|                     die("'%s' is not a commit" % commit) | ||||
|                 else: | ||||
|                     die("'%s' is a %s, but a commit was expected" % | ||||
|                         (commit, object_type)) | ||||
|         files = dash_dash[1:] | ||||
|     elif args: | ||||
|         commits = [] | ||||
|         while args: | ||||
|             if not disambiguate_revision(args[0]): | ||||
|                 break | ||||
|             commits.append(args.pop(0)) | ||||
|         if not commits: | ||||
|             commits = [default_commit] | ||||
|         files = args | ||||
|     else: | ||||
|         commits = [default_commit] | ||||
|         files = [] | ||||
|     return commits, files | ||||
| 
 | ||||
| 
 | ||||
| def disambiguate_revision(value): | ||||
|     """Returns True if `value` is a revision, False if it is a file, or dies.""" | ||||
|     # If `value` is ambiguous (neither a commit nor a file), the following | ||||
|     # command will die with an appropriate error message. | ||||
|     run('git', 'rev-parse', value, verbose=False) | ||||
|     object_type = get_object_type(value) | ||||
|     if object_type is None: | ||||
|         return False | ||||
|     if object_type in ('commit', 'tag'): | ||||
|         return True | ||||
|     die('`%s` is a %s, but a commit or filename was expected' % | ||||
|         (value, object_type)) | ||||
| 
 | ||||
| 
 | ||||
| def get_object_type(value): | ||||
|     """Returns a string description of an object's type, or None if it is not | ||||
|     a valid git object.""" | ||||
|     cmd = ['git', 'cat-file', '-t', value] | ||||
|     p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||||
|     stdout, stderr = p.communicate() | ||||
|     if p.returncode != 0: | ||||
|         return None | ||||
|     return convert_string(stdout.strip()) | ||||
| 
 | ||||
| 
 | ||||
| def compute_diff_and_extract_lines(commits, files): | ||||
|     """Calls compute_diff() followed by extract_lines().""" | ||||
|     diff_process = compute_diff(commits, files) | ||||
|     changed_lines = extract_lines(diff_process.stdout) | ||||
|     diff_process.stdout.close() | ||||
|     diff_process.wait() | ||||
|     if diff_process.returncode != 0: | ||||
|         # Assume error was already printed to stderr. | ||||
|         sys.exit(2) | ||||
|     return changed_lines | ||||
| 
 | ||||
| 
 | ||||
| def compute_diff(commits, files): | ||||
|     """Return a subprocess object producing the diff from `commits`. | ||||
| 
 | ||||
|     The return value's `stdin` file object will produce a patch with the | ||||
|     differences between the working directory and the first commit if a single | ||||
|     one was specified, or the difference between both specified commits, filtered | ||||
|     on `files` (if non-empty).  Zero context lines are used in the patch.""" | ||||
|     git_tool = 'diff-index' | ||||
|     if len(commits) > 1: | ||||
|         git_tool = 'diff-tree' | ||||
|     cmd = ['git', git_tool, '-p', '-U0'] + commits + ['--'] | ||||
|     cmd.extend(files) | ||||
|     p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) | ||||
|     p.stdin.close() | ||||
|     return p | ||||
| 
 | ||||
| 
 | ||||
| def extract_lines(patch_file): | ||||
|     """Extract the changed lines in `patch_file`. | ||||
| 
 | ||||
|     The return value is a dictionary mapping filename to a list of (start_line, | ||||
|     line_count) pairs. | ||||
| 
 | ||||
|     The input must have been produced with ``-U0``, meaning unidiff format with | ||||
|     zero lines of context.  The return value is a dict mapping filename to a | ||||
|     list of line `Range`s.""" | ||||
|     matches = {} | ||||
|     for line in patch_file: | ||||
|         line = convert_string(line) | ||||
|         match = re.search(r'^\+\+\+\ [^/]+/(.*)', line) | ||||
|         if match: | ||||
|             filename = match.group(1).rstrip('\r\n') | ||||
|         match = re.search(r'^@@ -[0-9,]+ \+(\d+)(,(\d+))?', line) | ||||
|         if match: | ||||
|             start_line = int(match.group(1)) | ||||
|             line_count = 1 | ||||
|             if match.group(3): | ||||
|                 line_count = int(match.group(3)) | ||||
|             if line_count > 0: | ||||
|                 matches.setdefault(filename, []).append( | ||||
|                     Range(start_line, line_count)) | ||||
|     return matches | ||||
| 
 | ||||
| 
 | ||||
| def filter_by_extension(dictionary, allowed_extensions): | ||||
|     """Delete every key in `dictionary` that doesn't have an allowed extension. | ||||
| 
 | ||||
|     `allowed_extensions` must be a collection of lowercase file extensions, | ||||
|     excluding the period.""" | ||||
|     allowed_extensions = frozenset(allowed_extensions) | ||||
|     for filename in list(dictionary.keys()): | ||||
|         base_ext = filename.rsplit('.', 1) | ||||
|         if len(base_ext) == 1 and '' in allowed_extensions: | ||||
|             continue | ||||
|         if len(base_ext) == 1 or base_ext[1].lower() not in allowed_extensions: | ||||
|             del dictionary[filename] | ||||
| 
 | ||||
| 
 | ||||
| def cd_to_toplevel(): | ||||
|     """Change to the top level of the git repository.""" | ||||
|     toplevel = run('git', 'rev-parse', '--show-toplevel') | ||||
|     os.chdir(toplevel) | ||||
| 
 | ||||
| 
 | ||||
| def create_tree_from_workdir(filenames): | ||||
|     """Create a new git tree with the given files from the working directory. | ||||
| 
 | ||||
|     Returns the object ID (SHA-1) of the created tree.""" | ||||
|     return create_tree(filenames, '--stdin') | ||||
| 
 | ||||
| 
 | ||||
| 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.""" | ||||
|     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, | ||||
|                                                 stdout=subprocess.PIPE) | ||||
|                 stdout = git_metadata.communicate()[0] | ||||
|                 mode = oct(int(stdout.split()[0], 8)) | ||||
|             else: | ||||
|                 mode = oct(os.stat(filename).st_mode) | ||||
|             # Adjust python3 octal format so that it matches what git expects | ||||
|             if mode.startswith('0o'): | ||||
|                 mode = '0' + mode[2:] | ||||
|             blob_id = clang_format_to_blob(filename, line_ranges, | ||||
|                                            revision=revision, | ||||
|                                            binary=binary, | ||||
|                                            style=style) | ||||
|             yield '%s %s\t%s' % (mode, blob_id, filename) | ||||
|     return create_tree(index_info_generator(), '--index-info') | ||||
| 
 | ||||
| 
 | ||||
| def create_tree(input_lines, mode): | ||||
|     """Create a tree object from the given input. | ||||
| 
 | ||||
|     If mode is '--stdin', it must be a list of filenames.  If mode is | ||||
|     '--index-info' is must be a list of values suitable for "git update-index | ||||
|     --index-info", such as "<mode> <SP> <sha1> <TAB> <filename>".  Any other mode | ||||
|     is invalid.""" | ||||
|     assert mode in ('--stdin', '--index-info') | ||||
|     cmd = ['git', 'update-index', '--add', '-z', mode] | ||||
|     with temporary_index_file(): | ||||
|         p = subprocess.Popen(cmd, stdin=subprocess.PIPE) | ||||
|         for line in input_lines: | ||||
|             p.stdin.write(to_bytes('%s\0' % line)) | ||||
|         p.stdin.close() | ||||
|         if p.wait() != 0: | ||||
|             die('`%s` failed' % ' '.join(cmd)) | ||||
|         tree_id = run('git', 'write-tree') | ||||
|         return tree_id | ||||
| 
 | ||||
| 
 | ||||
| def clang_format_to_blob(filename, line_ranges, revision=None, | ||||
|                          binary='clang-format', style=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. | ||||
| 
 | ||||
|     Returns the object ID (SHA-1) of the created blob.""" | ||||
|     clang_format_cmd = [binary] | ||||
|     if style: | ||||
|         clang_format_cmd.extend(['-style='+style]) | ||||
|     clang_format_cmd.extend([ | ||||
|         '-lines=%s:%s' % (start_line, start_line+line_count-1) | ||||
|         for start_line, line_count in line_ranges]) | ||||
|     if revision: | ||||
|         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, | ||||
|                                     stdout=subprocess.PIPE) | ||||
|         git_show.stdin.close() | ||||
|         clang_format_stdin = git_show.stdout | ||||
|     else: | ||||
|         clang_format_cmd.extend([filename]) | ||||
|         git_show = None | ||||
|         clang_format_stdin = subprocess.PIPE | ||||
|     try: | ||||
|         clang_format = subprocess.Popen(clang_format_cmd, stdin=clang_format_stdin, | ||||
|                                         stdout=subprocess.PIPE) | ||||
|         if clang_format_stdin == subprocess.PIPE: | ||||
|             clang_format_stdin = clang_format.stdin | ||||
|     except OSError as e: | ||||
|         if e.errno == errno.ENOENT: | ||||
|             die('cannot find executable "%s"' % binary) | ||||
|         else: | ||||
|             raise | ||||
|     clang_format_stdin.close() | ||||
|     hash_object_cmd = ['git', 'hash-object', | ||||
|                        '-w', '--path='+filename, '--stdin'] | ||||
|     hash_object = subprocess.Popen(hash_object_cmd, stdin=clang_format.stdout, | ||||
|                                    stdout=subprocess.PIPE) | ||||
|     clang_format.stdout.close() | ||||
|     stdout = hash_object.communicate()[0] | ||||
|     if hash_object.returncode != 0: | ||||
|         die('`%s` failed' % ' '.join(hash_object_cmd)) | ||||
|     if clang_format.wait() != 0: | ||||
|         die('`%s` failed' % ' '.join(clang_format_cmd)) | ||||
|     if git_show and git_show.wait() != 0: | ||||
|         die('`%s` failed' % ' '.join(git_show_cmd)) | ||||
|     return convert_string(stdout).rstrip('\r\n') | ||||
| 
 | ||||
| 
 | ||||
| @contextlib.contextmanager | ||||
| def temporary_index_file(tree=None): | ||||
|     """Context manager for setting GIT_INDEX_FILE to a temporary file and deleting | ||||
|     the file afterward.""" | ||||
|     index_path = create_temporary_index(tree) | ||||
|     old_index_path = os.environ.get('GIT_INDEX_FILE') | ||||
|     os.environ['GIT_INDEX_FILE'] = index_path | ||||
|     try: | ||||
|         yield | ||||
|     finally: | ||||
|         if old_index_path is None: | ||||
|             del os.environ['GIT_INDEX_FILE'] | ||||
|         else: | ||||
|             os.environ['GIT_INDEX_FILE'] = old_index_path | ||||
|         os.remove(index_path) | ||||
| 
 | ||||
| 
 | ||||
| def create_temporary_index(tree=None): | ||||
|     """Create a temporary index file and return the created file's path. | ||||
| 
 | ||||
|     If `tree` is not None, use that as the tree to read in.  Otherwise, an | ||||
|     empty index is created.""" | ||||
|     gitdir = run('git', 'rev-parse', '--git-dir') | ||||
|     path = os.path.join(gitdir, temp_index_basename) | ||||
|     if tree is None: | ||||
|         tree = '--empty' | ||||
|     run('git', 'read-tree', '--index-output='+path, tree) | ||||
|     return path | ||||
| 
 | ||||
| 
 | ||||
| def print_diff(old_tree, new_tree): | ||||
|     """Print the diff between the two trees to stdout.""" | ||||
|     # We use the porcelain 'diff' and not plumbing 'diff-tree' because the output | ||||
|     # is expected to be viewed by the user, and only the former does nice things | ||||
|     # like color and pagination. | ||||
|     # | ||||
|     # We also only print modified files since `new_tree` only contains the files | ||||
|     # that were modified, so unmodified files would show as deleted without the | ||||
|     # filter. | ||||
|     subprocess.check_call(['git', 'diff', '--diff-filter=M', old_tree, new_tree, | ||||
|                            '--']) | ||||
| 
 | ||||
| 
 | ||||
| def apply_changes(old_tree, new_tree, force=False, patch_mode=False): | ||||
|     """Apply the changes in `new_tree` to the working directory. | ||||
| 
 | ||||
|     Bails if there are local changes in those files and not `force`.  If | ||||
|     `patch_mode`, runs `git checkout --patch` to select hunks interactively.""" | ||||
|     changed_files = run('git', 'diff-tree', '--diff-filter=M', '-r', '-z', | ||||
|                         '--name-only', old_tree, | ||||
|                         new_tree).rstrip('\0').split('\0') | ||||
|     if not force: | ||||
|         unstaged_files = run('git', 'diff-files', | ||||
|                              '--name-status', *changed_files) | ||||
|         if unstaged_files: | ||||
|             print('The following files would be modified but ' | ||||
|                   'have unstaged changes:', file=sys.stderr) | ||||
|             print(unstaged_files, file=sys.stderr) | ||||
|             print('Please commit, stage, or stash them first.', file=sys.stderr) | ||||
|             sys.exit(2) | ||||
|     if patch_mode: | ||||
|         # In patch mode, we could just as well create an index from the new tree | ||||
|         # and checkout from that, but then the user will be presented with a | ||||
|         # message saying "Discard ... from worktree".  Instead, we use the old | ||||
|         # tree as the index and checkout from new_tree, which gives the slightly | ||||
|         # better message, "Apply ... to index and worktree".  This is not quite | ||||
|         # right, since it won't be applied to the user's index, but oh well. | ||||
|         with temporary_index_file(old_tree): | ||||
|             subprocess.check_call(['git', 'checkout', '--patch', new_tree]) | ||||
|         index_tree = old_tree | ||||
|     else: | ||||
|         with temporary_index_file(new_tree): | ||||
|             run('git', 'checkout-index', '-a', '-f') | ||||
|     return changed_files | ||||
| 
 | ||||
| 
 | ||||
| def run(*args, **kwargs): | ||||
|     stdin = kwargs.pop('stdin', '') | ||||
|     verbose = kwargs.pop('verbose', True) | ||||
|     strip = kwargs.pop('strip', True) | ||||
|     for name in kwargs: | ||||
|         raise TypeError("run() got an unexpected keyword argument '%s'" % name) | ||||
|     p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||||
|                          stdin=subprocess.PIPE) | ||||
|     stdout, stderr = p.communicate(input=stdin) | ||||
| 
 | ||||
|     stdout = convert_string(stdout) | ||||
|     stderr = convert_string(stderr) | ||||
| 
 | ||||
|     if p.returncode == 0: | ||||
|         if stderr: | ||||
|             if verbose: | ||||
|                 print('`%s` printed to stderr:' % | ||||
|                       ' '.join(args), file=sys.stderr) | ||||
|             print(stderr.rstrip(), file=sys.stderr) | ||||
|         if strip: | ||||
|             stdout = stdout.rstrip('\r\n') | ||||
|         return stdout | ||||
|     if verbose: | ||||
|         print('`%s` returned %s' % | ||||
|               (' '.join(args), p.returncode), file=sys.stderr) | ||||
|     if stderr: | ||||
|         print(stderr.rstrip(), file=sys.stderr) | ||||
|     sys.exit(2) | ||||
| 
 | ||||
| 
 | ||||
| def die(message): | ||||
|     print('error:', message, file=sys.stderr) | ||||
|     sys.exit(2) | ||||
| 
 | ||||
| 
 | ||||
| def to_bytes(str_input): | ||||
|     # Encode to UTF-8 to get binary data. | ||||
|     if isinstance(str_input, bytes): | ||||
|         return str_input | ||||
|     return str_input.encode('utf-8') | ||||
| 
 | ||||
| 
 | ||||
| def to_string(bytes_input): | ||||
|     if isinstance(bytes_input, str): | ||||
|         return bytes_input | ||||
|     return bytes_input.encode('utf-8') | ||||
| 
 | ||||
| 
 | ||||
| def convert_string(bytes_input): | ||||
|     try: | ||||
|         return to_string(bytes_input.decode('utf-8')) | ||||
|     except AttributeError:  # 'str' object has no attribute 'decode'. | ||||
|         return str(bytes_input) | ||||
|     except UnicodeError: | ||||
|         return str(bytes_input) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
|  | @ -1,39 +0,0 @@ | |||
| #!/usr/bin/env bash | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| source "$(dirname "$BASH_SOURCE")/common.sh" | ||||
| 
 | ||||
| cd "$REPO_ROOT" | ||||
| 
 | ||||
| retval='1' | ||||
| 
 | ||||
| rm -f .clang-format | ||||
| cp .clang-format.base .clang-format | ||||
| for try in {1..10}; do | ||||
| 	errors="$(clang-format -dump-config 2>&1 >/dev/null)" || : | ||||
| 	if [ -z "${errors}" ]; then | ||||
| 		retval='0' | ||||
| 
 | ||||
| 		break | ||||
| 	fi | ||||
| 
 | ||||
| 	errors_line="$(echo "${errors}" | awk ' | ||||
| 		/^YAML:/{ | ||||
| 			sub(/^YAML:/, ""); | ||||
| 			sub(/:.*/, ""); | ||||
| 			print; | ||||
| 			exit; | ||||
| 		} | ||||
| 	')" | ||||
| 
 | ||||
| 	if ! [ "${errors_line}" -gt -1 ] 2>/dev/null; then | ||||
| 		break | ||||
| 	fi | ||||
| 
 | ||||
| 	rm -f .clang-format.new | ||||
| 	sed "${errors_line} d" .clang-format >.clang-format.new | ||||
| 	mv .clang-format.new .clang-format | ||||
| done | ||||
| 
 | ||||
| exit "${retval}" | ||||
|  | @ -538,7 +538,7 @@ void nano::transport::tcp_channels::start_tcp (nano::endpoint const & endpoint_a | |||
| 				nano::node_id_handshake message (node_l->network_params.network, cookie, boost::none); | ||||
| 				if (node_l->config.logging.network_node_id_handshake_logging ()) | ||||
| 				{ | ||||
| 					node_l->logger.try_log (boost::str (boost::format ("Node ID handshake request sent with node ID %1% to %2%: query %3%") % node_l->node_id.pub.to_node_id () % endpoint_a % (cookie.has_value() ? cookie->to_string() : "not set"))); | ||||
| 					node_l->logger.try_log (boost::str (boost::format ("Node ID handshake request sent with node ID %1% to %2%: query %3%") % node_l->node_id.pub.to_node_id () % endpoint_a % (cookie.has_value () ? cookie->to_string () : "not set"))); | ||||
| 				} | ||||
| 				channel->set_endpoint (); | ||||
| 				std::shared_ptr<std::vector<uint8_t>> receive_buffer (std::make_shared<std::vector<uint8_t>> ()); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 theohax
				theohax