def filemerge(ui, fname, patchedfname): 'Launch the preferred visual diff tool for two text files' detectedtools = hglib.difftools(ui) if not detectedtools: gdialog.Prompt(_('No diff tool found'), _('No visual diff tools were detected'), None).run() return None preferred = besttool(ui, detectedtools) diffcmd, diffopts, mergeopts = detectedtools[preferred] replace = dict(parent=fname, parent1=fname, plabel1=fname + _('[working copy]'), repo='', phash1='', phash2='', chash='', child=patchedfname, clabel=_('[original]')) launchtool(diffcmd, diffopts, replace, True)
def filemerge(ui, fname, patchedfname): 'Launch the preferred visual diff tool for two text files' detectedtools = hglib.difftools(ui) if not detectedtools: QMessageBox.warning(None, _('No diff tool found'), _('No visual diff tools were detected')) return None preferred = besttool(ui, detectedtools) diffcmd, diffopts, mergeopts = detectedtools[preferred] replace = dict(parent=fname, parent1=fname, plabel1=fname + _('[working copy]'), repo='', phash1='', phash2='', chash='', child=patchedfname, clabel=_('[original]')) launchtool(diffcmd, diffopts, replace, True)
def __init__(self, repo, pats, ctx1a, sa, ctx1b, sb, ctx2, cpy): 'Initialize the Dialog' QDialog.__init__(self) self.setWindowIcon(qtlib.geticon('visualdiff')) if ctx2.rev() is None: title = _('working changes') elif ctx1a == ctx2.parents()[0]: title = _('changeset %d:%s') % (ctx2.rev(), ctx2) else: title = _('revisions %d:%s to %d:%s') \ % (ctx1a.rev(), ctx1a, ctx2.rev(), ctx2) title = _('Visual Diffs - ') + title if pats: title += _(' filtered') self.setWindowTitle(title) self.resize(650, 250) repoagent = repo._pyqtobj # TODO self.reponame = hglib.fromunicode(repoagent.displayName()) self.ctxs = (ctx1a, ctx1b, ctx2) self.filesets = (sa, sb) self.copies = cpy self.repo = repo self.curFile = None layout = QVBoxLayout() self.setLayout(layout) lbl = QLabel(_('Temporary files are removed when this dialog ' 'is closed')) layout.addWidget(lbl) list = QListWidget() layout.addWidget(list) self.list = list list.itemActivated.connect(self.itemActivated) tools = hglib.difftools(repo.ui) preferred = besttool(repo.ui, tools) self.diffpath, self.diffopts, self.mergeopts = tools[preferred] self.tools = tools self.preferred = preferred if len(tools) > 1: hbox = QHBoxLayout() combo = QComboBox() lbl = QLabel(_('Select Tool:')) lbl.setBuddy(combo) hbox.addWidget(lbl) hbox.addWidget(combo, 1) layout.addLayout(hbox) for i, name in enumerate(tools.iterkeys()): combo.addItem(name) if name == preferred: defrow = i combo.setCurrentIndex(defrow) list.currentRowChanged.connect(self.updateToolSelection) combo.currentIndexChanged[str].connect(self.onToolSelected) self.toolCombo = combo BB = QDialogButtonBox bb = BB() layout.addWidget(bb) if ctx2.rev() is None: pass # Do not offer directory diffs when the working directory # is being referenced directly elif ctx1b: self.p1button = bb.addButton(_('Dir diff to p1'), BB.ActionRole) self.p1button.pressed.connect(self.p1dirdiff) self.p2button = bb.addButton(_('Dir diff to p2'), BB.ActionRole) self.p2button.pressed.connect(self.p2dirdiff) self.p3button = bb.addButton(_('3-way dir diff'), BB.ActionRole) self.p3button.pressed.connect(self.threewaydirdiff) else: self.dbutton = bb.addButton(_('Directory diff'), BB.ActionRole) self.dbutton.pressed.connect(self.p1dirdiff) self.updateDiffButtons(preferred) QShortcut(QKeySequence('CTRL+D'), self.list, self.activateCurrent) QTimer.singleShot(0, self.fillmodel)
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 __init__(self, repo, pats, ctx1a, sa, ctx1b, sb, ctx2, cpy): 'Initialize the Dialog' QDialog.__init__(self) self.setWindowIcon(qtlib.geticon('visualdiff')) if ctx2.rev() is None: title = _('working changes') elif ctx1a == ctx2.parents()[0]: title = _('changeset %d:%s') % (ctx2.rev(), ctx2) else: title = _('revisions %d:%s to %d:%s') \ % (ctx1a.rev(), ctx1a, ctx2.rev(), ctx2) title = _('Visual Diffs - ') + title if pats: title += _(' filtered') self.setWindowTitle(title) self.resize(650, 250) self.reponame = hglib.fromunicode(repo.displayname) self.ctxs = (ctx1a, ctx1b, ctx2) self.filesets = (sa, sb) self.copies = cpy self.repo = repo self.curFile = None layout = QVBoxLayout() self.setLayout(layout) lbl = QLabel( _('Temporary files are removed when this dialog ' 'is closed')) layout.addWidget(lbl) list = QListWidget() layout.addWidget(list) self.list = list list.itemActivated.connect(self.itemActivated) tools = hglib.difftools(repo.ui) preferred = besttool(repo.ui, tools) self.diffpath, self.diffopts, self.mergeopts = tools[preferred] self.tools = tools self.preferred = preferred if len(tools) > 1: hbox = QHBoxLayout() combo = QComboBox() lbl = QLabel(_('Select Tool:')) lbl.setBuddy(combo) hbox.addWidget(lbl) hbox.addWidget(combo, 1) layout.addLayout(hbox) for i, name in enumerate(tools.iterkeys()): combo.addItem(name) if name == preferred: defrow = i combo.setCurrentIndex(defrow) list.currentRowChanged.connect(self.updateToolSelection) combo.currentIndexChanged['QString'].connect(self.onToolSelected) self.toolCombo = combo BB = QDialogButtonBox bb = BB() layout.addWidget(bb) if ctx2.rev() is None: pass # Do not offer directory diffs when the working directory # is being referenced directly elif ctx1b: self.p1button = bb.addButton(_('Dir diff to p1'), BB.ActionRole) self.p1button.pressed.connect(self.p1dirdiff) self.p2button = bb.addButton(_('Dir diff to p2'), BB.ActionRole) self.p2button.pressed.connect(self.p2dirdiff) self.p3button = bb.addButton(_('3-way dir diff'), BB.ActionRole) self.p3button.pressed.connect(self.threewaydirdiff) else: self.dbutton = bb.addButton(_('Directory diff'), BB.ActionRole) self.dbutton.pressed.connect(self.p1dirdiff) self.updateDiffButtons(preferred) QShortcut(QKeySequence('CTRL+D'), self.list, self.activateCurrent) QTimer.singleShot(0, self.fillmodel)
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()
def __init__(self, repo, pats, ctx1a, sa, ctx1b, sb, ctx2, cpy): 'Initialize the Dialog' gtk.Dialog.__init__(self, title=_('Visual Diffs')) gtklib.set_tortoise_icon(self, 'menushowchanged.ico') gtklib.set_tortoise_keys(self) if ctx2.rev() is None: title = _('working changes') elif ctx1a == ctx2.parents()[0]: title = _('changeset ') + str(ctx2.rev()) else: title = _('revisions %d to %d') % (ctx1a.rev(), ctx2.rev()) title = _('Visual Diffs - ') + title if pats: title += _(' filtered') self.set_title(title) self.set_default_size(400, 250) self.set_has_separator(False) self.reponame=hglib.get_reponame(repo) self.ctxs = (ctx1a, ctx1b, ctx2) self.copies = cpy self.ui = repo.ui lbl = gtk.Label(_('Temporary files are removed when this dialog ' 'is closed')) self.vbox.pack_start(lbl, False, False, 2) scroller = gtk.ScrolledWindow() scroller.set_shadow_type(gtk.SHADOW_IN) scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) treeview = gtk.TreeView() self.treeview = treeview treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) treeview.set_search_equal_func(self.search_filelist) scroller.add(treeview) self.vbox.pack_start(scroller, True, True, 2) treeview.connect('row-activated', self.rowactivated) treeview.set_headers_visible(False) treeview.set_property('enable-grid-lines', True) treeview.set_enable_search(False) accelgroup = gtk.AccelGroup() self.add_accel_group(accelgroup) mod = gtklib.get_thg_modifier() key, modifier = gtk.accelerator_parse(mod+'d') treeview.add_accelerator('thg-diff', accelgroup, key, modifier, gtk.ACCEL_VISIBLE) treeview.connect('thg-diff', self.rowactivated) cell = gtk.CellRendererText() stcol = gtk.TreeViewColumn('Status', cell) stcol.set_resizable(True) stcol.add_attribute(cell, 'text', 0) treeview.append_column(stcol) cell = gtk.CellRendererText() fcol = gtk.TreeViewColumn('Filename', cell) fcol.set_resizable(True) fcol.add_attribute(cell, 'text', 1) treeview.append_column(fcol) model = gtk.ListStore(str, str) treeview.set_model(model) tools = hglib.difftools(repo.ui) preferred = besttool(repo.ui, tools) self.diffpath, self.diffopts, self.mergeopts = tools[preferred] hbox = gtk.HBox() self.vbox.pack_start(hbox, False, False, 2) if ctx2.rev() is None: pass # Do not offer directory diffs when the working directory # is being referenced directly elif ctx1b: self.p1button = gtk.Button(_('Dir diff to p1')) self.p1button.connect('pressed', self.p1dirdiff) self.p2button = gtk.Button(_('Dir diff to p2')) self.p2button.connect('pressed', self.p2dirdiff) self.p3button = gtk.Button(_('3-way dir diff')) self.p3button.connect('pressed', self.threewaydirdiff) hbox.pack_end(self.p3button, False, False) hbox.pack_end(self.p2button, False, False) hbox.pack_end(self.p1button, False, False) else: self.dbutton = gtk.Button(_('Directory diff')) self.dbutton.connect('pressed', self.p1dirdiff) hbox.pack_end(self.dbutton, False, False) self.update_diff_buttons(preferred) if len(tools) > 1: combo = gtk.combo_box_new_text() for i, name in enumerate(tools.iterkeys()): combo.append_text(name) if name == preferred: defrow = i combo.set_active(defrow) combo.connect('changed', self.toolselect, tools) hbox.pack_start(combo, False, False, 2) patterns = repo.ui.configitems('diff-patterns') patterns = [(p, t) for p,t in patterns if t in tools] filesel = treeview.get_selection() filesel.connect('changed', self.fileselect, repo, combo, tools, patterns, preferred) gobject.idle_add(self.fillmodel, repo, model, sa, sb)