Mercurial > public > mercurial-scm > hg
comparison hgext/extdiff.py @ 45127:da2e69a278df
extdiff: refactor logic to diff revs of versions of files
Now that code for both cases, diffing patches or files is in separate function,
it will be better to refactor them more and understand.
Differential Revision: https://phab.mercurial-scm.org/D8687
author | Pulkit Goyal <7895pulkit@gmail.com> |
---|---|
date | Tue, 07 Jul 2020 13:13:18 +0530 |
parents | 48c38018bd77 |
children | d23881b17388 |
comparison
equal
deleted
inserted
replaced
45126:48c38018bd77 | 45127:da2e69a278df |
---|---|
380 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)) | 380 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)) |
381 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff') | 381 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff') |
382 return 1 | 382 return 1 |
383 | 383 |
384 | 384 |
385 def diffrevs( | |
386 ui, | |
387 repo, | |
388 node1a, | |
389 node1b, | |
390 node2, | |
391 matcher, | |
392 tmproot, | |
393 cmdline, | |
394 do3way, | |
395 guitool, | |
396 opts, | |
397 ): | |
398 | |
399 subrepos = opts.get(b'subrepos') | |
400 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos) | |
401 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed) | |
402 if do3way: | |
403 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos) | |
404 mod_b, add_b, rem_b = ( | |
405 set(stb.modified), | |
406 set(stb.added), | |
407 set(stb.removed), | |
408 ) | |
409 else: | |
410 mod_b, add_b, rem_b = set(), set(), set() | |
411 modadd = mod_a | add_a | mod_b | add_b | |
412 common = modadd | rem_a | rem_b | |
413 if not common: | |
414 return 0 | |
415 # Always make a copy of node1a (and node1b, if applicable) | |
416 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) | |
417 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] | |
418 rev1a = b'@%d' % repo[node1a].rev() | |
419 if do3way: | |
420 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) | |
421 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0] | |
422 rev1b = b'@%d' % repo[node1b].rev() | |
423 else: | |
424 dir1b = None | |
425 rev1b = b'' | |
426 | |
427 fnsandstat = [] | |
428 | |
429 # If node2 in not the wc or there is >1 change, copy it | |
430 dir2root = b'' | |
431 rev2 = b'' | |
432 if node2: | |
433 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] | |
434 rev2 = b'@%d' % repo[node2].rev() | |
435 elif len(common) > 1: | |
436 # we only actually need to get the files to copy back to | |
437 # the working dir in this case (because the other cases | |
438 # are: diffing 2 revisions or single file -- in which case | |
439 # the file is already directly passed to the diff tool). | |
440 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos) | |
441 else: | |
442 # This lets the diff tool open the changed file directly | |
443 dir2 = b'' | |
444 dir2root = repo.root | |
445 | |
446 label1a = rev1a | |
447 label1b = rev1b | |
448 label2 = rev2 | |
449 | |
450 # If only one change, diff the files instead of the directories | |
451 # Handle bogus modifies correctly by checking if the files exist | |
452 if len(common) == 1: | |
453 common_file = util.localpath(common.pop()) | |
454 dir1a = os.path.join(tmproot, dir1a, common_file) | |
455 label1a = common_file + rev1a | |
456 if not os.path.isfile(dir1a): | |
457 dir1a = pycompat.osdevnull | |
458 if do3way: | |
459 dir1b = os.path.join(tmproot, dir1b, common_file) | |
460 label1b = common_file + rev1b | |
461 if not os.path.isfile(dir1b): | |
462 dir1b = pycompat.osdevnull | |
463 dir2 = os.path.join(dir2root, dir2, common_file) | |
464 label2 = common_file + rev2 | |
465 | |
466 if not opts.get(b'per_file'): | |
467 # Run the external tool on the 2 temp directories or the patches | |
468 cmdline = formatcmdline( | |
469 cmdline, | |
470 repo.root, | |
471 do3way=do3way, | |
472 parent1=dir1a, | |
473 plabel1=label1a, | |
474 parent2=dir1b, | |
475 plabel2=label1b, | |
476 child=dir2, | |
477 clabel=label2, | |
478 ) | |
479 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)) | |
480 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff') | |
481 else: | |
482 # Run the external tool once for each pair of files | |
483 _runperfilediff( | |
484 cmdline, | |
485 repo.root, | |
486 ui, | |
487 guitool=guitool, | |
488 do3way=do3way, | |
489 confirm=opts.get(b'confirm'), | |
490 commonfiles=common, | |
491 tmproot=tmproot, | |
492 dir1a=dir1a, | |
493 dir1b=dir1b, | |
494 dir2root=dir2root, | |
495 dir2=dir2, | |
496 rev1a=rev1a, | |
497 rev1b=rev1b, | |
498 rev2=rev2, | |
499 ) | |
500 | |
501 for copy_fn, working_fn, st in fnsandstat: | |
502 cpstat = os.lstat(copy_fn) | |
503 # Some tools copy the file and attributes, so mtime may not detect | |
504 # all changes. A size check will detect more cases, but not all. | |
505 # The only certain way to detect every case is to diff all files, | |
506 # which could be expensive. | |
507 # copyfile() carries over the permission, so the mode check could | |
508 # be in an 'elif' branch, but for the case where the file has | |
509 # changed without affecting mtime or size. | |
510 if ( | |
511 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME] | |
512 or cpstat.st_size != st.st_size | |
513 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100) | |
514 ): | |
515 ui.debug( | |
516 b'file changed while diffing. ' | |
517 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn) | |
518 ) | |
519 util.copyfile(copy_fn, working_fn) | |
520 | |
521 return 1 | |
522 | |
523 | |
385 def dodiff(ui, repo, cmdline, pats, opts, guitool=False): | 524 def dodiff(ui, repo, cmdline, pats, opts, guitool=False): |
386 '''Do the actual diff: | 525 '''Do the actual diff: |
387 | 526 |
388 - copy to a temp structure if diffing 2 internal revisions | 527 - copy to a temp structure if diffing 2 internal revisions |
389 - copy to a temp structure if diffing working revision with | 528 - copy to a temp structure if diffing working revision with |
404 if not revs: | 543 if not revs: |
405 ctx1b = repo[None].p2() | 544 ctx1b = repo[None].p2() |
406 else: | 545 else: |
407 ctx1b = repo[nullid] | 546 ctx1b = repo[nullid] |
408 | 547 |
409 perfile = opts.get(b'per_file') | |
410 confirm = opts.get(b'confirm') | |
411 | |
412 node1a = ctx1a.node() | 548 node1a = ctx1a.node() |
413 node1b = ctx1b.node() | 549 node1b = ctx1b.node() |
414 node2 = ctx2.node() | 550 node2 = ctx2.node() |
415 | 551 |
416 # Disable 3-way merge if there is only one parent | 552 # Disable 3-way merge if there is only one parent |
417 if do3way: | 553 if do3way: |
418 if node1b == nullid: | 554 if node1b == nullid: |
419 do3way = False | 555 do3way = False |
420 | 556 |
421 subrepos = opts.get(b'subrepos') | |
422 | |
423 matcher = scmutil.match(repo[node2], pats, opts) | 557 matcher = scmutil.match(repo[node2], pats, opts) |
424 | 558 |
425 if opts.get(b'patch'): | 559 if opts.get(b'patch'): |
426 if subrepos: | 560 if opts.get(b'subrepos'): |
427 raise error.Abort(_(b'--patch cannot be used with --subrepos')) | 561 raise error.Abort(_(b'--patch cannot be used with --subrepos')) |
428 if perfile: | 562 if opts.get(b'per_file'): |
429 raise error.Abort(_(b'--patch cannot be used with --per-file')) | 563 raise error.Abort(_(b'--patch cannot be used with --per-file')) |
430 if node2 is None: | 564 if node2 is None: |
431 raise error.Abort(_(b'--patch requires two revisions')) | 565 raise error.Abort(_(b'--patch requires two revisions')) |
432 else: | |
433 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos) | |
434 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed) | |
435 if do3way: | |
436 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos) | |
437 mod_b, add_b, rem_b = ( | |
438 set(stb.modified), | |
439 set(stb.added), | |
440 set(stb.removed), | |
441 ) | |
442 else: | |
443 mod_b, add_b, rem_b = set(), set(), set() | |
444 modadd = mod_a | add_a | mod_b | add_b | |
445 common = modadd | rem_a | rem_b | |
446 if not common: | |
447 return 0 | |
448 | 566 |
449 tmproot = pycompat.mkdtemp(prefix=b'extdiff.') | 567 tmproot = pycompat.mkdtemp(prefix=b'extdiff.') |
450 try: | 568 try: |
451 if opts.get(b'patch'): | 569 if opts.get(b'patch'): |
452 return diffpatch( | 570 return diffpatch( |
453 ui, repo, node1a, node2, tmproot, matcher, cmdline, do3way | 571 ui, repo, node1a, node2, tmproot, matcher, cmdline, do3way |
454 ) | 572 ) |
455 | 573 |
456 # Always make a copy of node1a (and node1b, if applicable) | 574 return diffrevs( |
457 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) | 575 ui, |
458 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] | 576 repo, |
459 rev1a = b'@%d' % repo[node1a].rev() | 577 node1a, |
460 if do3way: | 578 node1b, |
461 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) | 579 node2, |
462 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[ | 580 matcher, |
463 0 | 581 tmproot, |
464 ] | 582 cmdline, |
465 rev1b = b'@%d' % repo[node1b].rev() | 583 do3way, |
466 else: | 584 guitool, |
467 dir1b = None | 585 opts, |
468 rev1b = b'' | 586 ) |
469 | 587 |
470 fnsandstat = [] | |
471 | |
472 # If node2 in not the wc or there is >1 change, copy it | |
473 dir2root = b'' | |
474 rev2 = b'' | |
475 if node2: | |
476 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] | |
477 rev2 = b'@%d' % repo[node2].rev() | |
478 elif len(common) > 1: | |
479 # we only actually need to get the files to copy back to | |
480 # the working dir in this case (because the other cases | |
481 # are: diffing 2 revisions or single file -- in which case | |
482 # the file is already directly passed to the diff tool). | |
483 dir2, fnsandstat = snapshot( | |
484 ui, repo, modadd, None, tmproot, subrepos | |
485 ) | |
486 else: | |
487 # This lets the diff tool open the changed file directly | |
488 dir2 = b'' | |
489 dir2root = repo.root | |
490 | |
491 label1a = rev1a | |
492 label1b = rev1b | |
493 label2 = rev2 | |
494 | |
495 # If only one change, diff the files instead of the directories | |
496 # Handle bogus modifies correctly by checking if the files exist | |
497 if len(common) == 1: | |
498 common_file = util.localpath(common.pop()) | |
499 dir1a = os.path.join(tmproot, dir1a, common_file) | |
500 label1a = common_file + rev1a | |
501 if not os.path.isfile(dir1a): | |
502 dir1a = pycompat.osdevnull | |
503 if do3way: | |
504 dir1b = os.path.join(tmproot, dir1b, common_file) | |
505 label1b = common_file + rev1b | |
506 if not os.path.isfile(dir1b): | |
507 dir1b = pycompat.osdevnull | |
508 dir2 = os.path.join(dir2root, dir2, common_file) | |
509 label2 = common_file + rev2 | |
510 | |
511 if not perfile: | |
512 # Run the external tool on the 2 temp directories or the patches | |
513 cmdline = formatcmdline( | |
514 cmdline, | |
515 repo.root, | |
516 do3way=do3way, | |
517 parent1=dir1a, | |
518 plabel1=label1a, | |
519 parent2=dir1b, | |
520 plabel2=label1b, | |
521 child=dir2, | |
522 clabel=label2, | |
523 ) | |
524 ui.debug( | |
525 b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot) | |
526 ) | |
527 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff') | |
528 else: | |
529 # Run the external tool once for each pair of files | |
530 _runperfilediff( | |
531 cmdline, | |
532 repo.root, | |
533 ui, | |
534 guitool=guitool, | |
535 do3way=do3way, | |
536 confirm=confirm, | |
537 commonfiles=common, | |
538 tmproot=tmproot, | |
539 dir1a=dir1a, | |
540 dir1b=dir1b, | |
541 dir2root=dir2root, | |
542 dir2=dir2, | |
543 rev1a=rev1a, | |
544 rev1b=rev1b, | |
545 rev2=rev2, | |
546 ) | |
547 | |
548 for copy_fn, working_fn, st in fnsandstat: | |
549 cpstat = os.lstat(copy_fn) | |
550 # Some tools copy the file and attributes, so mtime may not detect | |
551 # all changes. A size check will detect more cases, but not all. | |
552 # The only certain way to detect every case is to diff all files, | |
553 # which could be expensive. | |
554 # copyfile() carries over the permission, so the mode check could | |
555 # be in an 'elif' branch, but for the case where the file has | |
556 # changed without affecting mtime or size. | |
557 if ( | |
558 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME] | |
559 or cpstat.st_size != st.st_size | |
560 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100) | |
561 ): | |
562 ui.debug( | |
563 b'file changed while diffing. ' | |
564 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn) | |
565 ) | |
566 util.copyfile(copy_fn, working_fn) | |
567 | |
568 return 1 | |
569 finally: | 588 finally: |
570 ui.note(_(b'cleaning up temp directory\n')) | 589 ui.note(_(b'cleaning up temp directory\n')) |
571 shutil.rmtree(tmproot) | 590 shutil.rmtree(tmproot) |
572 | 591 |
573 | 592 |