Skip to content

Instantly share code, notes, and snippets.

@PlasmaPower
Last active November 22, 2023 06:02
Show Gist options
  • Save PlasmaPower/795af590f88cfb5e21c5ad9b8a32afdf to your computer and use it in GitHub Desktop.
Save PlasmaPower/795af590f88cfb5e21c5ad9b8a32afdf to your computer and use it in GitHub Desktop.
Git diff intersection finder
#!/usr/bin/env python3
import argparse
import sys
from pathlib import PurePath
from unidiff import PatchSet
from unidiff.constants import LINE_TYPE_CONTEXT
parser = argparse.ArgumentParser(prog="diff-intersection")
parser.add_argument("base_patch", help="The base patch to be filtered")
parser.add_argument("filter_patches", nargs="*", help="Filter the base patch down to common modifications with these patches")
parser.add_argument("--only-hunks", action='store_true', help="Don't filter lines; only filter hunks")
parser.add_argument("--ignore-files", default=[], nargs="*", help="Filter out any diffs in these file globs")
args = parser.parse_args()
with open(args.base_patch) as f:
base_patch = PatchSet(f.read())
filter_patches = []
for path in args.filter_patches:
with open(path) as f:
filter_patches.append(PatchSet(f.read()))
def extract_lines(patch, output=set()):
for file in patch:
for hunk in file:
current_line = None
for line in hunk:
if line.target_line_no is not None:
current_line = line.target_line_no
if not line.is_context:
output.add((file.target_file or file.source_file, current_line))
return output
filter_lines = set()
for patch in filter_patches:
extract_lines(patch, filter_lines)
remove_files = []
for file in base_patch:
file_ignored = False
for ignore_file in args.ignore_files:
if PurePath(file.target_file[2:]).match(ignore_file) or PurePath(file.source_file[2:]).match(ignore_file):
remove_files.append(file)
file_ignored = True
if file_ignored:
continue
found_file_match = False
remove_hunks = []
for hunk in file:
found_hunk_match = False
current_line = None
remove_lines = []
for line in hunk:
if line.target_line_no is not None:
current_line = line.target_line_no
if not line.is_context:
matches = (file.target_file or file.source_file, current_line) in filter_lines
if matches:
found_hunk_match = True
found_file_match = True
elif args.only_hunks:
pass
elif line.is_added:
line.line_type = LINE_TYPE_CONTEXT
elif line.is_removed:
remove_lines.append(line)
if not found_hunk_match:
remove_hunks.append(hunk)
else:
for line in remove_lines:
hunk.remove(line)
if not found_file_match:
remove_files.append(file)
else:
for hunk in remove_hunks:
file.remove(hunk)
for file in remove_files:
base_patch.remove(file)
files_changed = 0
insertions = 0
deletions = 0
for file in base_patch:
files_changed += 1
for hunk in file:
for line in hunk:
if line.is_added:
insertions += 1
elif line.is_removed:
deletions += 1
sys.stderr.write(f" {files_changed} files changed, {insertions} insertions(+), {deletions} deletions(-)\n")
print(base_patch)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment