def diff(orig, ui, repo, *args, **opts): """show a diff of the most recent revision against its parent from svn """ if not opts.get('svn', False) or opts.get('change', None): return orig(ui, repo, *args, **opts) meta = repo.svnmeta() hashes = meta.revmap.hashes() if not opts.get('rev', None): parent = repo[None].parents()[0] o_r = util.outgoing_revisions(repo, hashes, parent.node()) if o_r: parent = repo[o_r[-1]].parents()[0] opts['rev'] = ['%s:.' % node.hex(parent.node()), ] node1, node2 = scmutil.revpair(repo, opts['rev']) if not isinstance(node1, bytes): # hg 4.6 and later return contexts, so convert to bytestr node1, node2 = node1.node(), node2.node() baserev, _junk = hashes.get(node1, (-1, 'junk')) newrev, _junk = hashes.get(node2, (-1, 'junk')) it = patch.diff(repo, node1, node2, opts=patch.diffopts(ui, opts={'git': True, 'show_function': False, 'ignore_all_space': False, 'ignore_space_change': False, 'ignore_blank_lines': False, 'unified': True, 'text': False, })) ui.write(util.filterdiff(''.join(it), baserev, newrev))
def autodiff(ui, repo, *pats, **opts): diffopts = patch.diffopts(ui, opts) git = opts.get('git', 'no') brokenfiles = set() losedatafn = None if git in ('yes', 'no'): diffopts.git = git == 'yes' diffopts.upgrade = False elif git == 'auto': diffopts.git = False diffopts.upgrade = True elif git == 'warn': diffopts.git = False diffopts.upgrade = True def losedatafn(fn=None, **kwargs): brokenfiles.add(fn) return True elif git == 'abort': diffopts.git = False diffopts.upgrade = True def losedatafn(fn=None, **kwargs): raise util.Abort('losing data for %s' % fn) else: raise util.Abort('--git must be yes, no or auto') node1, node2 = scmutil.revpair(repo, []) m = scmutil.match(repo[node2], pats, opts) it = patch.diff(repo, node1, node2, match=m, opts=diffopts, losedatafn=losedatafn) for chunk in it: ui.write(chunk) for fn in sorted(brokenfiles): ui.write(('data lost for: %s\n' % fn))
def diff(orig, ui, repo, *args, **opts): """show a diff of the most recent revision against its parent from svn """ if not opts.get('svn', False) or opts.get('change', None): return orig(ui, repo, *args, **opts) meta = repo.svnmeta() hashes = meta.revmap.hashes() if not opts.get('rev', None): parent = repo[None].parents()[0] o_r = util.outgoing_revisions(repo, hashes, parent.node()) if o_r: parent = repo[o_r[-1]].parents()[0] opts['rev'] = ['%s:.' % node.hex(parent.node()), ] node1, node2 = scmutil.revpair(repo, opts['rev']) baserev, _junk = hashes.get(node1, (-1, 'junk')) newrev, _junk = hashes.get(node2, (-1, 'junk')) it = patch.diff(repo, node1, node2, opts=patch.diffopts(ui, opts={'git': True, 'show_function': False, 'ignore_all_space': False, 'ignore_space_change': False, 'ignore_blank_lines': False, 'unified': True, 'text': False, })) ui.write(util.filterdiff(''.join(it), baserev, newrev))
def foamdiff(ui, repo, *files, **opts): ''' Compare two OpenFOAM-dictionaries for semantic differences Very simple. Uses pyFoamCompareDictionary.py and does not pass options along to it ''' revs = opts.get('rev') change = opts.get('change') if len(files) == 0: raise util.Abort("No files specified") if revs and change: msg = _('cannot specify --rev and --change at the same time') raise util.Abort(msg) elif change: node2 = repo.lookup(change) node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = scmutil.revpair(repo, revs) if not revs: node1b = repo.dirstate.parents()[1] else: node1b = nullid if node1b != nullid: raise util.Abort("Can't do 3-way comparisons") matcher = scmutil.match(repo[node2], files, opts) mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3]) modadd = mod_a | add_a common = modadd | rem_a if not common: return 0 tmproot = tempfile.mkdtemp(prefix='foamdiff.') ui.debug("Writing temporary files to %s\n" % tmproot) try: dir1a_files = mod_a | rem_a dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0] dir2root = tmproot if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0] else: dir2 = '' dir2root = repo.root for f in common: ui.write("\n Comparing: %s\n" % f) f1 = path.join(tmproot, dir1a, f) f2 = path.join(dir2root, dir2, f) CompareDictionary(args=[f1, f2]) finally: ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot)
def foamdiff(ui, repo, *files, **opts): ''' Compare two OpenFOAM-dictionaries for semantic differences Very simple. Uses pyFoamCompareDictionary.py and does not pass options along to it ''' revs = opts.get('rev') change = opts.get('change') if len(files)==0: raise util.Abort("No files specified") if revs and change: msg = _('cannot specify --rev and --change at the same time') raise util.Abort(msg) elif change: node2 = repo.lookup(change) node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = scmutil.revpair(repo, revs) if not revs: node1b = repo.dirstate.parents()[1] else: node1b = nullid if node1b!=nullid: raise util.Abort("Can't do 3-way comparisons") matcher = scmutil.match(repo[node2], files, opts) mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3]) modadd = mod_a | add_a common = modadd | rem_a if not common: return 0 tmproot = tempfile.mkdtemp(prefix='foamdiff.') ui.debug("Writing temporary files to %s\n" %tmproot) try: dir1a_files = mod_a | rem_a dir1a=snapshot(ui, repo, dir1a_files, node1a, tmproot)[0] dir2root = tmproot if node2: dir2=snapshot(ui, repo, modadd, node2, tmproot)[0] else: dir2='' dir2root=repo.root for f in common: ui.write("\n Comparing: %s\n" %f) f1=path.join(tmproot,dir1a,f) f2=path.join(dir2root,dir2,f) CompareDictionary(args=[f1,f2]) finally: ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot)
def _bigstatus(ui, repo, pats, opts, ds, bigfiles): MAX_SIZE = 10000000 brepo = bigfiles_repo(ui) tracked_gotbig = [] # not in .bigfiles added_big = [] # not in .bigfiles modified = [] # already in .bigfiles removed = [] # missing, but still in .bigfiles gotsmall = [] # still in .bigfiles missinginrepo = [] # file recorded in .bigfiles not in bigfiles repo node1, node2 = scmutil.revpair(repo, None) mod_all, added_all = repo.status(node1, node2, scmutil.match(repo[None], pats, opts), None, None, True)[0:2] for file in mod_all: f=repo.wjoin(file) fsize=os.lstat(f).st_size if fsize > MAX_SIZE: tracked_gotbig.append(file) for file in added_all: f=repo.wjoin(file) fsize=os.lstat(f).st_size if fsize > MAX_SIZE: added_big.append(file) for file, hash in bigfiles.iteritems(): f=repo.wjoin(file) try: st = os.lstat(f) except OSError: frepo = "%s/%s.%s" % (brepo, file, hash) if os.path.exists(frepo) or os.path.exists(frepo+'.gz'): removed.append(file) else: missinginrepo.append(file) continue if st.st_size <= MAX_SIZE: gotsmall.append(file) fhash = accelerated_hash(repo, file, st, ds) if fhash != hash: modified.append(file) return tracked_gotbig, added_big, modified, removed, gotsmall, \ missinginrepo
def autodiff(ui, repo, *pats, **opts): opts = pycompat.byteskwargs(opts) diffopts = patch.difffeatureopts(ui, opts) git = opts.get(b'git', b'no') brokenfiles = set() losedatafn = None if git in (b'yes', b'no'): diffopts.git = git == b'yes' diffopts.upgrade = False elif git == b'auto': diffopts.git = False diffopts.upgrade = True elif git == b'warn': diffopts.git = False diffopts.upgrade = True def losedatafn(fn=None, **kwargs): brokenfiles.add(fn) return True elif git == b'abort': diffopts.git = False diffopts.upgrade = True def losedatafn(fn=None, **kwargs): raise error.Abort(b'losing data for %s' % fn) else: raise error.Abort(b'--git must be yes, no or auto') ctx1, ctx2 = scmutil.revpair(repo, []) m = scmutil.match(ctx2, pats, opts) it = patch.diff(repo, ctx1.node(), ctx2.node(), match=m, opts=diffopts, losedatafn=losedatafn) for chunk in it: ui.write(chunk) for fn in sorted(brokenfiles): ui.write((b'data lost for: %s\n' % fn))
def visualdiff(ui, repo, pats, opts): revs = opts.get('rev', []) change = opts.get('change') try: ctx1b = None if change: ctx2 = repo[change] p = ctx2.parents() if len(p) > 1: ctx1a, ctx1b = p else: ctx1a = p[0] else: n1, n2 = scmutil.revpair(repo, revs) ctx1a, ctx2 = repo[n1], repo[n2] p = ctx2.parents() if not revs and len(p) > 1: ctx1b = p[1] except (error.LookupError, error.RepoError): QMessageBox.warning(None, _('Unable to find changeset'), _('You likely need to refresh this application')) return None pats = scmutil.expandpats(pats) m = match.match(repo.root, '', pats, None, None, 'relpath') n2 = ctx2.node() mod_a, add_a, rem_a = map(set, repo.status(ctx1a.node(), n2, m)[:3]) if ctx1b: mod_b, add_b, rem_b = map(set, repo.status(ctx1b.node(), n2, m)[:3]) cpy = copies.mergecopies(repo, ctx1a, ctx1b, ctx1a.ancestor(ctx1b))[0] else: cpy = copies.pathcopies(ctx1a, ctx2) mod_b, add_b, rem_b = set(), set(), set() MA = mod_a | add_a | mod_b | add_b MAR = MA | rem_a | rem_b if not MAR: QMessageBox.information(None, _('No file changes'), _('There are no file changes to view')) return None detectedtools = hglib.difftools(repo.ui) if not detectedtools: QMessageBox.warning(None, _('No diff tool found'), _('No visual diff tools were detected')) return None preferred = besttool(repo.ui, detectedtools, opts.get('tool')) # Build tool list based on diff-patterns matches toollist = set() patterns = repo.ui.configitems('diff-patterns') patterns = [(p, t) for p,t in patterns if t in detectedtools] for path in MAR: for pat, tool in patterns: mf = match.match(repo.root, '', [pat]) if mf(path): toollist.add(tool) break else: toollist.add(preferred) cto = cpy.keys() for path in MAR: if path in cto: hascopies = True break else: hascopies = False force = repo.ui.configbool('tortoisehg', 'forcevdiffwin') if len(toollist) > 1 or (hascopies and len(MAR) > 1) or force: usewin = True else: preferred = toollist.pop() dirdiff = repo.ui.configbool('merge-tools', preferred + '.dirdiff') dir3diff = repo.ui.configbool('merge-tools', preferred + '.dir3diff') usewin = repo.ui.configbool('merge-tools', preferred + '.usewin') if not usewin and len(MAR) > 1: if ctx1b is not None: usewin = not dir3diff else: usewin = not dirdiff if usewin: # Multiple required tools, or tool does not support directory diffs sa = [mod_a, add_a, rem_a] sb = [mod_b, add_b, rem_b] dlg = FileSelectionDialog(repo, pats, ctx1a, sa, ctx1b, sb, ctx2, cpy) return dlg # We can directly use the selected tool, without a visual diff window diffcmd, diffopts, mergeopts = detectedtools[preferred] # Disable 3-way merge if there is only one parent or no tool support do3way = False if ctx1b: if mergeopts: do3way = True args = mergeopts else: args = diffopts if str(ctx1b.rev()) in revs: ctx1a = ctx1b else: args = diffopts def dodiff(): assert not (hascopies and len(MAR) > 1), \ 'dodiff cannot handle copies when diffing dirs' sa = [mod_a, add_a, rem_a] sb = [mod_b, add_b, rem_b] ctxs = [ctx1a, ctx1b, ctx2] # If more than one file, diff on working dir copy. copyworkingdir = len(MAR) > 1 dirs, labels, fns_and_mtimes = snapshotset(repo, ctxs, sa, sb, cpy, copyworkingdir) dir1a, dir1b, dir2 = dirs label1a, label1b, label2 = labels fns_and_mtime = fns_and_mtimes[2] if len(MAR) > 1 and label2 == '': label2 = 'working files' def getfile(fname, dir, label): file = os.path.join(qtlib.gettempdir(), dir, fname) if os.path.isfile(file): return fname+label, file nullfile = os.path.join(qtlib.gettempdir(), 'empty') fp = open(nullfile, 'w') fp.close() return (hglib.fromunicode(_nonexistant, 'replace') + label, nullfile) # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(MAR) == 1: file2 = MAR.pop() file2local = util.localpath(file2) if file2 in cto: file1 = util.localpath(cpy[file2]) else: file1 = file2 label1a, dir1a = getfile(file1, dir1a, label1a) if do3way: label1b, dir1b = getfile(file1, dir1b, label1b) label2, dir2 = getfile(file2local, dir2, label2) if do3way: label1a += '[local]' label1b += '[other]' label2 += '[merged]' repoagent = repo._pyqtobj # TODO replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b, plabel1=label1a, plabel2=label1b, phash1=str(ctx1a), phash2=str(ctx1b), repo=hglib.fromunicode(repoagent.displayName()), clabel=label2, child=dir2, chash=str(ctx2)) launchtool(diffcmd, args, replace, True) # detect if changes were made to mirrored working files for copy_fn, working_fn, mtime in fns_and_mtime: try: if os.lstat(copy_fn).st_mtime != mtime: ui.debug('file changed while diffing. ' 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) except EnvironmentError: pass # Ignore I/O errors or missing files def dodiffwrapper(): try: dodiff() finally: # cleanup happens atexit ui.note('cleaning up temp directory\n') if opts.get('mainapp'): dodiffwrapper() else: # We are not the main application, so this must be done in a # background thread thread = threading.Thread(target=dodiffwrapper, name='visualdiff') thread.setDaemon(True) thread.start()
def dodiff(ui, repo, cmdline, pats, opts): '''Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir ''' revs = opts.get('rev') change = opts.get('change') do3way = '$parent2' in cmdline if revs and change: msg = _('cannot specify --rev and --change at the same time') raise util.Abort(msg) elif change: node2 = scmutil.revsingle(repo, change, None).node() node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = scmutil.revpair(repo, revs) if not revs: node1b = repo.dirstate.p2() else: node1b = nullid # Disable 3-way merge if there is only one parent if do3way: if node1b == nullid: do3way = False matcher = scmutil.match(repo[node2], pats, opts) mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3]) if do3way: mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3]) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b common = modadd | rem_a | rem_b if not common: return 0 tmproot = tempfile.mkdtemp(prefix='extdiff.') try: # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0] rev1a = '@%d' % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0] rev1b = '@%d' % repo[node1b].rev() else: dir1b = None rev1b = '' fns_and_mtime = [] # If node2 in not the wc or there is >1 change, copy it dir2root = '' rev2 = '' if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0] rev2 = '@%d' % repo[node2].rev() elif len(common) > 1: #we only actually need to get the files to copy back to #the working dir in this case (because the other cases #are: diffing 2 revisions or single file -- in which case #the file is already directly passed to the diff tool). dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot) else: # This lets the diff tool open the changed file directly dir2 = '' dir2root = repo.root label1a = rev1a label1b = rev1b label2 = rev2 # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 # Function to quote file/dir names in the argument string. # When not operating in 3-way mode, an empty string is # returned for parent2 replace = { 'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b, 'plabel1': label1a, 'plabel2': label1b, 'clabel': label2, 'child': dir2, 'root': repo.root } def quote(match): pre = match.group(2) key = match.group(3) if not do3way and key == 'parent2': return pre return pre + util.shellquote(replace[key]) # Match parent2 first, so 'parent1?' will match both parent1 and parent regex = (r'''(['"]?)([^\s'"$]*)''' r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') if not do3way and not re.search(regex, cmdline): cmdline += ' $parent1 $child' cmdline = re.sub(regex, quote, cmdline) ui.debug('running %r in %s\n' % (cmdline, tmproot)) ui.system(cmdline, cwd=tmproot) for copy_fn, working_fn, mtime in fns_and_mtime: if os.lstat(copy_fn).st_mtime != mtime: ui.debug('file changed while diffing. ' 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) return 1 finally: ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot)
def dodiff(ui, repo, cmdline, pats, opts): '''Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir ''' revs = opts.get('rev') change = opts.get('change') do3way = '$parent2' in cmdline if revs and change: msg = _('cannot specify --rev and --change at the same time') raise error.Abort(msg) elif change: node2 = scmutil.revsingle(repo, change, None).node() node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = scmutil.revpair(repo, revs) if not revs: node1b = repo.dirstate.p2() else: node1b = nullid # Disable 3-way merge if there is only one parent if do3way: if node1b == nullid: do3way = False subrepos=opts.get('subrepos') matcher = scmutil.match(repo[node2], pats, opts) if opts.get('patch'): if subrepos: raise error.Abort(_('--patch cannot be used with --subrepos')) if node2 is None: raise error.Abort(_('--patch requires two revisions')) else: mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3]) if do3way: mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher, listsubrepos=subrepos)[:3]) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b common = modadd | rem_a | rem_b if not common: return 0 tmproot = tempfile.mkdtemp(prefix='extdiff.') try: if not opts.get('patch'): # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] rev1a = '@%d' % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0] rev1b = '@%d' % repo[node1b].rev() else: dir1b = None rev1b = '' fns_and_mtime = [] # If node2 in not the wc or there is >1 change, copy it dir2root = '' rev2 = '' if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] rev2 = '@%d' % repo[node2].rev() elif len(common) > 1: #we only actually need to get the files to copy back to #the working dir in this case (because the other cases #are: diffing 2 revisions or single file -- in which case #the file is already directly passed to the diff tool). dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot, subrepos) else: # This lets the diff tool open the changed file directly dir2 = '' dir2root = repo.root label1a = rev1a label1b = rev1b label2 = rev2 # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 else: template = 'hg-%h.patch' cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()], template=repo.vfs.reljoin(tmproot, template), match=matcher) label1a = cmdutil.makefilename(repo, template, node1a) label2 = cmdutil.makefilename(repo, template, node2) dir1a = repo.vfs.reljoin(tmproot, label1a) dir2 = repo.vfs.reljoin(tmproot, label2) dir1b = None label1b = None fns_and_mtime = [] # Function to quote file/dir names in the argument string. # When not operating in 3-way mode, an empty string is # returned for parent2 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b, 'plabel1': label1a, 'plabel2': label1b, 'clabel': label2, 'child': dir2, 'root': repo.root} def quote(match): pre = match.group(2) key = match.group(3) if not do3way and key == 'parent2': return pre return pre + util.shellquote(replace[key]) # Match parent2 first, so 'parent1?' will match both parent1 and parent regex = (r'''(['"]?)([^\s'"$]*)''' r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') if not do3way and not re.search(regex, cmdline): cmdline += ' $parent1 $child' cmdline = re.sub(regex, quote, cmdline) ui.debug('running %r in %s\n' % (cmdline, tmproot)) ui.system(cmdline, cwd=tmproot) for copy_fn, working_fn, mtime in fns_and_mtime: if os.lstat(copy_fn).st_mtime != mtime: ui.debug('file changed while diffing. ' 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) return 1 finally: ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot)
def dodiff(ui, repo, cmdline, pats, opts): '''Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir ''' revs = opts.get('rev') change = opts.get('change') do3way = '$parent2' in cmdline if revs and change: msg = _('cannot specify --rev and --change at the same time') raise error.Abort(msg) elif change: node2 = scmutil.revsingle(repo, change, None).node() node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = scmutil.revpair(repo, revs) if not revs: node1b = repo.dirstate.p2() else: node1b = nullid # Disable 3-way merge if there is only one parent if do3way: if node1b == nullid: do3way = False subrepos = opts.get('subrepos') matcher = scmutil.match(repo[node2], pats, opts) if opts.get('patch'): if subrepos: raise error.Abort(_('--patch cannot be used with --subrepos')) if node2 is None: raise error.Abort(_('--patch requires two revisions')) else: mod_a, add_a, rem_a = list( map(set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3])) if do3way: mod_b, add_b, rem_b = list( map( set, repo.status(node1b, node2, matcher, listsubrepos=subrepos)[:3])) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b common = modadd | rem_a | rem_b if not common: return 0 tmproot = tempfile.mkdtemp(prefix='extdiff.') try: if not opts.get('patch'): # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] rev1a = '@%d' % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0] rev1b = '@%d' % repo[node1b].rev() else: dir1b = None rev1b = '' fnsandstat = [] # If node2 in not the wc or there is >1 change, copy it dir2root = '' rev2 = '' if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] rev2 = '@%d' % repo[node2].rev() elif len(common) > 1: #we only actually need to get the files to copy back to #the working dir in this case (because the other cases #are: diffing 2 revisions or single file -- in which case #the file is already directly passed to the diff tool). dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos) else: # This lets the diff tool open the changed file directly dir2 = '' dir2root = repo.root label1a = rev1a label1b = rev1b label2 = rev2 # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 else: template = 'hg-%h.patch' cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()], fntemplate=repo.vfs.reljoin(tmproot, template), match=matcher) label1a = cmdutil.makefilename(repo, template, node1a) label2 = cmdutil.makefilename(repo, template, node2) dir1a = repo.vfs.reljoin(tmproot, label1a) dir2 = repo.vfs.reljoin(tmproot, label2) dir1b = None label1b = None fnsandstat = [] # Function to quote file/dir names in the argument string. # When not operating in 3-way mode, an empty string is # returned for parent2 replace = { 'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b, 'plabel1': label1a, 'plabel2': label1b, 'clabel': label2, 'child': dir2, 'root': repo.root } def quote(match): pre = match.group(2) key = match.group(3) if not do3way and key == 'parent2': return pre return pre + util.shellquote(replace[key]) # Match parent2 first, so 'parent1?' will match both parent1 and parent regex = (r'''(['"]?)([^\s'"$]*)''' r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') if not do3way and not re.search(regex, cmdline): cmdline += ' $parent1 $child' cmdline = re.sub(regex, quote, cmdline) ui.debug('running %r in %s\n' % (cmdline, tmproot)) ui.system(cmdline, cwd=tmproot, blockedtag='extdiff') for copy_fn, working_fn, st in fnsandstat: cpstat = os.lstat(copy_fn) # Some tools copy the file and attributes, so mtime may not detect # all changes. A size check will detect more cases, but not all. # The only certain way to detect every case is to diff all files, # which could be expensive. # copyfile() carries over the permission, so the mode check could # be in an 'elif' branch, but for the case where the file has # changed without affecting mtime or size. if (cpstat.st_mtime != st.st_mtime or cpstat.st_size != st.st_size or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)): ui.debug('file changed while diffing. ' 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) return 1 finally: ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot)
def dodiff(ui, repo, cmdline, pats, opts): """Do the actual diff.""" revs = opts.get('rev') old, new = scmutil.revpair(repo, revs) subrepos = opts.get('subrepos') matcher = scmutil.match(new, pats, opts) status = old.status(new, matcher, listsubrepos=subrepos) copy = copies.pathcopies(old, new, matcher) mod, add, rem = map(set, status[:3]) paths_new = mod | add paths_old = mod | set(copy.values()) paths_all = paths_old | paths_new if not paths_all: return 0 tmproot = pycompat.mkdtemp(prefix='extdiff2.') try: # Always make a copy of old dir_old = snapshot(ui, repo, paths_old, old.node(), tmproot, subrepos) dir_old = os.path.join(tmproot, dir_old) label_old = '@%d' % old.rev() # If new in not the wc, copy it if new.node(): dir_new = snapshot(ui, repo, paths_new, new.node(), tmproot, subrepos) label_new = '@%d' % new.rev() else: # This lets the diff tool open the changed file(s) directly dir_new = '' label_new = '' # Diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist for path in paths_new: path = util.localpath(path) path_old = os.path.join(dir_old, copy.get(path, path)) label_old = path + label_old #if not os.path.isfile(path_old): #path_old = os.devnull path_new = os.path.join(repo.root, path) if not dir_new: path_new = os.path.relpath(path_new) label_new = path + label_new # Function to quote file/dir names in the argument string. replace = { 'old': path_old, 'olabel': label_old, 'nlabel': label_new, 'new': path_new, 'root': repo.root } def quote(match): pre = match.group(2) key = match.group(3) return pre + procutil.shellquote(replace[key]) regex = (br"""(['"]?)([^\s'"$]*)""" br'\$(old|new|olabel|nlabel|root)\1') if not re.search(regex, cmdline): cmdline2 = cmdline + ' $old $new' else: cmdline2 = cmdline cmdline3 = re.sub(regex, quote, cmdline2) ui.write(pycompat.bytestr(cmdline3) + b'\n') ui.system(cmdline3, blockedtag='extdiff2') return 1 finally: ui.note(_('cleaning up temp directory\n')) shutil.rmtree(tmproot)
def dodiff(ui, repo, cmdline, pats, opts): """Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir """ revs = opts.get("rev") change = opts.get("change") do3way = "$parent2" in cmdline if revs and change: msg = _("cannot specify --rev and --change at the same time") raise util.Abort(msg) elif change: node2 = scmutil.revsingle(repo, change, None).node() node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = scmutil.revpair(repo, revs) if not revs: node1b = repo.dirstate.p2() else: node1b = nullid # Disable 3-way merge if there is only one parent if do3way: if node1b == nullid: do3way = False matcher = scmutil.match(repo[node2], pats, opts) mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3]) if do3way: mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3]) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b common = modadd | rem_a | rem_b if not common: return 0 tmproot = tempfile.mkdtemp(prefix="extdiff.") try: # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0] rev1a = "@%d" % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0] rev1b = "@%d" % repo[node1b].rev() else: dir1b = None rev1b = "" fns_and_mtime = [] # If node2 in not the wc or there is >1 change, copy it dir2root = "" rev2 = "" if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0] rev2 = "@%d" % repo[node2].rev() elif len(common) > 1: # we only actually need to get the files to copy back to # the working dir in this case (because the other cases # are: diffing 2 revisions or single file -- in which case # the file is already directly passed to the diff tool). dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot) else: # This lets the diff tool open the changed file directly dir2 = "" dir2root = repo.root label1a = rev1a label1b = rev1b label2 = rev2 # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 # Function to quote file/dir names in the argument string. # When not operating in 3-way mode, an empty string is # returned for parent2 replace = { "parent": dir1a, "parent1": dir1a, "parent2": dir1b, "plabel1": label1a, "plabel2": label1b, "clabel": label2, "child": dir2, "root": repo.root, } def quote(match): pre = match.group(2) key = match.group(3) if not do3way and key == "parent2": return pre return pre + util.shellquote(replace[key]) # Match parent2 first, so 'parent1?' will match both parent1 and parent regex = r"""(['"]?)([^\s'"$]*)""" r"\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1" if not do3way and not re.search(regex, cmdline): cmdline += " $parent1 $child" cmdline = re.sub(regex, quote, cmdline) ui.debug("running %r in %s\n" % (cmdline, tmproot)) ui.system(cmdline, cwd=tmproot) for copy_fn, working_fn, mtime in fns_and_mtime: if os.lstat(copy_fn).st_mtime != mtime: ui.debug("file changed while diffing. " "Overwriting: %s (src: %s)\n" % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) return 1 finally: ui.note(_("cleaning up temp directory\n")) shutil.rmtree(tmproot)
def dodiff(ui, repo, cmdline, pats, opts, guitool=False): '''Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir ''' revs = opts.get(b'rev') change = opts.get(b'change') do3way = b'$parent2' in cmdline if revs and change: msg = _(b'cannot specify --rev and --change at the same time') raise error.Abort(msg) elif change: ctx2 = scmutil.revsingle(repo, change, None) ctx1a, ctx1b = ctx2.p1(), ctx2.p2() else: ctx1a, ctx2 = scmutil.revpair(repo, revs) if not revs: ctx1b = repo[None].p2() else: ctx1b = repo[nullid] perfile = opts.get(b'per_file') confirm = opts.get(b'confirm') node1a = ctx1a.node() node1b = ctx1b.node() node2 = ctx2.node() # Disable 3-way merge if there is only one parent if do3way: if node1b == nullid: do3way = False subrepos = opts.get(b'subrepos') matcher = scmutil.match(repo[node2], pats, opts) if opts.get(b'patch'): if subrepos: raise error.Abort(_(b'--patch cannot be used with --subrepos')) if perfile: raise error.Abort(_(b'--patch cannot be used with --per-file')) if node2 is None: raise error.Abort(_(b'--patch requires two revisions')) else: mod_a, add_a, rem_a = map( set, repo.status(node1a, node2, matcher, listsubrepos=subrepos)[:3]) if do3way: mod_b, add_b, rem_b = map( set, repo.status(node1b, node2, matcher, listsubrepos=subrepos)[:3], ) else: mod_b, add_b, rem_b = set(), set(), set() modadd = mod_a | add_a | mod_b | add_b common = modadd | rem_a | rem_b if not common: return 0 tmproot = pycompat.mkdtemp(prefix=b'extdiff.') try: if not opts.get(b'patch'): # Always make a copy of node1a (and node1b, if applicable) dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0] rev1a = b'@%d' % repo[node1a].rev() if do3way: dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0] rev1b = b'@%d' % repo[node1b].rev() else: dir1b = None rev1b = b'' fnsandstat = [] # If node2 in not the wc or there is >1 change, copy it dir2root = b'' rev2 = b'' if node2: dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0] rev2 = b'@%d' % repo[node2].rev() elif len(common) > 1: # we only actually need to get the files to copy back to # the working dir in this case (because the other cases # are: diffing 2 revisions or single file -- in which case # the file is already directly passed to the diff tool). dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos) else: # This lets the diff tool open the changed file directly dir2 = b'' dir2root = repo.root label1a = rev1a label1b = rev1b label2 = rev2 # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 else: template = b'hg-%h.patch' with formatter.nullformatter(ui, b'extdiff', {}) as fm: cmdutil.export( repo, [repo[node1a].rev(), repo[node2].rev()], fm, fntemplate=repo.vfs.reljoin(tmproot, template), match=matcher, ) label1a = cmdutil.makefilename(repo[node1a], template) label2 = cmdutil.makefilename(repo[node2], template) dir1a = repo.vfs.reljoin(tmproot, label1a) dir2 = repo.vfs.reljoin(tmproot, label2) dir1b = None label1b = None fnsandstat = [] if not perfile: # Run the external tool on the 2 temp directories or the patches cmdline = formatcmdline( cmdline, repo.root, do3way=do3way, parent1=dir1a, plabel1=label1a, parent2=dir1b, plabel2=label1b, child=dir2, clabel=label2, ) ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)) ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff') else: # Run the external tool once for each pair of files _runperfilediff( cmdline, repo.root, ui, guitool=guitool, do3way=do3way, confirm=confirm, commonfiles=common, tmproot=tmproot, dir1a=dir1a, dir1b=dir1b, dir2root=dir2root, dir2=dir2, rev1a=rev1a, rev1b=rev1b, rev2=rev2, ) for copy_fn, working_fn, st in fnsandstat: cpstat = os.lstat(copy_fn) # Some tools copy the file and attributes, so mtime may not detect # all changes. A size check will detect more cases, but not all. # The only certain way to detect every case is to diff all files, # which could be expensive. # copyfile() carries over the permission, so the mode check could # be in an 'elif' branch, but for the case where the file has # changed without affecting mtime or size. if (cpstat[stat.ST_MTIME] != st[stat.ST_MTIME] or cpstat.st_size != st.st_size or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)): ui.debug(b'file changed while diffing. ' b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) return 1 finally: ui.note(_(b'cleaning up temp directory\n')) shutil.rmtree(tmproot)
def dodiff(ui, repo, cmdline, pats, opts, guitool=False): """Do the actual diff: - copy to a temp structure if diffing 2 internal revisions - copy to a temp structure if diffing working revision with another one and more than 1 file is changed - just invoke the diff for a single file in the working dir """ cmdutil.check_at_most_one_arg(opts, b'rev', b'change') revs = opts.get(b'rev') from_rev = opts.get(b'from') to_rev = opts.get(b'to') change = opts.get(b'change') do3way = b'$parent2' in cmdline if change: ctx2 = scmutil.revsingle(repo, change, None) ctx1a, ctx1b = ctx2.p1(), ctx2.p2() elif from_rev or to_rev: repo = scmutil.unhidehashlikerevs( repo, [from_rev] + [to_rev], b'nowarn' ) ctx1a = scmutil.revsingle(repo, from_rev, None) ctx1b = repo[nullrev] ctx2 = scmutil.revsingle(repo, to_rev, None) else: ctx1a, ctx2 = scmutil.revpair(repo, revs) if not revs: ctx1b = repo[None].p2() else: ctx1b = repo[nullrev] # Disable 3-way merge if there is only one parent if do3way: if ctx1b.rev() == nullrev: do3way = False matcher = scmutil.match(ctx2, pats, opts) if opts.get(b'patch'): if opts.get(b'subrepos'): raise error.Abort(_(b'--patch cannot be used with --subrepos')) if opts.get(b'per_file'): raise error.Abort(_(b'--patch cannot be used with --per-file')) if ctx2.node() is None: raise error.Abort(_(b'--patch requires two revisions')) tmproot = pycompat.mkdtemp(prefix=b'extdiff.') try: if opts.get(b'patch'): return diffpatch( ui, repo, ctx1a.node(), ctx2.node(), tmproot, matcher, cmdline ) return diffrevs( ui, repo, ctx1a, ctx1b, ctx2, matcher, tmproot, cmdline, do3way, guitool, opts, ) finally: ui.note(_(b'cleaning up temp directory\n')) shutil.rmtree(tmproot)
def visualdiff(ui, repo, pats, opts): revs = opts.get('rev', []) change = opts.get('change') try: ctx1b = None if change: ctx2 = repo[change] p = ctx2.parents() if len(p) > 1: ctx1a, ctx1b = p else: ctx1a = p[0] else: n1, n2 = scmutil.revpair(repo, revs) ctx1a, ctx2 = repo[n1], repo[n2] p = ctx2.parents() if not revs and len(p) > 1: ctx1b = p[1] except (error.LookupError, error.RepoError): QMessageBox.warning(None, _('Unable to find changeset'), _('You likely need to refresh this application')) return None pats = scmutil.expandpats(pats) m = match.match(repo.root, '', pats, None, None, 'relpath') n2 = ctx2.node() mod_a, add_a, rem_a = map(set, repo.status(ctx1a.node(), n2, m)[:3]) if ctx1b: mod_b, add_b, rem_b = map(set, repo.status(ctx1b.node(), n2, m)[:3]) cpy = copies.mergecopies(repo, ctx1a, ctx1b, ctx1a.ancestor(ctx1b))[0] else: cpy = copies.pathcopies(ctx1a, ctx2) mod_b, add_b, rem_b = set(), set(), set() MA = mod_a | add_a | mod_b | add_b MAR = MA | rem_a | rem_b if not MAR: QMessageBox.information(None, _('No file changes'), _('There are no file changes to view')) return None detectedtools = hglib.difftools(repo.ui) if not detectedtools: QMessageBox.warning(None, _('No diff tool found'), _('No visual diff tools were detected')) return None preferred = besttool(repo.ui, detectedtools, opts.get('tool')) # Build tool list based on diff-patterns matches toollist = set() patterns = repo.ui.configitems('diff-patterns') patterns = [(p, t) for p, t in patterns if t in detectedtools] for path in MAR: for pat, tool in patterns: mf = match.match(repo.root, '', [pat]) if mf(path): toollist.add(tool) break else: toollist.add(preferred) cto = cpy.keys() for path in MAR: if path in cto: hascopies = True break else: hascopies = False force = repo.ui.configbool('tortoisehg', 'forcevdiffwin') if len(toollist) > 1 or (hascopies and len(MAR) > 1) or force: usewin = True else: preferred = toollist.pop() dirdiff = repo.ui.configbool('merge-tools', preferred + '.dirdiff') dir3diff = repo.ui.configbool('merge-tools', preferred + '.dir3diff') usewin = repo.ui.configbool('merge-tools', preferred + '.usewin') if not usewin and len(MAR) > 1: if ctx1b is not None: usewin = not dir3diff else: usewin = not dirdiff if usewin: # Multiple required tools, or tool does not support directory diffs sa = [mod_a, add_a, rem_a] sb = [mod_b, add_b, rem_b] dlg = FileSelectionDialog(repo, pats, ctx1a, sa, ctx1b, sb, ctx2, cpy) return dlg # We can directly use the selected tool, without a visual diff window diffcmd, diffopts, mergeopts = detectedtools[preferred] # Disable 3-way merge if there is only one parent or no tool support do3way = False if ctx1b: if mergeopts: do3way = True args = mergeopts else: args = diffopts if str(ctx1b.rev()) in revs: ctx1a = ctx1b else: args = diffopts def dodiff(): assert not (hascopies and len(MAR) > 1), \ 'dodiff cannot handle copies when diffing dirs' sa = [mod_a, add_a, rem_a] sb = [mod_b, add_b, rem_b] ctxs = [ctx1a, ctx1b, ctx2] # If more than one file, diff on working dir copy. copyworkingdir = len(MAR) > 1 dirs, labels, fns_and_mtimes = snapshotset(repo, ctxs, sa, sb, cpy, copyworkingdir) dir1a, dir1b, dir2 = dirs label1a, label1b, label2 = labels fns_and_mtime = fns_and_mtimes[2] if len(MAR) > 1 and label2 == '': label2 = 'working files' def getfile(fname, dir, label): file = os.path.join(qtlib.gettempdir(), dir, fname) if os.path.isfile(file): return fname + label, file nullfile = os.path.join(qtlib.gettempdir(), 'empty') fp = open(nullfile, 'w') fp.close() return (hglib.fromunicode(_nonexistant, 'replace') + label, nullfile) # If only one change, diff the files instead of the directories # Handle bogus modifies correctly by checking if the files exist if len(MAR) == 1: file2 = MAR.pop() file2local = util.localpath(file2) if file2 in cto: file1 = util.localpath(cpy[file2]) else: file1 = file2 label1a, dir1a = getfile(file1, dir1a, label1a) if do3way: label1b, dir1b = getfile(file1, dir1b, label1b) label2, dir2 = getfile(file2local, dir2, label2) if do3way: label1a += '[local]' label1b += '[other]' label2 += '[merged]' replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b, plabel1=label1a, plabel2=label1b, phash1=str(ctx1a), phash2=str(ctx1b), repo=hglib.fromunicode(repo.displayname), clabel=label2, child=dir2, chash=str(ctx2)) launchtool(diffcmd, args, replace, True) # detect if changes were made to mirrored working files for copy_fn, working_fn, mtime in fns_and_mtime: try: if os.lstat(copy_fn).st_mtime != mtime: ui.debug('file changed while diffing. ' 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) util.copyfile(copy_fn, working_fn) except EnvironmentError: pass # Ignore I/O errors or missing files def dodiffwrapper(): try: dodiff() finally: # cleanup happens atexit ui.note('cleaning up temp directory\n') if opts.get('mainapp'): dodiffwrapper() else: # We are not the main application, so this must be done in a # background thread thread = threading.Thread(target=dodiffwrapper, name='visualdiff') thread.setDaemon(True) thread.start()