Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/patch.py @ 32997:66117dae87f9
patch: rewrite reversehunks (issue5337)
The old reversehunks code accesses "crecord.uihunk._hunk", which is the raw
recordhunk without crecord selection information, therefore "revert -i"
cannot revert individual lines, aka. issue5337.
The patch rewrites related logic to return the right reverse hunk for
revert. Namely,
1. "fromline" and "toline" are correctly swapped [1]
2. crecord.uihunk generates a correct reverse hunk [2]
Besides, reversehunks(hunks) will no longer modify its input "hunks", which
is more expected.
[1]: To explain why "fromline" and "toline" need to be swapped, take the
following example:
$ cat > a <<EOF
> 1
> 2
> 3
> 4
> EOF
$ cat > b <<EOF
> 2
> 3
> 5
> EOF
$ diff a b
1d0 <---- "1" is "fromline" and "0" is "toline"
< 1 and they are swapped if diff from the reversed direction
4c3 |
< 4 |
--- |
> 5 |
|
$ diff b a |
0a1 <---------+
> 1
3c4 <---- also "4c3" gets swapped to "3c4"
< 5
---
> 4
[2]: This is a bit tricky.
For example, given a file which is empty in working parent but has 3 lines
in working copy, and the user selection:
select hunk to discard
[x] +1
[ ] +2
[x] +3
The user intent is to drop "1" and "3" in working copy but keep "2", so the
reverse patch would be something like:
-1
2 (2 is a "context line")
-3
We cannot just take all selected lines and swap "-" and "+", which will be:
-1
-3
That patch won't apply because of "2". So the correct way is to insert "2"
as a "context line" by inserting it first then deleting it:
-2
+2
Therefore, the correct revert patch is:
-1
-2
+2
-3
It could be reordered to look more like a common diff hunk:
-1
-2
-3
+2
Note: It's possible to return multiple hunks so there won't be lines like
"-2", "+2". But the current implementation is much simpler.
For deletions, like the working parent has "1\n2\n3\n" and it was changed to
empty in working copy:
select hunk to discard
[x] -1
[ ] -2
[x] -3
The user intent is to drop the deletion of 1 and 3 (in other words, keep
those lines), but still delete "2".
The reverse patch is meant to be applied to working copy which is empty.
So the patch would be:
+1
+3
That is to say, there is no need to special handle the unselected "2" like
the above insertion case.
author | Jun Wu <quark@fb.com> |
---|---|
date | Tue, 20 Jun 2017 23:22:38 -0700 |
parents | 017ad85e5ac8 |
children | 1d5d7e2b7ab5 |
comparison
equal
deleted
inserted
replaced
32996:41b081ac2145 | 32997:66117dae87f9 |
---|---|
956 def countchanges(self, hunk): | 956 def countchanges(self, hunk): |
957 """hunk -> (n+,n-)""" | 957 """hunk -> (n+,n-)""" |
958 add = len([h for h in hunk if h[0] == '+']) | 958 add = len([h for h in hunk if h[0] == '+']) |
959 rem = len([h for h in hunk if h[0] == '-']) | 959 rem = len([h for h in hunk if h[0] == '-']) |
960 return add, rem | 960 return add, rem |
961 | |
962 def reversehunk(self): | |
963 """return another recordhunk which is the reverse of the hunk | |
964 | |
965 If this hunk is diff(A, B), the returned hunk is diff(B, A). To do | |
966 that, swap fromline/toline and +/- signs while keep other things | |
967 unchanged. | |
968 """ | |
969 m = {'+': '-', '-': '+'} | |
970 hunk = ['%s%s' % (m[l[0]], l[1:]) for l in self.hunk] | |
971 return recordhunk(self.header, self.toline, self.fromline, self.proc, | |
972 self.before, hunk, self.after) | |
961 | 973 |
962 def write(self, fp): | 974 def write(self, fp): |
963 delta = len(self.before) + len(self.after) | 975 delta = len(self.before) + len(self.after) |
964 if self.after and self.after[-1] == '\\ No newline at end of file\n': | 976 if self.after and self.after[-1] == '\\ No newline at end of file\n': |
965 delta -= 1 | 977 delta -= 1 |
1491 @@ -1,4 +1,3 @@ | 1503 @@ -1,4 +1,3 @@ |
1492 -firstline | 1504 -firstline |
1493 c | 1505 c |
1494 1 | 1506 1 |
1495 2 | 1507 2 |
1496 @@ -1,6 +2,6 @@ | 1508 @@ -2,6 +1,6 @@ |
1497 c | 1509 c |
1498 1 | 1510 1 |
1499 2 | 1511 2 |
1500 - 3 | 1512 - 3 |
1501 +4 | 1513 +4 |
1502 5 | 1514 5 |
1503 d | 1515 d |
1504 @@ -5,3 +6,2 @@ | 1516 @@ -6,3 +5,2 @@ |
1505 5 | 1517 5 |
1506 d | 1518 d |
1507 -lastline | 1519 -lastline |
1508 | 1520 |
1509 ''' | 1521 ''' |
1510 | 1522 |
1511 from . import crecord as crecordmod | |
1512 newhunks = [] | 1523 newhunks = [] |
1513 for c in hunks: | 1524 for c in hunks: |
1514 if isinstance(c, crecordmod.uihunk): | 1525 if util.safehasattr(c, 'reversehunk'): |
1515 # curses hunks encapsulate the record hunk in _hunk | 1526 c = c.reversehunk() |
1516 c = c._hunk | |
1517 if isinstance(c, recordhunk): | |
1518 for j, line in enumerate(c.hunk): | |
1519 if line.startswith("-"): | |
1520 c.hunk[j] = "+" + c.hunk[j][1:] | |
1521 elif line.startswith("+"): | |
1522 c.hunk[j] = "-" + c.hunk[j][1:] | |
1523 c.added, c.removed = c.removed, c.added | |
1524 newhunks.append(c) | 1527 newhunks.append(c) |
1525 return newhunks | 1528 return newhunks |
1526 | 1529 |
1527 def parsepatch(originalchunks): | 1530 def parsepatch(originalchunks): |
1528 """patch -> [] of headers -> [] of hunks """ | 1531 """patch -> [] of headers -> [] of hunks """ |