@@ -1,558 +1 @@
-# Copyright (C) 2004, 2005 Aaron Bentley
-# <aaron.bentley@utoronto.ca>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-class PatchSyntax(Exception):
- def __init__(self, msg):
- Exception.__init__(self, msg)
-
-
-class MalformedPatchHeader(PatchSyntax):
- def __init__(self, desc, line):
- self.desc = desc
- self.line = line
- msg = "Malformed patch header. %s\n%r" % (self.desc, self.line)
- PatchSyntax.__init__(self, msg)
-
-class MalformedHunkHeader(PatchSyntax):
- def __init__(self, desc, line):
- self.desc = desc
- self.line = line
- msg = "Malformed hunk header. %s\n%r" % (self.desc, self.line)
- PatchSyntax.__init__(self, msg)
-
-class MalformedLine(PatchSyntax):
- def __init__(self, desc, line):
- self.desc = desc
- self.line = line
- msg = "Malformed line. %s\n%s" % (self.desc, self.line)
- PatchSyntax.__init__(self, msg)
-
-def get_patch_names(iter_lines):
- try:
- line = iter_lines.next()
- if not line.startswith("--- "):
- raise MalformedPatchHeader("No orig name", line)
- else:
- orig_name = line[4:].rstrip("\n")
- except StopIteration:
- raise MalformedPatchHeader("No orig line", "")
- try:
- line = iter_lines.next()
- if not line.startswith("+++ "):
- raise PatchSyntax("No mod name")
- else:
- mod_name = line[4:].rstrip("\n")
- except StopIteration:
- raise MalformedPatchHeader("No mod line", "")
- return (orig_name, mod_name)
-
-def parse_range(textrange):
- """Parse a patch range, handling the "1" special-case
-
- :param textrange: The text to parse
- :type textrange: str
- :return: the position and range, as a tuple
- :rtype: (int, int)
- """
- tmp = textrange.split(',')
- if len(tmp) == 1:
- pos = tmp[0]
- range = "1"
- else:
- (pos, range) = tmp
- pos = int(pos)
- range = int(range)
- return (pos, range)
-
-
-def hunk_from_header(line):
- if not line.startswith("@@") or not line.endswith("@@\n") \
- or not len(line) > 4:
- raise MalformedHunkHeader("Does not start and end with @@.", line)
- try:
- (orig, mod) = line[3:-4].split(" ")
- except Exception, e:
- raise MalformedHunkHeader(str(e), line)
- if not orig.startswith('-') or not mod.startswith('+'):
- raise MalformedHunkHeader("Positions don't start with + or -.", line)
- try:
- (orig_pos, orig_range) = parse_range(orig[1:])
- (mod_pos, mod_range) = parse_range(mod[1:])
- except Exception, e:
- raise MalformedHunkHeader(str(e), line)
- if mod_range < 0 or orig_range < 0:
- raise MalformedHunkHeader("Hunk range is negative", line)
- return Hunk(orig_pos, orig_range, mod_pos, mod_range)
-
-
-class HunkLine:
- def __init__(self, contents):
- self.contents = contents
-
- def get_str(self, leadchar):
- if self.contents == "\n" and leadchar == " " and False:
- return "\n"
- if not self.contents.endswith('\n'):
- terminator = '\n' + NO_NL
- else:
- terminator = ''
- return leadchar + self.contents + terminator
-
-
-class ContextLine(HunkLine):
- def __init__(self, contents):
- HunkLine.__init__(self, contents)
-
- def __str__(self):
- return self.get_str(" ")
-
-
-class InsertLine(HunkLine):
- def __init__(self, contents):
- HunkLine.__init__(self, contents)
-
- def __str__(self):
- return self.get_str("+")
-
-
-class RemoveLine(HunkLine):
- def __init__(self, contents):
- HunkLine.__init__(self, contents)
-
- def __str__(self):
- return self.get_str("-")
-
-NO_NL = '\\ No newline at end of file\n'
-__pychecker__="no-returnvalues"
-
-def parse_line(line):
- if line.startswith("\n"):
- return ContextLine(line)
- elif line.startswith(" "):
- return ContextLine(line[1:])
- elif line.startswith("+"):
- return InsertLine(line[1:])
- elif line.startswith("-"):
- return RemoveLine(line[1:])
- elif line == NO_NL:
- return NO_NL
- else:
- raise MalformedLine("Unknown line type", line)
-__pychecker__=""
-
-
-class Hunk:
- def __init__(self, orig_pos, orig_range, mod_pos, mod_range):
- self.orig_pos = orig_pos
- self.orig_range = orig_range
- self.mod_pos = mod_pos
- self.mod_range = mod_range
- self.lines = []
-
- def get_header(self):
- return "@@ -%s +%s @@\n" % (self.range_str(self.orig_pos,
- self.orig_range),
- self.range_str(self.mod_pos,
- self.mod_range))
-
- def range_str(self, pos, range):
- """Return a file range, special-casing for 1-line files.
-
- :param pos: The position in the file
- :type pos: int
- :range: The range in the file
- :type range: int
- :return: a string in the format 1,4 except when range == pos == 1
- """
- if range == 1:
- return "%i" % pos
- else:
- return "%i,%i" % (pos, range)
-
- def __str__(self):
- lines = [self.get_header()]
- for line in self.lines:
- lines.append(str(line))
- return "".join(lines)
-
- def shift_to_mod(self, pos):
- if pos < self.orig_pos-1:
- return 0
- elif pos > self.orig_pos+self.orig_range:
- return self.mod_range - self.orig_range
- else:
- return self.shift_to_mod_lines(pos)
-
- def shift_to_mod_lines(self, pos):
- assert (pos >= self.orig_pos-1 and pos <= self.orig_pos+self.orig_range)
- position = self.orig_pos-1
- shift = 0
- for line in self.lines:
- if isinstance(line, InsertLine):
- shift += 1
- elif isinstance(line, RemoveLine):
- if position == pos:
- return None
- shift -= 1
- position += 1
- elif isinstance(line, ContextLine):
- position += 1
- if position > pos:
- break
- return shift
-
-def iter_hunks(iter_lines):
- hunk = None
- for line in iter_lines:
- if line == "\n":
- if hunk is not None:
- yield hunk
- hunk = None
- continue
- if hunk is not None:
- yield hunk
- hunk = hunk_from_header(line)
- orig_size = 0
- mod_size = 0
- while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
- hunk_line = parse_line(iter_lines.next())
- hunk.lines.append(hunk_line)
- if isinstance(hunk_line, (RemoveLine, ContextLine)):
- orig_size += 1
- if isinstance(hunk_line, (InsertLine, ContextLine)):
- mod_size += 1
- if hunk is not None:
- yield hunk
-
-class Patch:
- def __init__(self, oldname, newname):
- self.oldname = oldname
- self.newname = newname
- self.hunks = []
-
- def __str__(self):
- ret = self.get_header()
- ret += "".join([str(h) for h in self.hunks])
- return ret
-
- def get_header(self):
- return "--- %s\n+++ %s\n" % (self.oldname, self.newname)
-
- def stats_str(self):
- """Return a string of patch statistics"""
- removes = 0
- inserts = 0
- for hunk in self.hunks:
- for line in hunk.lines:
- if isinstance(line, InsertLine):
- inserts+=1;
- elif isinstance(line, RemoveLine):
- removes+=1;
- return "%i inserts, %i removes in %i hunks" % \
- (inserts, removes, len(self.hunks))
-
- def pos_in_mod(self, position):
- newpos = position
- for hunk in self.hunks:
- shift = hunk.shift_to_mod(position)
- if shift is None:
- return None
- newpos += shift
- return newpos
-
- def iter_inserted(self):
- """Iteraties through inserted lines
-
- :return: Pair of line number, line
- :rtype: iterator of (int, InsertLine)
- """
- for hunk in self.hunks:
- pos = hunk.mod_pos - 1;
- for line in hunk.lines:
- if isinstance(line, InsertLine):
- yield (pos, line)
- pos += 1
- if isinstance(line, ContextLine):
- pos += 1
-
-def parse_patch(iter_lines):
- (orig_name, mod_name) = get_patch_names(iter_lines)
- patch = Patch(orig_name, mod_name)
- for hunk in iter_hunks(iter_lines):
- patch.hunks.append(hunk)
- return patch
-
-
-def iter_file_patch(iter_lines):
- saved_lines = []
- for line in iter_lines:
- if line.startswith('=== '):
- continue
- elif line.startswith('--- '):
- if len(saved_lines) > 0:
- yield saved_lines
- saved_lines = []
- saved_lines.append(line)
- if len(saved_lines) > 0:
- yield saved_lines
-
-
-def iter_lines_handle_nl(iter_lines):
- """
- Iterates through lines, ensuring that lines that originally had no
- terminating \n are produced without one. This transformation may be
- applied at any point up until hunk line parsing, and is safe to apply
- repeatedly.
- """
- last_line = None
- for line in iter_lines:
- if line == NO_NL:
- assert last_line.endswith('\n')
- last_line = last_line[:-1]
- line = None
- if last_line is not None:
- yield last_line
- last_line = line
- if last_line is not None:
- yield last_line
-
-
-def parse_patches(iter_lines):
- iter_lines = iter_lines_handle_nl(iter_lines)
- return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
-
-
-def difference_index(atext, btext):
- """Find the indext of the first character that differs betweeen two texts
-
- :param atext: The first text
- :type atext: str
- :param btext: The second text
- :type str: str
- :return: The index, or None if there are no differences within the range
- :rtype: int or NoneType
- """
- length = len(atext)
- if len(btext) < length:
- length = len(btext)
- for i in range(length):
- if atext[i] != btext[i]:
- return i;
- return None
-
-class PatchConflict(Exception):
- def __init__(self, line_no, orig_line, patch_line):
- orig = orig_line.rstrip('\n')
- patch = str(patch_line).rstrip('\n')
- msg = 'Text contents mismatch at line %d. Original has "%s",'\
- ' but patch says it should be "%s"' % (line_no, orig, patch)
- Exception.__init__(self, msg)
-
-
-def iter_patched(orig_lines, patch_lines):
- """Iterate through a series of lines with a patch applied.
- This handles a single file, and does exact, not fuzzy patching.
- """
- if orig_lines is not None:
- orig_lines = orig_lines.__iter__()
- seen_patch = []
- patch_lines = iter_lines_handle_nl(patch_lines.__iter__())
- get_patch_names(patch_lines)
- line_no = 1
- for hunk in iter_hunks(patch_lines):
- while line_no < hunk.orig_pos:
- orig_line = orig_lines.next()
- yield orig_line
- line_no += 1
- for hunk_line in hunk.lines:
- seen_patch.append(str(hunk_line))
- if isinstance(hunk_line, InsertLine):
- yield hunk_line.contents
- elif isinstance(hunk_line, (ContextLine, RemoveLine)):
- orig_line = orig_lines.next()
- if orig_line != hunk_line.contents:
- raise PatchConflict(line_no, orig_line, "".join(seen_patch))
- if isinstance(hunk_line, ContextLine):
- yield orig_line
- else:
- assert isinstance(hunk_line, RemoveLine)
- line_no += 1
-
-import unittest
-import os.path
-class PatchesTester(unittest.TestCase):
- def datafile(self, filename):
- data_path = os.path.join(os.path.dirname(__file__), "testdata",
- filename)
- return file(data_path, "rb")
-
- def testValidPatchHeader(self):
- """Parse a valid patch header"""
- lines = "--- orig/commands.py\n+++ mod/dommands.py\n".split('\n')
- (orig, mod) = get_patch_names(lines.__iter__())
- assert(orig == "orig/commands.py")
- assert(mod == "mod/dommands.py")
-
- def testInvalidPatchHeader(self):
- """Parse an invalid patch header"""
- lines = "-- orig/commands.py\n+++ mod/dommands.py".split('\n')
- self.assertRaises(MalformedPatchHeader, get_patch_names,
- lines.__iter__())
-
- def testValidHunkHeader(self):
- """Parse a valid hunk header"""
- header = "@@ -34,11 +50,6 @@\n"
- hunk = hunk_from_header(header);
- assert (hunk.orig_pos == 34)
- assert (hunk.orig_range == 11)
- assert (hunk.mod_pos == 50)
- assert (hunk.mod_range == 6)
- assert (str(hunk) == header)
-
- def testValidHunkHeader2(self):
- """Parse a tricky, valid hunk header"""
- header = "@@ -1 +0,0 @@\n"
- hunk = hunk_from_header(header);
- assert (hunk.orig_pos == 1)
- assert (hunk.orig_range == 1)
- assert (hunk.mod_pos == 0)
- assert (hunk.mod_range == 0)
- assert (str(hunk) == header)
-
- def makeMalformed(self, header):
- self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
-
- def testInvalidHeader(self):
- """Parse an invalid hunk header"""
- self.makeMalformed(" -34,11 +50,6 \n")
- self.makeMalformed("@@ +50,6 -34,11 @@\n")
- self.makeMalformed("@@ -34,11 +50,6 @@")
- self.makeMalformed("@@ -34.5,11 +50,6 @@\n")
- self.makeMalformed("@@-34,11 +50,6@@\n")
- self.makeMalformed("@@ 34,11 50,6 @@\n")
- self.makeMalformed("@@ -34,11 @@\n")
- self.makeMalformed("@@ -34,11 +50,6.5 @@\n")
- self.makeMalformed("@@ -34,11 +50,-6 @@\n")
-
- def lineThing(self,text, type):
- line = parse_line(text)
- assert(isinstance(line, type))
- assert(str(line)==text)
-
- def makeMalformedLine(self, text):
- self.assertRaises(MalformedLine, parse_line, text)
-
- def testValidLine(self):
- """Parse a valid hunk line"""
- self.lineThing(" hello\n", ContextLine)
- self.lineThing("+hello\n", InsertLine)
- self.lineThing("-hello\n", RemoveLine)
-
- def testMalformedLine(self):
- """Parse invalid valid hunk lines"""
- self.makeMalformedLine("hello\n")
-
- def compare_parsed(self, patchtext):
- lines = patchtext.splitlines(True)
- patch = parse_patch(lines.__iter__())
- pstr = str(patch)
- i = difference_index(patchtext, pstr)
- if i is not None:
- print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
- self.assertEqual (patchtext, str(patch))
-
- def testAll(self):
- """Test parsing a whole patch"""
- patchtext = """--- orig/commands.py
-+++ mod/commands.py
-@@ -1337,7 +1337,8 @@
-
- def set_title(self, command=None):
- try:
-- version = self.tree.tree_version.nonarch
-+ version = pylon.alias_or_version(self.tree.tree_version, self.tree,
-+ full=False)
- except:
- version = "[no version]"
- if command is None:
-@@ -1983,7 +1984,11 @@
- version)
- if len(new_merges) > 0:
- if cmdutil.prompt("Log for merge"):
-- mergestuff = cmdutil.log_for_merge(tree, comp_version)
-+ if cmdutil.prompt("changelog for merge"):
-+ mergestuff = "Patches applied:\\n"
-+ mergestuff += pylon.changelog_for_merge(new_merges)
-+ else:
-+ mergestuff = cmdutil.log_for_merge(tree, comp_version)
- log.description += mergestuff
- log.save()
- try:
-"""
- self.compare_parsed(patchtext)
-
- def testInit(self):
- """Handle patches missing half the position, range tuple"""
- patchtext = \
-"""--- orig/__init__.py
-+++ mod/__init__.py
-@@ -1 +1,2 @@
- __docformat__ = "restructuredtext en"
-+__doc__ = An alternate Arch commandline interface
-"""
- self.compare_parsed(patchtext)
-
-
-
- def testLineLookup(self):
- import sys
- """Make sure we can accurately look up mod line from orig"""
- patch = parse_patch(self.datafile("diff"))
- orig = list(self.datafile("orig"))
- mod = list(self.datafile("mod"))
- removals = []
- for i in range(len(orig)):
- mod_pos = patch.pos_in_mod(i)
- if mod_pos is None:
- removals.append(orig[i])
- continue
- assert(mod[mod_pos]==orig[i])
- rem_iter = removals.__iter__()
- for hunk in patch.hunks:
- for line in hunk.lines:
- if isinstance(line, RemoveLine):
- next = rem_iter.next()
- if line.contents != next:
- sys.stdout.write(" orig:%spatch:%s" % (next,
- line.contents))
- assert(line.contents == next)
- self.assertRaises(StopIteration, rem_iter.next)
-
- def testFirstLineRenumber(self):
- """Make sure we handle lines at the beginning of the hunk"""
- patch = parse_patch(self.datafile("insert_top.patch"))
- assert (patch.pos_in_mod(0)==1)
-
-def test():
- patchesTestSuite = unittest.makeSuite(PatchesTester,'test')
- runner = unittest.TextTestRunner(verbosity=0)
- return runner.run(patchesTestSuite)
-
-
-if __name__ == "__main__":
- test()
-# arch-tag: d1541a25-eac5-4de9-a476-08a7cecd5683
+Total contents change