Skip to content

Instantly share code, notes, and snippets.

@jlebar
Created August 31, 2012 01:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jlebar/3547640 to your computer and use it in GitHub Desktop.
Save jlebar/3547640 to your computer and use it in GitHub Desktop.
Reorder a patch file so it's easier to review
#!/usr/bin/env python
"""Re-order the files in a patch according to a set of rules.
Input is accepted on stdin, and written to stdout.
Usage: cat patch | reorder-patch > reordered-patch
"""
import sys
import os
import re
def splitdiff(f):
"""Split a file containing a diff into pieces, one per file.
We always return a patch header as the first piece; if the given patch does
not have a header, we return the empty string.
"""
lines = ''
for line in f:
if line.startswith('diff'):
yield lines
lines = ''
lines += line
yield lines
def get_filename(piece):
"""Get the filename from a diff piece."""
try:
# The filename is the last space-separated token in the first line of
# the piece. This is a wildly inefficient implementation.
return piece.split('\n')[0].split(' ')[-1]
except:
return ''
def compare_diff_pieces(piece_a, piece_b):
"""Determine whether piece_a should go above or below piece_b, according to
the following rules:
* idl come first, then ipdl files, then other files.
* .h files come before their corresponding .cpp files.
* Files with "/test/" or "/tests/" in their path come after other files.
* Makefiles come last in their directory.
* Otherwise sort in lexicographic order.
"""
a = get_filename(piece_a)
b = get_filename(piece_b)
def bool_comparator(fn):
"""Transform a boolean function into a comparison function over a and
b, such that a < b if fn(a) and not fn(b)."""
val_a = fn(a)
val_b = fn(b)
if val_a and not val_b:
return -1
elif val_b and not val_a:
return 1
return 0
def comparators():
# Think of these bool_comparators as: You get sorted towards the top of
# the patch if you match the lambda.
yield bool_comparator(lambda x: x.endswith('.idl'))
yield bool_comparator(lambda x: x.endswith('.ipdl'))
if a.rsplit('.', 1)[0] == b.rsplit('.', 1)[0]:
yield bool_comparator(lambda x: x.endswith('.h'))
yield bool_comparator(lambda x: not (('/test/' in x) or ('/tests/' in x)))
if a.rsplit('/', 1)[0] == b.rsplit('/', 1)[0]:
yield bool_comparator(lambda x: 'Makefile.in' not in x)
yield cmp(a, b)
for res in comparators():
if res != 0:
return res
return 0
def reorder(infile, outfile):
pieces = splitdiff(infile)
# outfile gets the first piece first (it's the patch header), then we sort
# the remaining pieces.
outfile.write(''.join(next(pieces)))
diff_pieces = list(pieces)
diff_pieces.sort(compare_diff_pieces)
outfile.write(''.join(diff_pieces))
if __name__ == '__main__':
infile = sys.stdin
outfile = sys.stdout
reorder(infile, outfile)
@mmuman
Copy link

mmuman commented Dec 11, 2014

Very useful for comparing svn diffs that somehow never get generated in the same order twice... Had to check for "Index" in pieces though:

https://gist.github.com/mmuman/102fd59f99898dfd6a25/03f46c5f7931bc22c416bb367d4e8938a16edd54

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment