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 |