##// END OF EJS Templates
Attempt to make diff deal with null sources properly...
mpm@selenic.com -
r264:4c1d7072 default
parent child Browse files
Show More
@@ -1,604 +1,604 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, re, sys, signal
9 9 import fancyopts, ui, hg
10 10 from demandload import *
11 11 demandload(globals(), "mdiff time hgweb traceback")
12 12
13 13 class UnknownCommand(Exception): pass
14 14
15 15 def filterfiles(filters, files):
16 16 l = [ x for x in files if x in filters ]
17 17
18 18 for t in filters:
19 19 if t and t[-1] != os.sep: t += os.sep
20 20 l += [ x for x in files if x.startswith(t) ]
21 21 return l
22 22
23 23 def relfilter(repo, files):
24 24 if os.getcwd() != repo.root:
25 25 p = os.getcwd()[len(repo.root) + 1: ]
26 26 return filterfiles(p, files)
27 27 return files
28 28
29 29 def relpath(repo, args):
30 30 if os.getcwd() != repo.root:
31 31 p = os.getcwd()[len(repo.root) + 1: ]
32 32 return [ os.path.normpath(os.path.join(p, x)) for x in args ]
33 33 return args
34 34
35 35 def dodiff(repo, files = None, node1 = None, node2 = None):
36 36 def date(c):
37 37 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
38 38
39 39 if node2:
40 40 change = repo.changelog.read(node2)
41 41 mmap2 = repo.manifest.read(change[0])
42 42 (c, a, d) = repo.diffrevs(node1, node2)
43 43 def read(f): return repo.file(f).read(mmap2[f])
44 44 date2 = date(change)
45 45 else:
46 46 date2 = time.asctime()
47 47 (c, a, d, u) = repo.diffdir(repo.root, node1)
48 48 if not node1:
49 49 node1 = repo.dirstate.parents()[0]
50 50 def read(f): return file(os.path.join(repo.root, f)).read()
51 51
52 52 change = repo.changelog.read(node1)
53 53 mmap = repo.manifest.read(change[0])
54 54 date1 = date(change)
55 55
56 56 if files:
57 57 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
58 58
59 59 for f in c:
60 60 to = repo.file(f).read(mmap[f])
61 61 tn = read(f)
62 62 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
63 63 for f in a:
64 to = ""
64 to = None
65 65 tn = read(f)
66 66 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
67 67 for f in d:
68 68 to = repo.file(f).read(mmap[f])
69 tn = ""
69 tn = None
70 70 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
71 71
72 72 def help(ui, cmd=None):
73 73 '''show help for a given command or all commands'''
74 74 if cmd:
75 75 try:
76 76 i = find(cmd)
77 77 ui.write("%s\n\n" % i[2])
78 78 ui.write(i[0].__doc__, "\n")
79 79 except UnknownCommand:
80 80 ui.warn("unknown command %s" % cmd)
81 81 sys.exit(0)
82 82 else:
83 83 ui.status('hg commands:\n\n')
84 84
85 85 h = {}
86 86 for e in table.values():
87 87 f = e[0]
88 88 if f.__name__.startswith("debug"): continue
89 89 d = ""
90 90 if f.__doc__:
91 91 d = f.__doc__.splitlines(0)[0].rstrip()
92 92 h[f.__name__] = d
93 93
94 94 fns = h.keys()
95 95 fns.sort()
96 96 m = max(map(len, fns))
97 97 for f in fns:
98 98 ui.status(' %-*s %s\n' % (m, f, h[f]))
99 99
100 100 # Commands start here, listed alphabetically
101 101
102 102 def add(ui, repo, file, *files):
103 103 '''add the specified files on the next commit'''
104 104 repo.add(relpath(repo, (file,) + files))
105 105
106 106 def addremove(ui, repo):
107 107 """add all new files, delete all missing files"""
108 108 (c, a, d, u) = repo.diffdir(repo.root)
109 109 repo.add(u)
110 110 repo.remove(d)
111 111
112 112 def annotate(u, repo, file, *files, **ops):
113 113 """show changeset information per file line"""
114 114 def getnode(rev):
115 115 return hg.short(repo.changelog.node(rev))
116 116
117 117 def getname(rev):
118 118 try:
119 119 return bcache[rev]
120 120 except KeyError:
121 121 cl = repo.changelog.read(repo.changelog.node(rev))
122 122 name = cl[1]
123 123 f = name.find('@')
124 124 if f >= 0:
125 125 name = name[:f]
126 126 bcache[rev] = name
127 127 return name
128 128
129 129 bcache = {}
130 130 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
131 131 if not ops['user'] and not ops['changeset']:
132 132 ops['number'] = 1
133 133
134 134 node = repo.dirstate.parents()[0]
135 135 if ops['revision']:
136 136 node = repo.changelog.lookup(ops['revision'])
137 137 change = repo.changelog.read(node)
138 138 mmap = repo.manifest.read(change[0])
139 139 maxuserlen = 0
140 140 maxchangelen = 0
141 141 for f in relpath(repo, (file,) + files):
142 142 lines = repo.file(f).annotate(mmap[f])
143 143 pieces = []
144 144
145 145 for o, f in opmap:
146 146 if ops[o]:
147 147 l = [ f(n) for n,t in lines ]
148 148 m = max(map(len, l))
149 149 pieces.append([ "%*s" % (m, x) for x in l])
150 150
151 151 for p,l in zip(zip(*pieces), lines):
152 152 u.write(" ".join(p) + ": " + l[1])
153 153
154 154 def branch(ui, path):
155 155 '''branch from a local repository'''
156 156 # this should eventually support remote repos
157 157 os.system("cp -al %s/.hg .hg" % path)
158 158
159 159 def cat(ui, repo, file, rev = []):
160 160 """output the latest or given revision of a file"""
161 161 r = repo.file(file)
162 162 n = r.tip()
163 163 if rev: n = r.lookup(rev)
164 164 sys.stdout.write(r.read(n))
165 165
166 166 def commit(ui, repo, *files):
167 167 """commit the specified files or all outstanding changes"""
168 168 repo.commit(relpath(repo, files))
169 169
170 170 def debugaddchangegroup(ui, repo):
171 171 data = sys.stdin.read()
172 172 repo.addchangegroup(data)
173 173
174 174 def debugchangegroup(ui, repo, roots):
175 175 newer = repo.newer(map(repo.lookup, roots))
176 176 for chunk in repo.changegroup(newer):
177 177 sys.stdout.write(chunk)
178 178
179 179 def debugindex(ui, file):
180 180 r = hg.revlog(open, file, "")
181 181 print " rev offset length base linkrev"+\
182 182 " p1 p2 nodeid"
183 183 for i in range(r.count()):
184 184 e = r.index[i]
185 185 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
186 186 i, e[0], e[1], e[2], e[3],
187 187 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
188 188
189 189 def debugindexdot(ui, file):
190 190 r = hg.revlog(open, file, "")
191 191 print "digraph G {"
192 192 for i in range(r.count()):
193 193 e = r.index[i]
194 194 print "\t%d -> %d" % (r.rev(e[4]), i)
195 195 if e[5] != hg.nullid:
196 196 print "\t%d -> %d" % (r.rev(e[5]), i)
197 197 print "}"
198 198
199 199 def diff(ui, repo, *files, **opts):
200 200 """diff working directory (or selected files)"""
201 201 revs = []
202 202 if opts['rev']:
203 203 revs = map(lambda x: repo.lookup(x), opts['rev'])
204 204
205 205 if len(revs) > 2:
206 206 self.ui.warn("too many revisions to diff\n")
207 207 sys.exit(1)
208 208
209 209 if files:
210 210 files = relpath(repo, files)
211 211 else:
212 212 files = relpath(repo, [""])
213 213
214 214 dodiff(repo, files, *revs)
215 215
216 216 def export(ui, repo, changeset):
217 217 """dump the changeset header and diffs for a revision"""
218 218 node = repo.lookup(changeset)
219 219 prev, other = repo.changelog.parents(node)
220 220 change = repo.changelog.read(node)
221 221 print "# HG changeset patch"
222 222 print "# User %s" % change[1]
223 223 print "# Node ID %s" % hg.hex(node)
224 224 print "# Parent %s" % hg.hex(prev)
225 225 print
226 226 if other != hg.nullid:
227 227 print "# Parent %s" % hg.hex(other)
228 228 print change[4].rstrip()
229 229 print
230 230
231 231 dodiff(repo, None, prev, node)
232 232
233 233 def forget(ui, repo, file, *files):
234 234 """don't add the specified files on the next commit"""
235 235 repo.forget(relpath(repo, (file,) + files))
236 236
237 237 def heads(ui, repo):
238 238 '''show current repository heads'''
239 239 for n in repo.changelog.heads():
240 240 i = repo.changelog.rev(n)
241 241 changes = repo.changelog.read(n)
242 242 (p1, p2) = repo.changelog.parents(n)
243 243 (h, h1, h2) = map(hg.hex, (n, p1, p2))
244 244 (i1, i2) = map(repo.changelog.rev, (p1, p2))
245 245 print "rev: %4d:%s" % (i, h)
246 246 print "parents: %4d:%s" % (i1, h1)
247 247 if i2: print " %4d:%s" % (i2, h2)
248 248 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
249 249 hg.hex(changes[0]))
250 250 print "user:", changes[1]
251 251 print "date:", time.asctime(
252 252 time.localtime(float(changes[2].split(' ')[0])))
253 253 if ui.verbose: print "files:", " ".join(changes[3])
254 254 print "description:"
255 255 print changes[4]
256 256
257 257 def history(ui, repo):
258 258 """show the changelog history"""
259 259 for i in range(repo.changelog.count()):
260 260 n = repo.changelog.node(i)
261 261 changes = repo.changelog.read(n)
262 262 (p1, p2) = repo.changelog.parents(n)
263 263 (h, h1, h2) = map(hg.hex, (n, p1, p2))
264 264 (i1, i2) = map(repo.changelog.rev, (p1, p2))
265 265 print "rev: %4d:%s" % (i, h)
266 266 print "parents: %4d:%s" % (i1, h1)
267 267 if i2: print " %4d:%s" % (i2, h2)
268 268 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
269 269 hg.hex(changes[0]))
270 270 print "user:", changes[1]
271 271 print "date:", time.asctime(
272 272 time.localtime(float(changes[2].split(' ')[0])))
273 273 if ui.verbose: print "files:", " ".join(changes[3])
274 274 print "description:"
275 275 print changes[4]
276 276
277 277 def init(ui):
278 278 """create a repository"""
279 279 hg.repository(ui, ".", create=1)
280 280
281 281 def log(ui, repo, f):
282 282 """show the revision history of a single file"""
283 283 f = relpath(repo, [f])[0]
284 284
285 285 r = repo.file(f)
286 286 for i in range(r.count()):
287 287 n = r.node(i)
288 288 (p1, p2) = r.parents(n)
289 289 (h, h1, h2) = map(hg.hex, (n, p1, p2))
290 290 (i1, i2) = map(r.rev, (p1, p2))
291 291 cr = r.linkrev(n)
292 292 cn = hg.hex(repo.changelog.node(cr))
293 293 print "rev: %4d:%s" % (i, h)
294 294 print "changeset: %4d:%s" % (cr, cn)
295 295 print "parents: %4d:%s" % (i1, h1)
296 296 if i2: print " %4d:%s" % (i2, h2)
297 297 changes = repo.changelog.read(repo.changelog.node(cr))
298 298 print "user: %s" % changes[1]
299 299 print "date: %s" % time.asctime(
300 300 time.localtime(float(changes[2].split(' ')[0])))
301 301 print "description:"
302 302 print changes[4].rstrip()
303 303 print
304 304
305 305 def manifest(ui, repo, rev = []):
306 306 """output the latest or given revision of the project manifest"""
307 307 n = repo.manifest.tip()
308 308 if rev:
309 309 n = repo.manifest.lookup(rev)
310 310 m = repo.manifest.read(n)
311 311 files = m.keys()
312 312 files.sort()
313 313
314 314 for f in files:
315 315 print hg.hex(m[f]), f
316 316
317 317 def parents(ui, repo, node = None):
318 318 '''show the parents of the current working dir'''
319 319 if node:
320 320 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
321 321 else:
322 322 p = repo.dirstate.parents()
323 323
324 324 for n in p:
325 325 if n != hg.nullid:
326 326 ui.write("%d:%s\n" % (repo.changelog.rev(n), hg.hex(n)))
327 327
328 328 def patch(ui, repo, patches, opts):
329 329 """import an ordered set of patches"""
330 330 try:
331 331 import psyco
332 332 psyco.full()
333 333 except:
334 334 pass
335 335
336 336 d = opts["base"]
337 337 strip = opts["strip"]
338 338 quiet = opts["quiet"] and "> /dev/null" or ""
339 339
340 340 for patch in patches:
341 341 ui.status("applying %s\n" % patch)
342 342 pf = os.path.join(d, patch)
343 343
344 344 text = ""
345 345 for l in file(pf):
346 346 if l[:4] == "--- ": break
347 347 text += l
348 348
349 349 f = os.popen("lsdiff --strip %d %s" % (strip, pf))
350 350 files = filter(None, map(lambda x: x.rstrip(), f.read().splitlines()))
351 351 f.close()
352 352
353 353 if files:
354 354 if os.system("patch -p%d < %s %s" % (strip, pf, quiet)):
355 355 raise "patch failed!"
356 356 repo.commit(files, text)
357 357
358 358 def pull(ui, repo, source):
359 359 """pull changes from the specified source"""
360 360 paths = {}
361 361 try:
362 362 pf = os.path.expanduser("~/.hgpaths")
363 363 for l in file(pf):
364 364 name, path = l.split()
365 365 paths[name] = path
366 366 except IOError:
367 367 pass
368 368
369 369 if source in paths: source = paths[source]
370 370
371 371 other = hg.repository(ui, source)
372 372 cg = repo.getchangegroup(other)
373 373 repo.addchangegroup(cg)
374 374
375 375 def rawcommit(ui, repo, files, rc):
376 376 "raw commit interface"
377 377
378 378 text = rc['text']
379 379 if not text and rc['logfile']:
380 380 try: text = open(rc['logfile']).read()
381 381 except IOError: pass
382 382 if not text and not rc['logfile']:
383 383 print "missing commit text"
384 384 return 1
385 385
386 386 files = relpath(repo, files)
387 387 if rc['files']:
388 388 files += open(rc['files']).read().splitlines()
389 389
390 390 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
391 391
392 392 def recover(ui, repo):
393 393 """roll back an interrupted transaction"""
394 394 repo.recover()
395 395
396 396 def remove(ui, repo, file, *files):
397 397 """remove the specified files on the next commit"""
398 398 repo.remove(relpath(repo, (file,) + files))
399 399
400 400 def serve(ui, repo, **opts):
401 401 """export the repository via HTTP"""
402 402 hgweb.server(repo.root, opts["name"], opts["templates"],
403 403 opts["address"], opts["port"])
404 404
405 405 def status(ui, repo):
406 406 '''show changed files in the working directory
407 407
408 408 C = changed
409 409 A = added
410 410 R = removed
411 411 ? = not tracked'''
412 412
413 413 (c, a, d, u) = repo.diffdir(repo.root)
414 414 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
415 415
416 416 for f in c: print "C", f
417 417 for f in a: print "A", f
418 418 for f in d: print "R", f
419 419 for f in u: print "?", f
420 420
421 421 def tags(ui, repo):
422 422 """list repository tags"""
423 423 repo.lookup(0) # prime the cache
424 424 i = repo.tags.items()
425 425 n = []
426 426 for e in i:
427 427 try:
428 428 l = repo.changelog.rev(e[1])
429 429 except KeyError:
430 430 l = -2
431 431 n.append((l, e))
432 432
433 433 n.sort()
434 434 n.reverse()
435 435 i = [ e[1] for e in n ]
436 436 for k, n in i:
437 437 try:
438 438 r = repo.changelog.rev(n)
439 439 except KeyError:
440 440 r = "?"
441 441 print "%-30s %5d:%s" % (k, repo.changelog.rev(n), hg.hex(n))
442 442
443 443 def tip(ui, repo):
444 444 """show the tip revision"""
445 445 n = repo.changelog.tip()
446 446 t = repo.changelog.rev(n)
447 447 ui.status("%d:%s\n" % (t, hg.hex(n)))
448 448
449 449 def undo(ui, repo):
450 450 """undo the last transaction"""
451 451 repo.undo()
452 452
453 453 def update(ui, repo, node=None):
454 454 '''update or merge working directory
455 455
456 456 If there are no outstanding changes in the working directory and
457 457 there is a linear relationship between the current version and the
458 458 requested version, the result is the requested version.
459 459
460 460 Otherwise the result is a merge between the contents of the
461 461 current working directory and the requested version. Files that
462 462 changed between either parent are marked as changed for the next
463 463 commit and a commit must be performed before any further updates
464 464 are allowed.
465 465 '''
466 466 node = node and repo.lookup(node) or repo.changelog.tip()
467 467 repo.update(node)
468 468
469 469 def verify(ui, repo):
470 470 """verify the integrity of the repository"""
471 471 return repo.verify()
472 472
473 473 # Command options and aliases are listed here, alphabetically
474 474
475 475 table = {
476 476 "add": (add, [], "hg add [files]"),
477 477 "addremove": (addremove, [], "hg addremove"),
478 478 "ann|annotate": (annotate,
479 479 [('r', 'revision', '', 'revision'),
480 480 ('u', 'user', None, 'show user'),
481 481 ('n', 'number', None, 'show revision number'),
482 482 ('c', 'changeset', None, 'show changeset')],
483 483 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
484 484 "branch|clone": (branch, [], 'hg branch [path]'),
485 485 "cat|dump": (cat, [], 'hg cat <file> [rev]'),
486 486 "commit|ci": (commit, [], 'hg commit [files]'),
487 487 "debugaddchangegroup": (debugaddchangegroup, [], 'debugaddchangegroup'),
488 488 "debugchangegroup": (debugchangegroup, [], 'debugchangegroup [roots]'),
489 489 "debugindex": (debugindex, [], 'debugindex <file>'),
490 490 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
491 491 "diff": (diff, [('r', 'rev', [], 'revision')],
492 492 'hg diff [-r A] [-r B] [files]'),
493 493 "export": (export, [], "hg export <changeset>"),
494 494 "forget": (forget, [], "hg forget [files]"),
495 495 "heads": (heads, [], 'hg heads'),
496 496 "history": (history, [], 'hg history'),
497 497 "help": (help, [], 'hg help [command]'),
498 498 "init": (init, [], 'hg init'),
499 499 "log": (log, [], 'hg log <file>'),
500 500 "manifest|dumpmanifest": (manifest, [], 'hg manifest [rev]'),
501 501 "parents": (parents, [], 'hg parents [node]'),
502 502 "patch|import": (patch,
503 503 [('p', 'strip', 1, 'path strip'),
504 504 ('b', 'base', "", 'base path'),
505 505 ('q', 'quiet', "", 'silence diff')],
506 506 "hg import [options] patches"),
507 507 "pull|merge": (pull, [], 'hg pull [source]'),
508 508 "rawcommit": (rawcommit,
509 509 [('p', 'parent', [], 'parent'),
510 510 ('d', 'date', "", 'data'),
511 511 ('u', 'user', "", 'user'),
512 512 ('F', 'files', "", 'file list'),
513 513 ('t', 'text', "", 'commit text'),
514 514 ('l', 'logfile', "", 'commit text file')],
515 515 'hg rawcommit [options] [files]'),
516 516 "recover": (recover, [], "hg recover"),
517 517 "remove": (remove, [], "hg remove [files]"),
518 518 "serve": (serve, [('p', 'port', 8000, 'listen port'),
519 519 ('a', 'address', '', 'interface address'),
520 520 ('n', 'name', os.getcwd(), 'repository name'),
521 521 ('t', 'templates', "", 'template map')],
522 522 "hg serve [options]"),
523 523 "status": (status, [], 'hg status'),
524 524 "tags": (tags, [], 'hg tags'),
525 525 "tip": (tip, [], 'hg tip'),
526 526 "undo": (undo, [], 'hg undo'),
527 527 "update|up|checkout|co|resolve": (update, [], 'hg update [node]'),
528 528 "verify": (verify, [], 'hg verify'),
529 529 }
530 530
531 531 norepo = "init branch help debugindex debugindexdot"
532 532
533 533 def find(cmd):
534 534 i = None
535 535 for e in table.keys():
536 536 if re.match(e + "$", cmd):
537 537 return table[e]
538 538
539 539 raise UnknownCommand(cmd)
540 540
541 541 class SignalInterrupt(Exception): pass
542 542
543 543 def catchterm(*args):
544 544 raise SignalInterrupt
545 545
546 546 def run():
547 547 sys.exit(dispatch(sys.argv[1:]))
548 548
549 549 def dispatch(args):
550 550 options = {}
551 551 opts = [('v', 'verbose', None, 'verbose'),
552 552 ('d', 'debug', None, 'debug'),
553 553 ('q', 'quiet', None, 'quiet'),
554 554 ('y', 'noninteractive', None, 'run non-interactively'),
555 555 ]
556 556
557 557 args = fancyopts.fancyopts(args, opts, options,
558 558 'hg [options] <command> [options] [files]')
559 559
560 560 if not args:
561 561 cmd = "help"
562 562 else:
563 563 cmd, args = args[0], args[1:]
564 564
565 565 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
566 566 not options["noninteractive"])
567 567
568 568 try:
569 569 i = find(cmd)
570 570 except UnknownCommand:
571 571 u.warn("unknown command '%s'\n" % cmd)
572 572 help(u)
573 573 sys.exit(1)
574 574
575 575 signal.signal(signal.SIGTERM, catchterm)
576 576
577 577 cmdoptions = {}
578 578 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
579 579
580 580 if cmd not in norepo.split():
581 581 repo = hg.repository(ui = u)
582 582 d = lambda: i[0](u, repo, *args, **cmdoptions)
583 583 else:
584 584 d = lambda: i[0](u, *args, **cmdoptions)
585 585
586 586 try:
587 587 return d()
588 588 except SignalInterrupt:
589 589 u.warn("killed!\n")
590 590 except KeyboardInterrupt:
591 591 u.warn("interrupted!\n")
592 592 except IOError, inst:
593 593 if inst.errno == 32:
594 594 u.warn("broken pipe\n")
595 595 else:
596 596 raise
597 597 except TypeError, inst:
598 598 # was this an argument error?
599 599 tb = traceback.extract_tb(sys.exc_info()[2])
600 600 if len(tb) > 2: # no
601 601 raise
602 602 u.warn("%s: invalid arguments\n" % i[0].__name__)
603 603 u.warn("syntax: %s\n" % i[2])
604 604 sys.exit(-1)
@@ -1,77 +1,91 b''
1 1 # mdiff.py - diff and patch routines for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import difflib, struct
9 9 from mercurial.mpatch import *
10 10
11 11 def unidiff(a, ad, b, bd, fn):
12 12 if not a and not b: return ""
13 a = a.splitlines(1)
14 b = b.splitlines(1)
15 l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn, ad, bd))
13
14 if a == None:
15 b = b.splitlines(1)
16 l1 = "--- %s\t%s\n" % ("/dev/null", ad)
17 l2 = "+++ %s\t%s\n" % ("b/" + fn, bd)
18 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
19 l = [l1, l2, l3] + ["+" + e for e in b]
20 elif b == None:
21 a = a.splitlines(1)
22 l1 = "--- %s\t%s\n" % ("a/" + fn, ad)
23 l2 = "+++ %s\t%s\n" % ("/dev/null", bd)
24 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
25 l = [l1, l2, l3] + ["-" + e for e in a]
26 else:
27 a = a.splitlines(1)
28 b = b.splitlines(1)
29 l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn, ad, bd))
16 30
17 31 for ln in xrange(len(l)):
18 32 if l[ln][-1] != '\n':
19 33 l[ln] += "\n\ No newline at end of file\n"
20 34
21 35 return "".join(l)
22 36
23 37 def textdiff(a, b):
24 38 return diff(a.splitlines(1), b.splitlines(1))
25 39
26 40 def sortdiff(a, b):
27 41 la = lb = 0
28 42 lena = len(a)
29 43 lenb = len(b)
30 44 while 1:
31 45 am, bm, = la, lb
32 46 while lb < lenb and la < len and a[la] == b[lb] :
33 47 la += 1
34 48 lb += 1
35 49 if la>am: yield (am, bm, la-am)
36 50 while lb < lenb and b[lb] < a[la]: lb += 1
37 51 if lb>=lenb: break
38 52 while la < lena and b[lb] > a[la]: la += 1
39 53 if la>=lena: break
40 54 yield (lena, lenb, 0)
41 55
42 56 def diff(a, b, sorted=0):
43 57 if not a:
44 58 s = "".join(b)
45 59 return s and (struct.pack(">lll", 0, 0, len(s)) + s)
46 60
47 61 bin = []
48 62 p = [0]
49 63 for i in a: p.append(p[-1] + len(i))
50 64
51 65 if sorted:
52 66 d = sortdiff(a, b)
53 67 else:
54 68 d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
55 69 la = 0
56 70 lb = 0
57 71 for am, bm, size in d:
58 72 s = "".join(b[lb:bm])
59 73 if am > la or s:
60 74 bin.append(struct.pack(">lll", p[la], p[am], len(s)) + s)
61 75 la = am + size
62 76 lb = bm + size
63 77
64 78 return "".join(bin)
65 79
66 80 def patchtext(bin):
67 81 pos = 0
68 82 t = []
69 83 while pos < len(bin):
70 84 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
71 85 pos += 12
72 86 t.append(bin[pos:pos + l])
73 87 pos += l
74 88 return "".join(t)
75 89
76 90 def patch(a, bin):
77 91 return patches(a, [bin])
General Comments 0
You need to be logged in to leave comments. Login now