##// END OF EJS Templates
patch: support diff data loss detection and upgrade...
Patrick Mezard -
r10189:e451e599 default
parent child Browse files
Show More
@@ -0,0 +1,46 b''
1 # Extension dedicated to test patch.diff() upgrade modes
2 #
3 #
4 from mercurial import cmdutil, patch, util
5
6 def autodiff(ui, repo, *pats, **opts):
7 diffopts = patch.diffopts(ui, opts)
8 git = opts.get('git', 'no')
9 brokenfiles = set()
10 losedatafn = None
11 if git in ('yes', 'no'):
12 diffopts.git = git == 'yes'
13 diffopts.upgrade = False
14 elif git == 'auto':
15 diffopts.git = False
16 diffopts.upgrade = True
17 elif git == 'warn':
18 diffopts.git = False
19 diffopts.upgrade = True
20 def losedatafn(fn=None, **kwargs):
21 brokenfiles.add(fn)
22 return True
23 elif git == 'abort':
24 diffopts.git = False
25 diffopts.upgrade = True
26 def losedatafn(fn=None, **kwargs):
27 raise util.Abort('losing data for %s' % fn)
28 else:
29 raise util.Abort('--git must be yes, no or auto')
30
31 node1, node2 = cmdutil.revpair(repo, [])
32 m = cmdutil.match(repo, pats, opts)
33 it = patch.diff(repo, node1, node2, match=m, opts=diffopts,
34 losedatafn=losedatafn)
35 for chunk in it:
36 ui.write(chunk)
37 for fn in sorted(brokenfiles):
38 ui.write('data lost for: %s\n' % fn)
39
40 cmdtable = {
41 "autodiff":
42 (autodiff,
43 [('', 'git', '', 'git upgrade mode (yes/no/auto/warn/abort)'),
44 ],
45 '[OPTION]... [FILE]...'),
46 }
@@ -0,0 +1,63 b''
1 #!/bin/sh
2
3 echo "[extensions]" >> $HGRCPATH
4 echo "autodiff=$TESTDIR/autodiff.py" >> $HGRCPATH
5 echo "[diff]" >> $HGRCPATH
6 echo "nodates=1" >> $HGRCPATH
7
8 hg init repo
9 cd repo
10 echo '% make a combination of new, changed and deleted file'
11 echo regular > regular
12 echo rmregular > rmregular
13 touch rmempty
14 echo exec > exec
15 chmod +x exec
16 echo rmexec > rmexec
17 chmod +x rmexec
18 echo setexec > setexec
19 echo unsetexec > unsetexec
20 chmod +x unsetexec
21 echo binary > binary
22 python -c "file('rmbinary', 'wb').write('\0')"
23 hg ci -Am addfiles
24 echo regular >> regular
25 echo newregular >> newregular
26 rm rmempty
27 touch newempty
28 rm rmregular
29 echo exec >> exec
30 echo newexec > newexec
31 chmod +x newexec
32 rm rmexec
33 chmod +x setexec
34 chmod -x unsetexec
35 python -c "file('binary', 'wb').write('\0\0')"
36 python -c "file('newbinary', 'wb').write('\0')"
37 rm rmbinary
38 hg addremove
39
40 echo '% git=no: regular diff for all files'
41 hg autodiff --git=no
42
43 echo '% git=no: git diff for single regular file'
44 hg autodiff --git=yes regular
45
46 echo '% git=auto: regular diff for regular files and removals'
47 hg autodiff --git=auto regular newregular rmregular rmbinary rmexec
48
49 for f in exec newexec setexec unsetexec binary newbinary newempty rmempty; do
50 echo '% git=auto: git diff for' $f
51 hg autodiff --git=auto $f
52 done
53
54 echo '% git=warn: regular diff with data loss warnings'
55 hg autodiff --git=warn
56
57 echo '% git=abort: fail on execute bit change'
58 hg autodiff --git=abort regular setexec
59
60 echo '% git=abort: succeed on regular file'
61 hg autodiff --git=abort regular
62
63 cd ..
@@ -0,0 +1,186 b''
1 % make a combination of new, changed and deleted file
2 adding binary
3 adding exec
4 adding regular
5 adding rmbinary
6 adding rmempty
7 adding rmexec
8 adding rmregular
9 adding setexec
10 adding unsetexec
11 adding newbinary
12 adding newempty
13 adding newexec
14 adding newregular
15 removing rmbinary
16 removing rmempty
17 removing rmexec
18 removing rmregular
19 % git=no: regular diff for all files
20 diff -r b3f053cd7c7f binary
21 Binary file binary has changed
22 diff -r b3f053cd7c7f exec
23 --- a/exec
24 +++ b/exec
25 @@ -1,1 +1,2 @@
26 exec
27 +exec
28 diff -r b3f053cd7c7f newbinary
29 Binary file newbinary has changed
30 diff -r b3f053cd7c7f newexec
31 --- /dev/null
32 +++ b/newexec
33 @@ -0,0 +1,1 @@
34 +newexec
35 diff -r b3f053cd7c7f newregular
36 --- /dev/null
37 +++ b/newregular
38 @@ -0,0 +1,1 @@
39 +newregular
40 diff -r b3f053cd7c7f regular
41 --- a/regular
42 +++ b/regular
43 @@ -1,1 +1,2 @@
44 regular
45 +regular
46 diff -r b3f053cd7c7f rmbinary
47 Binary file rmbinary has changed
48 diff -r b3f053cd7c7f rmexec
49 --- a/rmexec
50 +++ /dev/null
51 @@ -1,1 +0,0 @@
52 -rmexec
53 diff -r b3f053cd7c7f rmregular
54 --- a/rmregular
55 +++ /dev/null
56 @@ -1,1 +0,0 @@
57 -rmregular
58 % git=no: git diff for single regular file
59 diff --git a/regular b/regular
60 --- a/regular
61 +++ b/regular
62 @@ -1,1 +1,2 @@
63 regular
64 +regular
65 % git=auto: regular diff for regular files and removals
66 diff -r b3f053cd7c7f newregular
67 --- /dev/null
68 +++ b/newregular
69 @@ -0,0 +1,1 @@
70 +newregular
71 diff -r b3f053cd7c7f regular
72 --- a/regular
73 +++ b/regular
74 @@ -1,1 +1,2 @@
75 regular
76 +regular
77 diff -r b3f053cd7c7f rmbinary
78 Binary file rmbinary has changed
79 diff -r b3f053cd7c7f rmexec
80 --- a/rmexec
81 +++ /dev/null
82 @@ -1,1 +0,0 @@
83 -rmexec
84 diff -r b3f053cd7c7f rmregular
85 --- a/rmregular
86 +++ /dev/null
87 @@ -1,1 +0,0 @@
88 -rmregular
89 % git=auto: git diff for exec
90 diff -r b3f053cd7c7f exec
91 --- a/exec
92 +++ b/exec
93 @@ -1,1 +1,2 @@
94 exec
95 +exec
96 % git=auto: git diff for newexec
97 diff --git a/newexec b/newexec
98 new file mode 100755
99 --- /dev/null
100 +++ b/newexec
101 @@ -0,0 +1,1 @@
102 +newexec
103 % git=auto: git diff for setexec
104 diff --git a/setexec b/setexec
105 old mode 100644
106 new mode 100755
107 % git=auto: git diff for unsetexec
108 diff --git a/unsetexec b/unsetexec
109 old mode 100755
110 new mode 100644
111 % git=auto: git diff for binary
112 diff --git a/binary b/binary
113 index a9128c283485202893f5af379dd9beccb6e79486..09f370e38f498a462e1ca0faa724559b6630c04f
114 GIT binary patch
115 literal 2
116 Jc${Nk0000200961
117
118 % git=auto: git diff for newbinary
119 diff --git a/newbinary b/newbinary
120 new file mode 100644
121 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d
122 GIT binary patch
123 literal 1
124 Ic${MZ000310RR91
125
126 % git=auto: git diff for newempty
127 diff --git a/newempty b/newempty
128 new file mode 100644
129 % git=auto: git diff for rmempty
130 diff --git a/rmempty b/rmempty
131 deleted file mode 100644
132 % git=warn: regular diff with data loss warnings
133 diff -r b3f053cd7c7f binary
134 Binary file binary has changed
135 diff -r b3f053cd7c7f exec
136 --- a/exec
137 +++ b/exec
138 @@ -1,1 +1,2 @@
139 exec
140 +exec
141 diff -r b3f053cd7c7f newbinary
142 Binary file newbinary has changed
143 diff -r b3f053cd7c7f newexec
144 --- /dev/null
145 +++ b/newexec
146 @@ -0,0 +1,1 @@
147 +newexec
148 diff -r b3f053cd7c7f newregular
149 --- /dev/null
150 +++ b/newregular
151 @@ -0,0 +1,1 @@
152 +newregular
153 diff -r b3f053cd7c7f regular
154 --- a/regular
155 +++ b/regular
156 @@ -1,1 +1,2 @@
157 regular
158 +regular
159 diff -r b3f053cd7c7f rmbinary
160 Binary file rmbinary has changed
161 diff -r b3f053cd7c7f rmexec
162 --- a/rmexec
163 +++ /dev/null
164 @@ -1,1 +0,0 @@
165 -rmexec
166 diff -r b3f053cd7c7f rmregular
167 --- a/rmregular
168 +++ /dev/null
169 @@ -1,1 +0,0 @@
170 -rmregular
171 data lost for: binary
172 data lost for: newbinary
173 data lost for: newempty
174 data lost for: newexec
175 data lost for: rmempty
176 data lost for: setexec
177 data lost for: unsetexec
178 % git=abort: fail on execute bit change
179 abort: losing data for setexec
180 % git=abort: succeed on regular file
181 diff -r b3f053cd7c7f regular
182 --- a/regular
183 +++ b/regular
184 @@ -1,1 +1,2 @@
185 regular
186 +regular
@@ -27,7 +27,9 b' class diffopts(object):'
27 27 nodates removes dates from diff headers
28 28 ignorews ignores all whitespace changes in the diff
29 29 ignorewsamount ignores changes in the amount of whitespace
30 ignoreblanklines ignores changes whose lines are all blank'''
30 ignoreblanklines ignores changes whose lines are all blank
31 upgrade generates git diffs to avoid data loss
32 '''
31 33
32 34 defaults = {
33 35 'context': 3,
@@ -38,6 +40,7 b' class diffopts(object):'
38 40 'ignorews': False,
39 41 'ignorewsamount': False,
40 42 'ignoreblanklines': False,
43 'upgrade': False,
41 44 }
42 45
43 46 __slots__ = defaults.keys()
@@ -1246,17 +1246,25 b' def b85diff(to, tn):'
1246 1246 ret.append('\n')
1247 1247 return ''.join(ret)
1248 1248
1249 def _addmodehdr(header, omode, nmode):
1250 if omode != nmode:
1251 header.append('old mode %s\n' % omode)
1252 header.append('new mode %s\n' % nmode)
1249 class GitDiffRequired(Exception):
1250 pass
1253 1251
1254 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1252 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1253 losedatafn=None):
1255 1254 '''yields diff of changes to files between two nodes, or node and
1256 1255 working directory.
1257 1256
1258 1257 if node1 is None, use first dirstate parent instead.
1259 if node2 is None, compare node1 with working directory.'''
1258 if node2 is None, compare node1 with working directory.
1259
1260 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1261 every time some change cannot be represented with the current
1262 patch format. Return False to upgrade to git patch format, True to
1263 accept the loss or raise an exception to abort the diff. It is
1264 called with the name of current file being diffed as 'fn'. If set
1265 to None, patches will always be upgraded to git format when
1266 necessary.
1267 '''
1260 1268
1261 1269 if opts is None:
1262 1270 opts = mdiff.defaultopts
@@ -1288,24 +1296,50 b' def diff(repo, node1=None, node2=None, m'
1288 1296 modified, added, removed = changes[:3]
1289 1297
1290 1298 if not modified and not added and not removed:
1291 return
1299 return []
1300
1301 revs = None
1302 if not repo.ui.quiet:
1303 hexfunc = repo.ui.debugflag and hex or short
1304 revs = [hexfunc(node) for node in [node1, node2] if node]
1305
1306 copy = {}
1307 if opts.git or opts.upgrade:
1308 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1309 copy = copy.copy()
1310 for k, v in copy.items():
1311 copy[v] = k
1312
1313 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1314 modified, added, removed, copy, getfilectx, opts, losedata)
1315 if opts.upgrade and not opts.git:
1316 try:
1317 def losedata(fn):
1318 if not losedatafn or not losedatafn(fn=fn):
1319 raise GitDiffRequired()
1320 # Buffer the whole output until we are sure it can be generated
1321 return list(difffn(opts.copy(git=False), losedata))
1322 except GitDiffRequired:
1323 return difffn(opts.copy(git=True), None)
1324 else:
1325 return difffn(opts, None)
1326
1327 def _addmodehdr(header, omode, nmode):
1328 if omode != nmode:
1329 header.append('old mode %s\n' % omode)
1330 header.append('new mode %s\n' % nmode)
1331
1332 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1333 copy, getfilectx, opts, losedatafn):
1292 1334
1293 1335 date1 = util.datestr(ctx1.date())
1294 1336 man1 = ctx1.manifest()
1295 1337
1296 revs = None
1297 if not repo.ui.quiet and not opts.git:
1298 hexfunc = repo.ui.debugflag and hex or short
1299 revs = [hexfunc(node) for node in [node1, node2] if node]
1338 gone = set()
1339 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1300 1340
1301 1341 if opts.git:
1302 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1303 copy = copy.copy()
1304 for k, v in copy.items():
1305 copy[v] = k
1306
1307 gone = set()
1308 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1342 revs = None
1309 1343
1310 1344 for f in sorted(modified + added + removed):
1311 1345 to = None
@@ -1317,39 +1351,61 b' def diff(repo, node1=None, node2=None, m'
1317 1351 if f not in removed:
1318 1352 tn = getfilectx(f, ctx2).data()
1319 1353 a, b = f, f
1320 if opts.git:
1354 if opts.git or losedatafn:
1321 1355 if f in added:
1322 1356 mode = gitmode[ctx2.flags(f)]
1323 1357 if f in copy:
1324 a = copy[f]
1325 omode = gitmode[man1.flags(a)]
1326 _addmodehdr(header, omode, mode)
1327 if a in removed and a not in gone:
1328 op = 'rename'
1329 gone.add(a)
1358 if opts.git:
1359 a = copy[f]
1360 omode = gitmode[man1.flags(a)]
1361 _addmodehdr(header, omode, mode)
1362 if a in removed and a not in gone:
1363 op = 'rename'
1364 gone.add(a)
1365 else:
1366 op = 'copy'
1367 header.append('%s from %s\n' % (op, a))
1368 header.append('%s to %s\n' % (op, f))
1369 to = getfilectx(a, ctx1).data()
1330 1370 else:
1331 op = 'copy'
1332 header.append('%s from %s\n' % (op, a))
1333 header.append('%s to %s\n' % (op, f))
1334 to = getfilectx(a, ctx1).data()
1371 losedatafn(f)
1335 1372 else:
1336 header.append('new file mode %s\n' % mode)
1373 if opts.git:
1374 header.append('new file mode %s\n' % mode)
1375 elif ctx2.flags(f):
1376 losedatafn(f)
1337 1377 if util.binary(tn):
1338 dodiff = 'binary'
1378 if opts.git:
1379 dodiff = 'binary'
1380 else:
1381 losedatafn(f)
1382 if not opts.git and not tn:
1383 # regular diffs cannot represent new empty file
1384 losedatafn(f)
1339 1385 elif f in removed:
1340 # have we already reported a copy above?
1341 if f in copy and copy[f] in added and copy[copy[f]] == f:
1342 dodiff = False
1343 else:
1344 header.append('deleted file mode %s\n' %
1345 gitmode[man1.flags(f)])
1386 if opts.git:
1387 # have we already reported a copy above?
1388 if f in copy and copy[f] in added and copy[copy[f]] == f:
1389 dodiff = False
1390 else:
1391 header.append('deleted file mode %s\n' %
1392 gitmode[man1.flags(f)])
1393 elif not to:
1394 # regular diffs cannot represent empty file deletion
1395 losedatafn(f)
1346 1396 else:
1347 omode = gitmode[man1.flags(f)]
1348 nmode = gitmode[ctx2.flags(f)]
1349 _addmodehdr(header, omode, nmode)
1350 if util.binary(to) or util.binary(tn):
1351 dodiff = 'binary'
1352 header.insert(0, mdiff.diffline(revs, a, b, opts))
1397 oflag = man1.flags(f)
1398 nflag = ctx2.flags(f)
1399 binary = util.binary(to) or util.binary(tn)
1400 if opts.git:
1401 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1402 if binary:
1403 dodiff = 'binary'
1404 elif binary or nflag != oflag:
1405 losedatafn(f)
1406 if opts.git:
1407 header.insert(0, mdiff.diffline(revs, a, b, opts))
1408
1353 1409 if dodiff:
1354 1410 if dodiff == 'binary':
1355 1411 text = b85diff(to, tn)
General Comments 0
You need to be logged in to leave comments. Login now