Skip to content

Commit

Permalink
Add resolve-blank-lines script
Browse files Browse the repository at this point in the history
  • Loading branch information
mernst committed Nov 2, 2023
1 parent a406a2e commit 7a1ad35
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 10 deletions.
42 changes: 42 additions & 0 deletions src/scripts/merge_tools/resolve-blank-lines
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
# bash, not POSIX sh, because of "readarray".

# This script edits files in place to resolve conflict markers in which the
# differences are only in whitespace, including blank lines. This script
# leaves other conflict markers untouched.
# Usage:
# resolve-blank-lines [file ...]
#
# The script works on all files given on the command line.
# If none are given, the script works on all files in or under the current directory.
#
# The status code is 0 (success) if all conflicts are resolved.
# The status code is 1 (failure) if any conflict remains.
#
# This is not a git mergetool. A git mergetool is given the base, parent 1, and
# parent 2 files, all without conflict markers.
# However, this can be run after a git mergetool that leaves conflict markers
# in files, as the default git mergetool does.

# Comparison to other tools
#
# `git diff` has a `--ignore-blank-lines` option, but `git merge` has
# no option for ignoring blank lines. This script addresses that shortcoming.

if [ "$#" -eq 0 ] ; then
readarray -t files < <(grep -l -r '^<<<<<<< HEAD' .)
else
files=("$@")
fi

SCRIPTDIR="$(cd "$(dirname "$0")" && pwd -P)"

status=0

for file in "${files[@]}" ; do
if ! "${SCRIPTDIR}"/resolve-conflicts.py --blank_lines "$file" ; then
status=1
fi
done

exit $status
74 changes: 64 additions & 10 deletions src/scripts/merge_tools/resolve-conflicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
"""Edits a file in place to remove certain conflict markers.
Usage: resolve-conflicts.py [options] <filenme>
Only one option is acted upon. To address multiple types of conflict markers,
run the program more than once.
--adjacent_lines: Resolves conflicts on adjacent lines, by accepting both edits.
This is like the behavior of SVN and darcs, but different than the default
behavior of Git, Mercurial, and Bazaar.
--blank_lines: Resolves conflicts due to blank lines.
If "ours" and "theirs" differ only in whitespace (including blank lines), then accept "ours".
--java_imports: Resolves conflicts related to Java import statements
The output includes every `import` statements that is in either of the parents.
--adjacent_lines: Resolves conflicts on adjacent lines, by accepting both edits.
Exit status is 0 (success) if no conflicts remain.
Exit status is 1 (failure) if conflicts remain.
"""
Expand Down Expand Up @@ -40,6 +47,11 @@ def main(): # pylint: disable=too-many-locals
action="store_true",
help="If set, resolve conflicts related to Java import statements",
)
arg_parser.add_argument(
"--blank_lines",
action="store_true",
help="If set, resolve conflicts due to blank lines",
)
arg_parser.add_argument(
"--adjacent_lines",
action="store_true",
Expand All @@ -48,6 +60,17 @@ def main(): # pylint: disable=too-many-locals
args = arg_parser.parse_args()
filename = args.filename

num_options = 0
if args.adjacent_lines:
num_options += 1
if args.blank_lines:
num_options += 1
if args.java_imports:
num_options += 1
if num_options != 1:
print("resolve-conflicts.py: supply exactly one option.")
sys.exit(1)

with open(filename) as file:
lines = file.readlines()

Expand All @@ -64,7 +87,12 @@ def main(): # pylint: disable=too-many-locals
else:
(base, parent1, parent2, num_lines) = conflict
merged = merge(
base, parent1, parent2, args.java_imports, args.adjacent_lines
base,
parent1,
parent2,
args.adjacent_lines,
args.blank_lines,
args.java_imports,
)
if merged is None:
tmp.write(lines[i])
Expand Down Expand Up @@ -160,8 +188,9 @@ def merge(
base: List[str],
parent1: List[str],
parent2: List[str],
java_imports: bool,
adjacent_lines: bool,
blank_lines: bool,
java_imports: bool,
) -> Union[List[str], None]:
"""Given text for the base and two parents, return merged text.
Expand All @@ -174,6 +203,16 @@ def merge(
a list of lines, or None if it cannot do merging.
"""

if adjacent_lines:
adjacent_line_merge = merge_edits_on_different_lines(base, parent1, parent2)
if adjacent_line_merge is not None:
return adjacent_line_merge

if blank_lines:
blank_line_merge = merge_blank_lines(base, parent1, parent2)
if blank_line_merge is not None:
return blank_line_merge

if java_imports:
if (
all_import_lines(base)
Expand All @@ -185,11 +224,6 @@ def merge(
result.sort()
return result

if adjacent_lines:
adjacent_line_merge = merge_edits_on_different_lines(base, parent1, parent2)
if adjacent_line_merge is not None:
return adjacent_line_merge

return None


Expand Down Expand Up @@ -255,7 +289,8 @@ def merge_base_is_prefix_or_suffix(
deleted all the lines, possibly replacing them by something else. (We know
this because there is no common line in base and parent2. If there were, it
would also be in parent1, and the hunk would have been split into two at the
common line that's in all three texts.)
common line that's in all three texts. The Git Merge output doesn't include
any common context lines within the conflict markers.)
We know the relative position of the additions in parent1.
"""
base_len = len(base)
Expand Down Expand Up @@ -288,6 +323,25 @@ def is_subsequence(s1: Sequence[T], s2: Sequence[T]) -> bool:
return i == n


def merge_blank_lines(base, parent1, parent2):
"Returns parent1 if parent1 and parent2 differ only in whitespace."
if with_one_space(parent1) == with_one_space(parent2):
return parent1
return None


def with_one_space(lines):
"""Turns a list of strings into a single string, with each run of whitespace replaced
by a single space."""
# TODO: This could be more efficient. Even better, I could write a loop in
# merge_blank_lines that wouldn't need to create new strings at all. But this is
# expedient to write and is probably fast enough.
result_lines = []
for line in lines:
result_lines += line.split()
return " ".join(result_lines)


def debug_print(*args):
"""If debugging is enabled, pass the arguments to `print`."""
if debug:
Expand Down

0 comments on commit 7a1ad35

Please sign in to comment.