Remove Google VCS support from fix_includes.py

The script had rudimentary support for checking out files from Google's
version control system, which complicated testing and implementation
somewhat.

Now that this is not primarily a Google tool and most version control
systems do not use the read-only flag for preventing concurrent edits,
remove this code to simplify.

No functional change intended, unless you were using this support.
This commit is contained in:
Kim Grasman 2018-03-18 15:37:50 +01:00 committed by Kim Gräsman
parent eb1581dfbf
commit 0ac36dadb8
2 changed files with 10 additions and 308 deletions

View File

@ -19,11 +19,6 @@ mentioned in the output, removing their old #include lines and
replacing them with the lines given by the include_what_you_use
script.
We only edit files that are writeable (presumably open for p4 edit),
unless the user supplies a command to make files writeable via the
--checkout_command flag (eg '--checkout_command="p4 edit"').
This script runs in four stages. In the first, it groups physical
lines together to form 'move spans'. A 'move span' is the atomic unit
for moving or deleting code. A move span is either a) an #include
@ -67,10 +62,8 @@ __author__ = 'csilvers@google.com (Craig Silverstein)'
import difflib
import optparse
import os
import pipes # For (undocumented) pipes.quote
import re
import sys
import subprocess
from collections import OrderedDict
_USAGE = """\
@ -85,11 +78,9 @@ modifies the files mentioned in the output, removing their old
include_what_you_use script. It also sorts the #include and
forward-declare lines.
Only writable files (those opened for p4 edit) are modified (unless
--checkout_command is specified). All files mentioned in the
include-what-you-use script are modified, unless filenames are
specified on the commandline, in which case only those files are
modified.
All files mentioned in the include-what-you-use script are modified,
unless filenames are specified on the commandline, in which case only
those files are modified.
The exit code is the number of files that were modified (or that would
be modified if --dry_run was specified) unless that number exceeds 100,
@ -519,33 +510,12 @@ def _ReadFile(filename):
return None
def _ReadWriteableFile(filename, ignore_writeable):
"""Read from filename and return a list of file lines.
Given a filename, if the file is found and is writable, read
the file contents and return it as a list of lines (newlines
removed). If the file is not found or is not writable, or if
there is another IO error, return None.
Arguments:
filename: the name of the file to read.
ignore_writeable: if True, don't check whether the file is writeable;
return the contents anyway.
Returns:
A list of lines (without trailing newline) from filename, or None
if the file is not writable, or cannot be read.
"""
if os.access(filename, os.W_OK) or ignore_writeable:
return _ReadFile(filename)
return None
def _WriteFileContentsToFileObject(f, file_lines, line_ending):
"""Write the given file-lines to the file."""
f.write(line_ending.join(file_lines))
f.write(line_ending)
def _DetectLineEndings(filename):
"""Detect line ending of given file."""
@ -562,6 +532,7 @@ def _DetectLineEndings(filename):
finally:
f.close()
def _WriteFileContents(filename, file_lines):
"""Write the given file-lines to the file."""
try:
@ -576,36 +547,6 @@ def _WriteFileContents(filename, file_lines):
print("Error writing '%s': %s" % (filename, why))
def _CreateCommandLine(command, args):
"""Join the command with the args in a shell-quoted way."""
ret = '%s %s' % (command, ' '.join(map(pipes.quote, args)))
print('Running: ' + ret)
return ret
def _GetCommandOutputLines(command, args):
"""Return an iterable over the output lines of the given shell command."""
full_command = _CreateCommandLine(command, args)
proc = subprocess.Popen(full_command, shell=True, stdout=subprocess.PIPE)
return proc.stdout
def _RunCommand(command, args):
"""Run the given shell command."""
for line in _GetCommandOutputLines(command, args):
print(line, end=None)
def _GetCommandOutputWithInput(command, stdin_text):
"""Return the output of the given command fed the stdin_text."""
print(command)
proc = subprocess.Popen(command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
shell=True)
return proc.communicate(input=stdin_text)[0]
def PrintFileDiff(old_file_contents, new_file_contents):
"""Print a unified diff between files, specified as lists of lines."""
diff = difflib.unified_diff(old_file_contents, new_file_contents)
@ -2040,17 +1981,13 @@ def GetFixedFile(iwyu_record, flags):
of the include-what-you-use script (run at verbose level 1 or
higher) pertaining to a single source file.
iwyu_record.filename indicates what file to edit.
flags: commandline flags, as parsed by optparse. We use
flags.dry_run and flags.checkout_command, which determine
whether we try to fix unwritable files, and other flags
indirectly.
flags: commandline flags, as parsed by optparse.
Returns:
A list of strings representing the 'fixed' file, if the file has
changed, or None if the file hasn't changed at all.
"""
file_contents = _ReadWriteableFile(iwyu_record.filename,
flags.dry_run or flags.checkout_command)
file_contents = _ReadFile(iwyu_record.filename)
if not file_contents:
print('(skipping %s: not a writable file)' % iwyu_record.filename)
return None
@ -2070,68 +2007,6 @@ def GetFixedFile(iwyu_record, flags):
return fixed_lines
def CreateCLForCheckoutCommand(checkout_command, invoking_command):
"""Create a CL using a command inferred from the checkout_command.
Arguments:
checkout_commmand: The command specified with --checkout_command
invoking_command: If not None, the command line passed to iwyu.py
Returns:
A CL number on success, else None.
If the checkout_command is 'p4|g4|v4 edit' then attempt to create a
CL with an appropriate description. On success, return the CL
number. Otherwise return None.
"""
if checkout_command in ('p4 edit', 'g4 edit', 'v4 edit'):
what4 = checkout_command.split(' ')[0]
else:
# We don't know how to create a CL. Punt.
return None
description_lines = ['\tInclude what you use.\n']
if invoking_command:
description_lines.append(
'\tThe following command was run to modify the files:\n')
description_lines.append('\t' + invoking_command + '\n')
# The change description must be of a certain form. First invoke
# '<what4> client -o' to create a template, then replace
# '<enter description here>' with the desired description.
# The lines after that are File lines, which we will discard
# here, because we will add the files via '<what4> edit -c <CL#>'.
input_lines = []
description_added = False
output_lines = []
for line in _GetCommandOutputLines(what4, ['change', '-o']):
output_lines.append(line)
if line == '\t<enter description here>\n':
input_lines.extend(description_lines)
description_added = True
break
else:
input_lines.append(line)
if not description_added:
print('ERROR: Didn\'t find "\t<enter description here>" in'
' "%s change -o" output' % what4)
for line in output_lines:
print(line, end=None)
return None
input_text = ''.join(input_lines)
output = _GetCommandOutputWithInput('%s change -i'
% what4, input_text)
# Parse output for "Changelist XXX created."
m = re.match(r'Change (\d+) created.', output)
if not m:
print('ERROR: Unexpected change creation output "%s"' % output)
return None
return m.group(1)
def FixManyFiles(iwyu_records, flags):
"""Given a list of iwyu_records, fix each file listed in the record.
@ -2145,49 +2020,20 @@ def FixManyFiles(iwyu_records, flags):
the parsed output of the include-what-you-use script (run at
verbose level 1 or higher) pertaining to a single source file.
iwyu_record.filename indicates what file to edit.
flags: commandline flags, as parsed by optparse. We use
flags.dry_run and flags.checkout_command, which determine
how we read and write files, and other flags indirectly.
flags: commandline flags, as parsed by optparse..
Returns:
The number of files fixed (as opposed to ones that needed no fixing).
"""
file_and_fix_pairs = []
files_to_checkout = []
for iwyu_record in iwyu_records:
try:
fixed_lines = GetFixedFile(iwyu_record, flags)
if fixed_lines is not None:
file_and_fix_pairs.append((iwyu_record.filename, fixed_lines))
if flags.checkout_command and \
not flags.dry_run and \
not os.access(iwyu_record.filename, os.W_OK):
files_to_checkout.append(iwyu_record.filename)
except FixIncludesError as why:
print('ERROR: %s - skipping file %s' % (why, iwyu_record.filename))
# It's much faster to check out all the files at once.
if flags.checkout_command and files_to_checkout:
checkout_command = flags.checkout_command
# If --create_cl_if_possible was specified, AND if all files that
# need fixing are not writable (not opened for edit in this
# client), try to create a CL to contain those edits. This is to
# avoid inadvertently creating multiple CLs.
if (flags.create_cl_if_possible and
len(files_to_checkout) == len(file_and_fix_pairs)):
cl = CreateCLForCheckoutCommand(flags.checkout_command,
flags.invoking_command_line)
if cl:
# Assumption: the checkout command supports the '-c <CL#>' syntax.
checkout_command += (' -c ' + cl)
# Else if --append_to_cl was specified, add the files that need
# editing to the specified (existing) CL.
# NOTE: If one or more of these files is already open, this will
# presumably fail.
elif flags.append_to_cl:
checkout_command += (' -c %d' % flags.append_to_cl)
_RunCommand(checkout_command, files_to_checkout)
if not flags.dry_run:
for filename, fixed_lines in file_and_fix_pairs:
_WriteFileContents(filename, fixed_lines)
@ -2264,10 +2110,8 @@ def SortIncludesInFiles(files_to_process, flags):
"""For each file in files_to_process, sort its #includes.
This reads each input file, sorts the #include lines, and replaces
the input file with the result. Like ProcessIWYUOutput, this
requires that the file be writable, or that flags.checkout_command
be set. SortIncludesInFiles does not add or remove any #includes.
It also ignores forward-declares.
the input file with the result. SortIncludesInFiles does not add
or remove any #includes. It also ignores forward-declares.
Arguments:
files_to_process: a list (or set) of filenames.
@ -2278,7 +2122,6 @@ def SortIncludesInFiles(files_to_process, flags):
The number of files that had to be modified (because they weren't
already all correct, that is, already in sorted order).
"""
num_fixes_made = 0
sort_only_iwyu_records = []
for filename in files_to_process:
# An empty iwyu record has no adds or deletes, so its only effect
@ -2324,26 +2167,6 @@ def main(argv):
help=('fix_includes.py will skip editing any file whose'
' name matches this regular expression.'))
parser.add_option('--checkout_command', default=None,
help=('A command, such as "p4 edit", to run on all the'
' non-writeable files before modifying them. The'
' filenames will be appended to the command after'
' a space. The command will not be run on any file'
' that does not need to change.'))
parser.add_option('--create_cl_if_possible', action='store_true',
default=True,
help=('If --checkout_command is "p4|g4|v4 edit" and'
' all files to be modified needed to be checked out,'
' then create a CL containing those files.'))
parser.add_option('--nocreate_cl_if_possible', action='store_false',
dest='create_cl_if_possible')
parser.add_option('--append_to_cl', action='store',
default=None, type='int', dest='append_to_cl',
help=('If provided, with a checkout_command, add files'
' that need fixing to the specified existing CL.'))
parser.add_option('--separate_project_includes', default=None,
help=('Sort #includes for current project separately'
' from all other #includes. This flag specifies'
@ -2357,7 +2180,6 @@ def main(argv):
help=('Internal flag used by iwyu.py, It should be the'
' command line used to invoke iwyu.py'))
parser.add_option('-m', '--keep_iwyu_namespace_format', action='store_true',
default=False,
help=('Keep forward-declaration namespaces in IWYU format, '
@ -2380,10 +2202,6 @@ def main(argv):
not flags.separate_project_includes.endswith('/')):
flags.separate_project_includes += os.path.sep
if flags.append_to_cl and flags.create_cl_if_possible:
sys.exit('FATAL ERROR: At most one of --append_to_cl and '
'--create_cl_if_possible allowed')
if flags.sort_only:
if not files_to_modify:
sys.exit('FATAL ERROR: -s flag requires a list of filenames')

View File

@ -36,52 +36,12 @@ class FakeFlags(object):
self.comments = True
self.dry_run = False
self.ignore_re = None
self.checkout_command = None
self.safe_headers = False
self.separate_project_includes = None
self.create_cl_if_possible = True
self.invoking_command_line = 'iwyu.py my_targets'
self.find_affected_targets = True
self.keep_iwyu_namespace_format = False
class FakeRunCommand(object):
def __init__(self):
self.command = None
self.args = None
def RunCommand(self, command, args):
self.command = command
self.args = args
class FakeGetCommandOutputWithInput(object):
def __init__(self, stdout):
self.stdout = stdout
self.command = None
self.stdin = None
def GetCommandOutputWithInput(self, command, stdin):
print('GetCommandOutputWithInput(%s, ...)' % command)
self.command = command
self.stdin = stdin
return self.stdout
class FakeGetCommandOutputLines(object):
def __init__(self):
self.stdout_lines_map = {}
self.command = None
self.args = None
def GetCommandOutputLines(self, command, args):
print('GetCommandOutputLines(%s, %s)' % (command, args))
self.command = command
self.args = args
return self.stdout_lines_map[command]
def SetCommandOutputLines(self, command, lines):
lines = [line + '\n' for line in lines]
self.stdout_lines_map[command] = lines
class FixIncludesBase(unittest.TestCase):
"""Does setup that every test will want."""
@ -107,50 +67,10 @@ class FixIncludesBase(unittest.TestCase):
fix_includes._WriteFileContents = \
lambda filename, contents: self.actual_after_contents.extend(contents)
# Stub out OS writeability check to say all files are writeable.
fix_includes.os.access = \
lambda filename, flags: True
# Stub out the checkout command; keep a list of all files checked out.
self.fake_checkout_command = FakeRunCommand()
fix_includes._RunCommand = self.fake_checkout_command.RunCommand
# Stub out the CL creation command.
self.fake_cl_creation_command = (
FakeGetCommandOutputWithInput('Change 1234 created.\n'))
fix_includes._GetCommandOutputWithInput = (
self.fake_cl_creation_command.GetCommandOutputWithInput)
# Create a generic 'stubber'.
self.fake_get_command_output_lines = FakeGetCommandOutputLines()
fix_includes._GetCommandOutputLines = (
self.fake_get_command_output_lines.GetCommandOutputLines)
# Stub out the CL template command.
self.fake_get_command_output_lines.SetCommandOutputLines(
'g4', ['Description:',
'\t<enter description here>',
'',
'Files:',
'\tthis_file_should_not_appear_in_the_cl'])
# Stub out the find_clients_of_files command.
self.find_clients_of_files_command = (
'/google/data/ro/projects/cymbal/tools/find_clients_of_files.par')
self.fake_get_command_output_lines.SetCommandOutputLines(
self.find_clients_of_files_command,
['==== 1 targets from 1 packages are affected',
'==== targets',
'//foo/bar:a'])
# Stub out stdout
self.stdout_stub = StringIO()
fix_includes.sys.stdout = self.stdout_stub
def SetFindClientsOfFilesOutput(self, output_lines):
self.fake_get_command_output_lines.SetCommandOutputLines(
self.find_clients_of_files_command, output_lines)
def RegisterFileContents(self, file_contents_map):
"""Parses and stores the given map from filename to file-contents.
@ -231,11 +151,6 @@ class FixIncludesBase(unittest.TestCase):
if expected_num_modified_files is not None:
self.assertEqual(expected_num_modified_files, num_modified_files)
def MakeFilesUnwriteable(self):
"""Stub out OS writeability check to say all files are NOT writeable."""
fix_includes.os.access = \
lambda filename, flags: False
class FixIncludesTest(FixIncludesBase):
"""End-to-end tests from input file to output file."""
@ -2895,36 +2810,6 @@ The full include-list for no_key:
iwyu_output.splitlines(),
self.flags)
def testNotWriteable(self):
"""Test that files don't get rewritten if they are not writeable."""
infile = """\
// Copyright 2010
#include <notused.h>
#include "used.h"
int main() { return 0; }
"""
iwyu_output = """\
unwritable should add these lines:
#include <stdio.h>
#include "used2.h"
unwritable should remove these lines:
- #include <notused.h> // lines 3-3
The full include-list for unwritable:
#include <stdio.h>
#include "used.h"
#include "used2.h"
---
"""
self.RegisterFileContents({'unwritable': infile})
self.MakeFilesUnwriteable()
# No files are written, because they are not writeable.
self.ProcessAndTest(iwyu_output, unedited_files=['unwritable'])
def testFileSpecifiedOnCommandline(self):
"""Test we limit editing to files specified on the commandline."""
changed_infile = """\
@ -3101,7 +2986,6 @@ int main() { return 0; }
self.actual_after_contents)
self.assertEqual(2, num_files_modified)
def testSortingIncludesAlreadySorted(self):
"""Tests sorting includes only, when includes are already sorted."""
infile = """\