313
290
yield saved_lines
316
def iter_lines_handle_nl(iter_lines):
318
Iterates through lines, ensuring that lines that originally had no
319
terminating \n are produced without one. This transformation may be
320
applied at any point up until hunk line parsing, and is safe to apply
324
for line in iter_lines:
326
assert last_line.endswith('\n')
327
last_line = last_line[:-1]
329
if last_line is not None:
332
if last_line is not None:
336
293
def parse_patches(iter_lines):
337
iter_lines = iter_lines_handle_nl(iter_lines)
338
294
return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
298
"""A line associated with the log that produced it"""
299
def __init__(self, text, log=None):
303
class CantGetRevisionData(Exception):
304
def __init__(self, revision):
305
Exception.__init__(self, "Can't get data for revision %s" % revision)
307
def annotate_file2(file_lines, anno_iter):
308
for result in iter_annotate_file(file_lines, anno_iter):
313
def iter_annotate_file(file_lines, anno_iter):
314
lines = [AnnotateLine(f) for f in file_lines]
317
for result in anno_iter:
318
if isinstance(result, progress.Progress):
321
log, iter_inserted, patch = result
322
for (num, line) in iter_inserted:
325
for cur_patch in patches:
326
num = cur_patch.pos_in_mod(num)
330
if num >= len(lines):
332
if num is not None and lines[num].log is None:
334
patches=[patch]+patches
335
except CantGetRevisionData:
341
340
def difference_index(atext, btext):
342
341
"""Find the indext of the first character that differs betweeen two texts
359
class PatchConflict(Exception):
360
def __init__(self, line_no, orig_line, patch_line):
361
orig = orig_line.rstrip('\n')
362
patch = str(patch_line).rstrip('\n')
363
msg = 'Text contents mismatch at line %d. Original has "%s",'\
364
' but patch says it should be "%s"' % (line_no, orig, patch)
365
Exception.__init__(self, msg)
368
def iter_patched(orig_lines, patch_lines):
369
"""Iterate through a series of lines with a patch applied.
370
This handles a single file, and does exact, not fuzzy patching.
372
if orig_lines is not None:
373
orig_lines = orig_lines.__iter__()
375
patch_lines = iter_lines_handle_nl(patch_lines.__iter__())
376
get_patch_names(patch_lines)
378
for hunk in iter_hunks(patch_lines):
379
while line_no < hunk.orig_pos:
380
orig_line = orig_lines.next()
383
for hunk_line in hunk.lines:
384
seen_patch.append(str(hunk_line))
385
if isinstance(hunk_line, InsertLine):
386
yield hunk_line.contents
387
elif isinstance(hunk_line, (ContextLine, RemoveLine)):
388
orig_line = orig_lines.next()
389
if orig_line != hunk_line.contents:
390
raise PatchConflict(line_no, orig_line, "".join(seen_patch))
391
if isinstance(hunk_line, ContextLine):
394
assert isinstance(hunk_line, RemoveLine)
399
class PatchesTester(unittest.TestCase):
400
def datafile(self, filename):
401
data_path = os.path.join(os.path.dirname(__file__), "testdata",
403
return file(data_path, "rb")
405
def testValidPatchHeader(self):
406
"""Parse a valid patch header"""
407
lines = "--- orig/commands.py\n+++ mod/dommands.py\n".split('\n')
408
(orig, mod) = get_patch_names(lines.__iter__())
409
assert(orig == "orig/commands.py")
410
assert(mod == "mod/dommands.py")
412
def testInvalidPatchHeader(self):
413
"""Parse an invalid patch header"""
414
lines = "-- orig/commands.py\n+++ mod/dommands.py".split('\n')
415
self.assertRaises(MalformedPatchHeader, get_patch_names,
418
def testValidHunkHeader(self):
419
"""Parse a valid hunk header"""
420
header = "@@ -34,11 +50,6 @@\n"
421
hunk = hunk_from_header(header);
422
assert (hunk.orig_pos == 34)
423
assert (hunk.orig_range == 11)
424
assert (hunk.mod_pos == 50)
425
assert (hunk.mod_range == 6)
426
assert (str(hunk) == header)
428
def testValidHunkHeader2(self):
429
"""Parse a tricky, valid hunk header"""
430
header = "@@ -1 +0,0 @@\n"
431
hunk = hunk_from_header(header);
432
assert (hunk.orig_pos == 1)
433
assert (hunk.orig_range == 1)
434
assert (hunk.mod_pos == 0)
435
assert (hunk.mod_range == 0)
436
assert (str(hunk) == header)
438
def makeMalformed(self, header):
439
self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
441
def testInvalidHeader(self):
442
"""Parse an invalid hunk header"""
443
self.makeMalformed(" -34,11 +50,6 \n")
444
self.makeMalformed("@@ +50,6 -34,11 @@\n")
445
self.makeMalformed("@@ -34,11 +50,6 @@")
446
self.makeMalformed("@@ -34.5,11 +50,6 @@\n")
447
self.makeMalformed("@@-34,11 +50,6@@\n")
448
self.makeMalformed("@@ 34,11 50,6 @@\n")
449
self.makeMalformed("@@ -34,11 @@\n")
450
self.makeMalformed("@@ -34,11 +50,6.5 @@\n")
451
self.makeMalformed("@@ -34,11 +50,-6 @@\n")
453
def lineThing(self,text, type):
454
line = parse_line(text)
455
assert(isinstance(line, type))
456
assert(str(line)==text)
458
def makeMalformedLine(self, text):
459
self.assertRaises(MalformedLine, parse_line, text)
461
def testValidLine(self):
462
"""Parse a valid hunk line"""
463
self.lineThing(" hello\n", ContextLine)
464
self.lineThing("+hello\n", InsertLine)
465
self.lineThing("-hello\n", RemoveLine)
467
def testMalformedLine(self):
468
"""Parse invalid valid hunk lines"""
469
self.makeMalformedLine("hello\n")
471
def compare_parsed(self, patchtext):
472
lines = patchtext.splitlines(True)
473
patch = parse_patch(lines.__iter__())
475
i = difference_index(patchtext, pstr)
477
print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
478
self.assertEqual (patchtext, str(patch))
481
"""Test parsing a whole patch"""
482
patchtext = """--- orig/commands.py
361
class PatchesTester(unittest.TestCase):
362
def testValidPatchHeader(self):
363
"""Parse a valid patch header"""
364
lines = "--- orig/commands.py\n+++ mod/dommands.py\n".split('\n')
365
(orig, mod) = get_patch_names(lines.__iter__())
366
assert(orig == "orig/commands.py")
367
assert(mod == "mod/dommands.py")
369
def testInvalidPatchHeader(self):
370
"""Parse an invalid patch header"""
371
lines = "-- orig/commands.py\n+++ mod/dommands.py".split('\n')
372
self.assertRaises(MalformedPatchHeader, get_patch_names,
375
def testValidHunkHeader(self):
376
"""Parse a valid hunk header"""
377
header = "@@ -34,11 +50,6 @@\n"
378
hunk = hunk_from_header(header);
379
assert (hunk.orig_pos == 34)
380
assert (hunk.orig_range == 11)
381
assert (hunk.mod_pos == 50)
382
assert (hunk.mod_range == 6)
383
assert (str(hunk) == header)
385
def testValidHunkHeader2(self):
386
"""Parse a tricky, valid hunk header"""
387
header = "@@ -1 +0,0 @@\n"
388
hunk = hunk_from_header(header);
389
assert (hunk.orig_pos == 1)
390
assert (hunk.orig_range == 1)
391
assert (hunk.mod_pos == 0)
392
assert (hunk.mod_range == 0)
393
assert (str(hunk) == header)
395
def makeMalformed(self, header):
396
self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
398
def testInvalidHeader(self):
399
"""Parse an invalid hunk header"""
400
self.makeMalformed(" -34,11 +50,6 \n")
401
self.makeMalformed("@@ +50,6 -34,11 @@\n")
402
self.makeMalformed("@@ -34,11 +50,6 @@")
403
self.makeMalformed("@@ -34.5,11 +50,6 @@\n")
404
self.makeMalformed("@@-34,11 +50,6@@\n")
405
self.makeMalformed("@@ 34,11 50,6 @@\n")
406
self.makeMalformed("@@ -34,11 @@\n")
407
self.makeMalformed("@@ -34,11 +50,6.5 @@\n")
408
self.makeMalformed("@@ -34,11 +50,-6 @@\n")
410
def lineThing(self,text, type):
411
line = parse_line(text)
412
assert(isinstance(line, type))
413
assert(str(line)==text)
415
def makeMalformedLine(self, text):
416
self.assertRaises(MalformedLine, parse_line, text)
418
def testValidLine(self):
419
"""Parse a valid hunk line"""
420
self.lineThing(" hello\n", ContextLine)
421
self.lineThing("+hello\n", InsertLine)
422
self.lineThing("-hello\n", RemoveLine)
424
def testMalformedLine(self):
425
"""Parse invalid valid hunk lines"""
426
self.makeMalformedLine("hello\n")
428
def compare_parsed(self, patchtext):
429
lines = patchtext.splitlines(True)
430
patch = parse_patch(lines.__iter__())
432
i = difference_index(patchtext, pstr)
434
print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
435
assert (patchtext == str(patch))
438
"""Test parsing a whole patch"""
439
patchtext = """--- orig/commands.py
483
440
+++ mod/commands.py
484
441
@@ -1337,7 +1337,8 @@
508
self.compare_parsed(patchtext)
465
self.compare_parsed(patchtext)
511
"""Handle patches missing half the position, range tuple"""
468
"""Handle patches missing half the position, range tuple"""
513
470
"""--- orig/__init__.py
514
471
+++ mod/__init__.py
516
473
__docformat__ = "restructuredtext en"
517
+__doc__ = An alternate Arch commandline interface
519
self.compare_parsed(patchtext)
523
def testLineLookup(self):
525
"""Make sure we can accurately look up mod line from orig"""
526
patch = parse_patch(self.datafile("diff"))
527
orig = list(self.datafile("orig"))
528
mod = list(self.datafile("mod"))
530
for i in range(len(orig)):
531
mod_pos = patch.pos_in_mod(i)
533
removals.append(orig[i])
535
assert(mod[mod_pos]==orig[i])
536
rem_iter = removals.__iter__()
537
for hunk in patch.hunks:
538
for line in hunk.lines:
539
if isinstance(line, RemoveLine):
540
next = rem_iter.next()
541
if line.contents != next:
542
sys.stdout.write(" orig:%spatch:%s" % (next,
544
assert(line.contents == next)
545
self.assertRaises(StopIteration, rem_iter.next)
547
def testFirstLineRenumber(self):
548
"""Make sure we handle lines at the beginning of the hunk"""
549
patch = parse_patch(self.datafile("insert_top.patch"))
550
assert (patch.pos_in_mod(0)==1)
474
+__doc__ = An alternate Arch commandline interface"""
475
self.compare_parsed(patchtext)
479
def testLineLookup(self):
480
"""Make sure we can accurately look up mod line from orig"""
481
patch = parse_patch(open("testdata/diff"))
482
orig = list(open("testdata/orig"))
483
mod = list(open("testdata/mod"))
485
for i in range(len(orig)):
486
mod_pos = patch.pos_in_mod(i)
488
removals.append(orig[i])
490
assert(mod[mod_pos]==orig[i])
491
rem_iter = removals.__iter__()
492
for hunk in patch.hunks:
493
for line in hunk.lines:
494
if isinstance(line, RemoveLine):
495
next = rem_iter.next()
496
if line.contents != next:
497
sys.stdout.write(" orig:%spatch:%s" % (next,
499
assert(line.contents == next)
500
self.assertRaises(StopIteration, rem_iter.next)
502
def testFirstLineRenumber(self):
503
"""Make sure we handle lines at the beginning of the hunk"""
504
patch = parse_patch(open("testdata/insert_top.patch"))
505
assert (patch.pos_in_mod(0)==1)
553
508
patchesTestSuite = unittest.makeSuite(PatchesTester,'test')
554
509
runner = unittest.TextTestRunner(verbosity=0)
555
510
return runner.run(patchesTestSuite)