Mercurial and Git clients can push and pull from this alias URL to interact with this repository. You can change to which repository an alias points by going to the Aliases link on the project page.
# visdiff.py - launch external visual diff tools## Copyright 2009 Steve Borho <steve@borho.org>## This software may be used and distributed according to the terms of the# GNU General Public License version 2, incorporated herein by reference.importosimportsysimportsubprocessimportstatimportshutilimportthreadingimporttempfileimportrefrommercurialimporthg,cmdutil,util,error,match,copiesfromtortoisehg.hgqt.i18nimport_fromtortoisehg.utilimporthglib,pathsfromtortoisehg.hgqtimportqtlib,thgrepofromPyQt4.QtCoreimport*fromPyQt4.QtGuiimport*# Match parent2 first, so 'parent1?' will match both parent1 and parent_regex='\$(parent2|parent1?|child|plabel1|plabel2|clabel|repo|phash1|phash2|chash)'_nonexistant=_('[non-existant]')# This global counter is incremented for each visual diff done in a session# It ensures that the names for snapshots created do not collide._diffCount=0defsnapshotset(repo,ctxs,sa,sb,copies,copyworkingdir=False):'''snapshot files from parent-child set of revisions'''ctx1a,ctx1b,ctx2=ctxsmod_a,add_a,rem_a=samod_b,add_b,rem_b=sbglobal_diffCount_diffCount+=1ifcopies:sources=set(copies.values())else:sources=set()# Always make a copy of ctx1afiles1a=sources|mod_a|rem_a|((mod_b|add_b)-add_a)dir1a,fns_mtime1a=snapshot(repo,files1a,ctx1a)label1a='@%d:%s'%(ctx1a.rev(),ctx1a)# Make a copy of ctx1b if relevantifctx1b:files1b=sources|mod_b|rem_b|((mod_a|add_a)-add_b)dir1b,fns_mtime1b=snapshot(repo,files1b,ctx1b)label1b='@%d:%s'%(ctx1b.rev(),ctx1b)else:dir1b=Nonefns_mtime1b=[]label1b=''# Either make a copy of ctx2, or use working dir directly if relevant.files2=mod_a|add_a|mod_b|add_bifctx2.rev()isNone:ifcopyworkingdir:dir2,fns_mtime2=snapshot(repo,files2,ctx2)else:dir2=repo.rootfns_mtime2=[]# If ctx2 is working copy, use empty label.label2=''else:dir2,fns_mtime2=snapshot(repo,files2,ctx2)label2='@%d:%s'%(ctx2.rev(),ctx2)dirs=[dir1a,dir1b,dir2]labels=[label1a,label1b,label2]fns_and_mtimes=[fns_mtime1a,fns_mtime1b,fns_mtime2]returndirs,labels,fns_and_mtimesdefsnapshot(repo,files,ctx):'''snapshot files as of some revision'''dirname=os.path.basename(repo.root)or'root'dirname+='.%d'%_diffCountifctx.rev()isnotNone:dirname+='.%d'%ctx.rev()base=os.path.join(qtlib.gettempdir(),dirname)fns_and_mtime=[]ifnotos.path.exists(base):os.mkdir(base)forfninfiles:wfn=util.pconvert(fn)ifnotwfninctx:# File doesn't exist; could be a bogus modifycontinuedest=os.path.join(base,wfn)ifos.path.exists(dest):# File has already been snapshotcontinuedestdir=os.path.dirname(dest)ifnotos.path.isdir(destdir):os.makedirs(destdir)data=repo.wwritedata(wfn,ctx[wfn].data())f=open(dest,'wb')f.write(data)f.close()ifctx.rev()isNone:fns_and_mtime.append((dest,repo.wjoin(fn),os.lstat(dest).st_mtime))else:# Make file read/only, to indicate it's static (archival) natureos.chmod(dest,stat.S_IREAD)returnbase,fns_and_mtimedeflaunchtool(cmd,opts,replace,block):defquote(match):key=match.group()[1:]returnutil.shellquote(replace[key])ifisinstance(cmd,unicode):cmd=hglib.fromunicode(cmd)lopts=[]foroptinopts:ifisinstance(opt,unicode):lopts.append(hglib.fromunicode(opt))else:lopts.append(opt)args=' '.join(lopts)args=re.sub(_regex,quote,args)cmdline=util.shellquote(cmd)+' '+argscmdline=util.quotecommand(cmdline)try:proc=subprocess.Popen(cmdline,shell=True,creationflags=qtlib.openflags,stderr=subprocess.PIPE,stdout=subprocess.PIPE,stdin=subprocess.PIPE)ifblock:proc.communicate()except(OSError,EnvironmentError),e:QMessageBox.warning(None,_('Tool launch failure'),_('%s : %s')%(cmd,str(e)))deffilemerge(ui,fname,patchedfname):'Launch the preferred visual diff tool for two text files'detectedtools=hglib.difftools(ui)ifnotdetectedtools:QMessageBox.warning(None,_('No diff tool found'),_('No visual diff tools were detected'))returnNonepreferred=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)defbesttool(ui,tools,force=None):'Select preferred or highest priority tool from dictionary'preferred=forceorui.config('tortoisehg','vdiff')or \
ui.config('ui','merge')ifpreferredandpreferredintools:returnpreferredpris=[]fortintools.keys():p=int(ui.config('merge-tools',t+'.priority',0))pris.append((-p,t))tools=sorted(pris)returntools[0][1]defvisualdiff(ui,repo,pats,opts):revs=opts.get('rev',[])change=opts.get('change')try:ctx1b=Noneifchange:ctx2=repo[change]p=ctx2.parents()iflen(p)>1:ctx1a,ctx1b=pelse:ctx1a=p[0]else:n1,n2=hglib.revpair(repo,revs)ctx1a,ctx2=repo[n1],repo[n2]p=ctx2.parents()ifnotrevsandlen(p)>1:ctx1b=p[1]except(error.LookupError,error.RepoError):QMessageBox.warning(None,_('Unable to find changeset'),_('You likely need to refresh this application'))returnNonepats=hglib.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])ifctx1b:mod_b,add_b,rem_b=map(set,repo.status(ctx1b.node(),n2,m)[:3])cpy=copies.copies(repo,ctx1a,ctx1b,ctx1a.ancestor(ctx1b))[0]else:cpy=copies.copies(repo,ctx1a,ctx2,repo[-1])[0]mod_b,add_b,rem_b=set(),set(),set()MA=mod_a|add_a|mod_b|add_bMAR=MA|rem_a|rem_bifnotMAR:QMessageBox.information(None,_('No file changes'),_('There are no file changes to view'))returnNonedetectedtools=hglib.difftools(repo.ui)ifnotdetectedtools:QMessageBox.warning(None,_('No diff tool found'),_('No visual diff tools were detected'))returnNonepreferred=besttool(repo.ui,detectedtools,opts.get('tool'))# Build tool list based on diff-patterns matchestoollist=set()patterns=repo.ui.configitems('diff-patterns')patterns=[(p,t)forp,tinpatternsiftindetectedtools]forpathinMAR:forpat,toolinpatterns:mf=match.match(repo.root,'',[pat])ifmf(path):toollist.add(tool)breakelse:toollist.add(preferred)cto=cpy.keys()forpathinMAR:ifpathincto:hascopies=Truebreakelse:hascopies=Falseforce=repo.ui.configbool('tortoisehg','forcevdiffwin')iflen(toollist)>1or(hascopiesandlen(MAR)>1)orforce:usewin=Trueelse: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')ifnotusewinandlen(MAR)>1:ifctx1bisnotNone:usewin=notdir3diffelse:usewin=notdirdiffifusewin:# Multiple required tools, or tool does not support directory diffssa=[mod_a,add_a,rem_a]sb=[mod_b,add_b,rem_b]dlg=FileSelectionDialog(repo,pats,ctx1a,sa,ctx1b,sb,ctx2,cpy)returndlg# We can directly use the selected tool, without a visual diff windowdiffcmd,diffopts,mergeopts=detectedtools[preferred]# Disable 3-way merge if there is only one parent or no tool supportdo3way=Falseifctx1b:ifmergeopts:do3way=Trueargs=mergeoptselse:args=diffoptsifstr(ctx1b.rev())inrevs:ctx1a=ctx1belse:args=diffoptsdefdodiff():assertnot(hascopiesandlen(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)>1dirs,labels,fns_and_mtimes=snapshotset(repo,ctxs,sa,sb,cpy,copyworkingdir)dir1a,dir1b,dir2=dirslabel1a,label1b,label2=labelsfns_and_mtime=fns_and_mtimes[2]iflen(MAR)>1andlabel2=='':label2='working files'defgetfile(fname,dir,label):file=os.path.join(qtlib.gettempdir(),dir,fname)ifos.path.isfile(file):returnfname+label,file nullfile = os.path.join(qtlib.gettempdir(), 'empty')
fp = open(nullfile, 'w')
fp.close()
- return _nonexistant+label, nullfile
+ 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
iflen(MAR)==1:file2=util.localpath(MAR.pop())iffile2incto:file1=util.localpath(cpy[file2])else:file1=file2label1a,dir1a=getfile(file1,dir1a,label1a)ifdo3way:label1b,dir1b=getfile(file1,dir1b,label1b)label2,dir2=getfile(file2,dir2,label2)ifdo3way: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 filesforcopy_fn,working_fn,mtimeinfns_and_mtime:try:ifos.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)exceptEnvironmentError:pass# Ignore I/O errors or missing filesdefdodiffwrapper():try:dodiff()finally:# cleanup happens atexitui.note(_('cleaning up temp directory\n'))ifopts.get('mainapp'):dodiffwrapper()else:# We are not the main application, so this must be done in a# background threadthread=threading.Thread(target=dodiffwrapper,name='visualdiff')thread.setDaemon(True)thread.start()classFileSelectionDialog(QDialog):'Dialog for selecting visual diff candidates'def__init__(self,repo,pats,ctx1a,sa,ctx1b,sb,ctx2,cpy):'Initialize the Dialog'QDialog.__init__(self)self.setWindowIcon(qtlib.geticon('visualdiff'))ifctx2.rev()isNone:title=_('working changes')elifctx1a==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 - ')+titleifpats: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=cpyself.repo=repoself.curFile=Nonelayout=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=listlist.itemActivated.connect(self.itemActivated)tools=hglib.difftools(repo.ui)preferred=besttool(repo.ui,tools)self.diffpath,self.diffopts,self.mergeopts=tools[preferred]self.tools=toolsself.preferred=preferrediflen(tools)>1:hbox=QHBoxLayout()combo=QComboBox()lbl=QLabel(_('Select Tool:'))lbl.setBuddy(combo)hbox.addWidget(lbl)hbox.addWidget(combo,1)layout.addLayout(hbox)fori,nameinenumerate(tools.iterkeys()):combo.addItem(name)ifname==preferred:defrow=icombo.setCurrentIndex(defrow)list.currentRowChanged.connect(self.updateToolSelection)combo.currentIndexChanged['QString'].connect(self.onToolSelected)self.toolCombo=comboBB=QDialogButtonBoxbb=BB()layout.addWidget(bb)ifctx2.rev()isNone:pass# Do not offer directory diffs when the working directory# is being referenced directlyelifctx1b: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)@pyqtSlot()deffillmodel(self):repo=self.reposa,sb=self.filesetsself.dirs,self.revs=snapshotset(repo,self.ctxs,sa,sb,self.copies)[:2]defget_status(file,mod,add,rem):iffileinmod:return'M'iffileinadd:return'A'iffileinrem:return'R'return' 'mod_a,add_a,rem_a=saforfinsorted(mod_a|add_a|rem_a):status=get_status(f,mod_a,add_a,rem_a)row=QString('%s%s'%(status,hglib.tounicode(f)))self.list.addItem(row)@pyqtSlot(QString)defonToolSelected(self,tool):'user selected a tool from the tool combo'tool=hglib.fromunicode(tool)asserttoolinself.toolsself.diffpath,self.diffopts,self.mergeopts=self.tools[tool]self.updateDiffButtons(tool)@pyqtSlot(int)defupdateToolSelection(self,row):'user selected a file, pick an appropriate tool from combo'ifrow==-1:returnrepo=self.repopatterns=repo.ui.configitems('diff-patterns')patterns=[(p,t)forp,tinpatternsiftinself.tools]fname=self.list.item(row).text()[2:]fname=hglib.fromunicode(fname)ifself.curFile==fname:returnself.curFile=fnameforpat,toolinpatterns:mf=match.match(repo.root,'',[pat])ifmf(fname):selected=toolbreakelse:selected=self.preferredfori,nameinenumerate(self.tools.iterkeys()):ifname==selected:self.toolCombo.setCurrentIndex(i)defactivateCurrent(self):'CTRL+D has been pressed'row=self.list.currentRow()ifrow>=0:self.launch(self.list.item(row).text()[2:])defitemActivated(self,item):'A QListWidgetItem has been activated'self.launch(item.text()[2:])defupdateDiffButtons(self,tool):ifhasattr(self,'p1button'):d2=self.repo.ui.configbool('merge-tools',tool+'.dirdiff')d3=self.repo.ui.configbool('merge-tools',tool+'.dir3diff')self.p1button.setEnabled(d2)self.p2button.setEnabled(d2)self.p3button.setEnabled(d3)elifhasattr(self,'dbutton'):d2=self.repo.ui.configbool('merge-tools',tool+'.dirdiff')self.dbutton.setEnabled(d2)deflaunch(self,fname):fname=hglib.fromunicode(fname)source=self.copies.get(fname,None)dir1a,dir1b,dir2=self.dirsrev1a,rev1b,rev2=self.revsctx1a,ctx1b,ctx2=self.ctxsdefgetfile(ctx,dir,fname,source):m=ctx.manifest()iffnameinm:path=os.path.join(dir,util.localpath(fname))returnfname,pathelifsourceandsourceinm:path=os.path.join(dir,util.localpath(source))returnsource,pathelse: nullfile = os.path.join(qtlib.gettempdir(), 'empty')
fp = open(nullfile, 'w')
fp.close()
- return _nonexistant, nullfile
+ return hglib.fromunicode(_nonexistant, 'replace'), nullfile
local, file1a = getfile(ctx1a, dir1a, fname, source)
if ctx1b:
other,file1b=getfile(ctx1b,dir1b,fname,source)else:other,file1b=fname,Nonefname,file2=getfile(ctx2,dir2,fname,None)label1a=local+rev1alabel1b=other+rev1blabel2=fname+rev2ifctx1b:label1a+='[local]'label1b+='[other]'label2+='[merged]'# Function to quote file/dir names in the argument stringreplace=dict(parent=file1a,parent1=file1a,plabel1=label1a,parent2=file1b,plabel2=label1b,repo=self.reponame,phash1=str(ctx1a),phash2=str(ctx1b),chash=str(ctx2),clabel=label2,child=file2)args=ctx1bandself.mergeoptsorself.diffoptslaunchtool(self.diffpath,args,replace,False)defp1dirdiff(self):dir1a,dir1b,dir2=self.dirsrev1a,rev1b,rev2=self.revsctx1a,ctx1b,ctx2=self.ctxsreplace=dict(parent=dir1a,parent1=dir1a,plabel1=rev1a,repo=self.reponame,phash1=str(ctx1a),phash2=str(ctx1b),chash=str(ctx2),parent2='',plabel2='',clabel=rev2,child=dir2)launchtool(self.diffpath,self.diffopts,replace,False)defp2dirdiff(self):dir1a,dir1b,dir2=self.dirsrev1a,rev1b,rev2=self.revsctx1a,ctx1b,ctx2=self.ctxsreplace=dict(parent=dir1b,parent1=dir1b,plabel1=rev1b,repo=self.reponame,phash1=str(ctx1a),phash2=str(ctx1b),chash=str(ctx2),parent2='',plabel2='',clabel=rev2,child=dir2)launchtool(self.diffpath,self.diffopts,replace,False)defthreewaydirdiff(self):dir1a,dir1b,dir2=self.dirsrev1a,rev1b,rev2=self.revsctx1a,ctx1b,ctx2=self.ctxsreplace=dict(parent=dir1a,parent1=dir1a,plabel1=rev1a,repo=self.reponame,phash1=str(ctx1a),phash2=str(ctx1b),chash=str(ctx2),parent2=dir1b,plabel2=rev1b,clabel=dir2,child=rev2)launchtool(self.diffpath,self.mergeopts,replace,False)defrun(ui,*pats,**opts):try:path=opts.get('bundle')orpaths.find_root()repo=thgrepo.repository(ui,path=path)excepterror.RepoError:ui.warn(_('No repository found here')+'\n')returnNonepats=hglib.canonpaths(pats)ifopts.get('canonpats'):pats=list(pats)+opts['canonpats']dlg=visualdiff(ui,repo,pats,opts)ifnotdlg:sys.exit()returndlg
Attach a Trello Card
Add a tag
Your session has expired
You are no longer logged in. Please log in and try your request again.
Filter RSS Feed
This RSS feed URL allows you to see the contents of your current filter using any feed reader.
This link includes a special authentication token. If you share the URL with anyone else, they can see this RSS feed's activity. You can disable these tokens when needed.
Your current filter is unsaved; changing it won't affect this RSS feed.