This is the first - early - code that adds ignore functionality to EGit.
Currently it reads in all ignore patterns upon workspace startup into an
ignore cache. From this cache the ignore state of a resource is evaluated
in the same fashion as git does.
The code does not yet react to changes in ignore files but I'm planning to add
that soon and I can share a lot of code for that.
I send this code to receive feedback and to give you insight into what I'm
doing with it. I'm new both to EGit programming and Eclipse programming so
there might be things that could be done more elegantly :-)
A few notes:
- The patches are rebased on the current master (e3440623)
- The order of the patches must be re-arranged, but that is rather easy. The
correct order - once finished - would be:
Build up the ignore patterns cache upon workspace startup.
Use the ignore patterns cache to determine ignores
Enable the ignore handling of the plugin
Optimise ignore evaluation
Do not set .git as a Team ignore pattern
- The core.excludesfile code is currently untested, the other code seems to be
in a good state.
- There are a few FIXMEs in the code with questions and tasks. It's a work in
progress and these will disappear.
Ferry Huberts (5):
Build up the ignore patterns cache upon workspace startup.
Enable the ignore handling of the plugin
Optimise ignore evaluation
Do not set .git as a Team ignore pattern
Use the ignore patterns cache to determine ignores
org.spearce.egit.core/META-INF/MANIFEST.MF | 1 +
org.spearce.egit.core/plugin.xml | 6 -
.../src/org/spearce/egit/core/ignores/DType.java | 44 ++
.../src/org/spearce/egit/core/ignores/Exclude.java | 243 +++++++++
.../spearce/egit/core/ignores/GitIgnoreData.java | 180 +++++++
.../org/spearce/egit/core/ignores/IgnoreFile.java | 82 +++
.../egit/core/ignores/IgnoreFileOutside.java | 543 ++++++++++++++++++++
.../egit/core/ignores/IgnoreProjectCache.java | 245 +++++++++
.../egit/core/ignores/IgnoreRepositoryCache.java | 358 +++++++++++++
.../org/spearce/egit/core/op/TrackOperation.java | 7 +-
.../spearce/egit/core/project/GitProjectData.java | 8 +
.../decorators/DecoratableResourceAdapter.java | 11 +-
org.spearce.jgit/META-INF/MANIFEST.MF | 1 +
13 files changed, 1712 insertions(+), 17 deletions(-)
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/DType.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFile.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFileOutside.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java
The .git ignore pattern is only valid in the context that it matches
a .git directory that is actually a repository.
Signed-off-by: Ferry Huberts <redacted>
---
org.spearce.egit.core/plugin.xml | 6 ------
1 files changed, 0 insertions(+), 6 deletions(-)
@@ -94,14 +94,15 @@ public boolean visit(IResource resource) throws CoreException {// first. If a resource within a ignored folder is marked// we ignore it here, i.e. there is no way to unmark it expect// by explicitly selecting and invoking track on it.+booleanignored=GitIgnoreData.isIgnored(resource);if(resource.getType()==IResource.FILE){Entryentry=index.getEntry(repoPath);-if(!GitIgnoreData.isIgnored(resource)||((entry!=null)&&entry.isAssumedValid())){+if(!ignored||((entry!=null)&&entry.isAssumedValid())){entry=index.add(rm.getWorkDir(),newFile(rm.getWorkDir(),repoPath));entry.setAssumeValid(false);}}-if(GitIgnoreData.isIgnored(resource))+if(ignored)returnfalse;}catch(IOExceptione){
@@ -96,12 +96,12 @@ public boolean visit(IResource resource) throws CoreException {// by explicitly selecting and invoking track on it.if(resource.getType()==IResource.FILE){Entryentry=index.getEntry(repoPath);-if(!Team.isIgnoredHint(resource)||entry!=null&&entry.isAssumedValid()){+if(!GitIgnoreData.isIgnored(resource)||((entry!=null)&&entry.isAssumedValid())){entry=index.add(rm.getWorkDir(),newFile(rm.getWorkDir(),repoPath));entry.setAssumeValid(false);}}-if(Team.isIgnoredHint(resource))+if(GitIgnoreData.isIgnored(resource))returnfalse;}catch(IOExceptione){
@@ -105,6 +108,110 @@ Exclude(final String pattern, final String base,}/*+*InterfaceMethods+*/++/**+*TriestomatchagivenresourcetotheExclude+*+*@parampathName+*thefullpathoftheresource,relativetothecheckout+*directory+*@parambaseName+*thebaseNameoftheresource+*@paramresourceType+*thetypeoftheresource+*@returntruewhentheresourcematchesthisExclude,falseotherwise+*+*/+booleanisMatch(finalStringpathName,finalStringbaseName,+finalDTyperesourceType){+/* this is needed to make the exact same match as git does */+Stringxbase=pathName;+finalintpos=xbase.lastIndexOf('/');+if(pos<0){+xbase="";+}else{+xbase=xbase.substring(0,pos+1);+}++if(mustBeDir&&(resourceType!=DType.DT_DIR)){+returnfalse;+}++if(noDir){+/* pattern does not contain directories. dir.c: match basename */+if(noWildcard){+/* pattern does not contain directories and has no wildcards */+if(baseName.equals(pattern)){+returnto_exclude;+}+}elseif(endsWith){+/*+*patterndoesnotcontaindirectoriesandresourcemustend+*withpattern.substring(1)+*/+if(baseName.endsWith(pattern.substring(1))){+returnto_exclude;+}+}else{+/*+*patterndoesnotcontaindirectories,haswildcards,anddoes+*notendwithpattern.substring(1)+*/+try{+finalFileNameMatchermatcher=newFileNameMatcher(+pattern,null);+matcher.append(baseName);+if(matcher.isMatch()){+returnto_exclude;+}+}catch(finalInvalidPatternExceptione){+returnfalse;+}+}+}else{+/*+*patterncontainsdirectories.dir.c:matchwithFNM_PATHNAME:+*exclude(e.g.'this.pattern')hasbase(baselenlong)implicitly+*infrontofit.+*/+finalintbaselen=base.length();+StringmatchPattern=this.pattern;+if(matchPattern.startsWith("/")){+matchPattern=matchPattern.substring(1);+}++if((pathName.length()<baselen)+||((baselen>0)&&(pathName.charAt(baselen-1)!='/'))+||!pathName.substring(0,baselen).equals(xbase)){+returnfalse;+}++finalStringremainingResourceName=pathName.substring(baselen);+if(noWildcard){+/* pattern contains directories and has no wildcards */+if(remainingResourceName.equals(matchPattern)){+returnto_exclude;+}+}else{+/* pattern contains directories and has wildcards */+try{+finalFileNameMatchermatcher=newFileNameMatcher(+matchPattern,Character.valueOf('/'));+matcher.append(remainingResourceName);+if(matcher.isMatch()){+returnto_exclude;+}+}catch(finalInvalidPatternExceptione){+returnfalse;+}+}+}+returnfalse;+}++/**PrivateMethods*/
@@ -128,7 +129,7 @@ public synchronized static void importWorkspaceIgnores() {*FIXME:alsodothefile`gitconfig--global--get*core.excludesfile`,isthisalreadycovered?RepositoryConfig*globalConfig=newRepositoryConfig(null,newFile(FS.userHome(),-*".gitconfig"));+*".gitconfig"));RepositoryConfig.openUserConfig*//*
@@ -136,4 +137,44 @@ public synchronized static void importWorkspaceIgnores() {*repositories.toString());*/}++/**+*@paramresource+*theresourcetocheck+*@returnnullwhennotmatched,thematchingExcludeotherwisethe+*resource+*/+synchronizedstaticExcludeisResourceExcluded(finalIResourceresource){+if(resource==null){+returnnull;+}++finalRepositoryMappingmapping=RepositoryMapping+.getMapping(resource);+if(mapping==null){+returnnull;+}++finalIgnoreRepositoryCachecache=repositories.get(mapping+.getRepository());+if(cache==null){+returnnull;+}++/* FIXME: also check global core.excludesfile, is this already covered? */++returncache.isIgnored(resource,mapping);+}++/**+*@paramresource+*@returntruewhentheresourceisignored+*/+publicsynchronizedstaticbooleanisIgnored(finalIResourceresource){+if(isResourceExcluded(resource)!=null){+returntrue;+}++returnTeam.isIgnoredHint(resource);+}}
@@ -198,4 +199,47 @@ synchronized void processIgnoreFile(final IFile ignoreFile,// break;// }}++synchronizedExcludeisIgnored(finalStringpathName,+finalStringbaseName,finalDTypedType,+finalIPathdeepestDirectory){+IPathsearchDir=deepestDirectory;+StringlookupKey=(searchDir.isEmpty()?searchDir.toString()+:searchDir.toString()+"/");+finalbooleanresult=false;+searcher:while(!result){+/* look for the first ignore file up in the tree */+while(!ignoreFilesIndex.containsKey(lookupKey)){+searchDir=searchDir.removeLastSegments(1);+if(searchDir.isEmpty()){+breaksearcher;+}+lookupKey=(searchDir.isEmpty()?searchDir.toString()+:searchDir.toString()+"/");+}+finalIFileignoreFile=ignoreFilesIndex.get(lookupKey);++/* when found then try to match the resource to those patterns */+if(ignoreFile!=null){+finalLinkedList<Exclude>excludeList=ignoreFiles+.get(ignoreFile);+for(inti=excludeList.size()-1;i>=0;i--){+finalExcludex=excludeList.get(i);+if(x.isMatch(pathName,baseName,dType)){+returnx;+}+}+}++if(searchDir.isEmpty()){+breaksearcher;+}+searchDir=searchDir.removeLastSegments(1);+lookupKey=(searchDir.isEmpty()?searchDir.toString():searchDir+.toString()++"/");+}++returnnull;+}}
@@ -35,7 +35,7 @@/** the repository */privateRepositoryrepository=null;-/** the checkout directory for the repository */+/** the checkout directory for the repository, full path, platform specific */privateStringcheckoutDir=null;/** the cache that holds ignore data on a per-project basis */
@@ -59,6 +59,9 @@/** the core.excludesfile setting */privateStringcoreExcludesSetting=null;+/** the exclude for the repository itself */+privateExcluderepositoryExclude=null;+/***Retrieveaprojectmappingfromtheprojectscache.Whentheprojectis*notyetinthecachethencreateanewmappingforitandstoreitinthe
@@ -0,0 +1,158 @@+/*******************************************************************************+*Copyright(C)2009,FerryHuberts<ferry.huberts@pelagic.nl>+*+*Allrightsreserved.Thisprogramandtheaccompanyingmaterials+*aremadeavailableunderthetermsoftheEclipsePublicLicensev1.0+*SeeLICENSEforthefulllicensetext,alsoavailable.+*******************************************************************************/+packageorg.spearce.egit.core.ignores;++importjava.util.regex.Pattern;++/**+*Thisclassdescribesanignorepatterninthesamewayasgitdoes,withsome+*extrainformationtosupportEclipsespecificfunctionality.+*+*Thegitdefinitioncanbefoundinthesourcefiledir.h,withinthe+*exclude_liststructuredefinition.Thecodecanbefoundinthesourcefile+*dir.c:excluded_1+*/+classExclude{+/** the pattern to match */+privateStringpattern=null;++/**+*thedirectoryinwhichthepatternisanchored,relativetothecheckout+*directoryandwithatrailingslash(exceptwheninthecheckout+*directory,inwhichcaseitwillbeanemptystring).SlashesareinUnix+*format:forwardslashes+*/+privateStringbase=null;++/**+*truewhentheresourcemustbeexcludedwhenmatched,falseincaseofa+*negativepattern:whenitmustbeincluded+*/+privatebooleanto_exclude=true;++/** true when the resource must be a directory */+privatebooleanmustBeDir=false;++/** true when the pattern does not contain directories */+privatebooleannoDir=false;++/** true when the resource must end with pattern.substring(1) */+privatebooleanendsWith=false;++/** true when the pattern has no wildcards */+privatebooleannoWildcard=false;++/*+*ExtraInformation+*/++/**+*thefullpathnameoftheignorefile.Storedsothatausercanask+*'whichpatterninwhichignorefilemakesthisresourcebeignored?'+*/+privateStringignoreFileAbsolutePath=null;++/**+*thelinenumberofthepatternintheignorefile.Storedforthesame+*reasonastheignoreFileFullPathfield+*/+privateintlineNumber=0;++/**+*Constructor.Seethegitsourcefiledir.c,methodadd_exclude+*+*@parampattern+*thepatterntomatch+*@parambase+*thedirectoryinwhichthepatternisanchored,relativeto+*thecheckoutdirectoryandwithatrailingslash(exceptwhen+*inthecheckoutdirectory,inwhichcaseitwillbeanempty+*string).SlashesareinUnixformat:forwardslashes+*@paramignoreFileAbsolutePath+*thefullpathnameoftheignorefile.Storedsothatauser+*canask'whichpatterninwhichignorefilemakesthis+*resourcebeignored?'+*@paramlineNumber+*thelinenumberofthepatternintheignorefile.Storedfor+*thesamereasonastheignoreFileFullPathfield+*/+Exclude(finalStringpattern,finalStringbase,+finalStringignoreFileAbsolutePath,finalintlineNumber){+this.pattern=pattern;+this.base=base;++this.to_exclude=!this.pattern.startsWith("!");+if(!this.to_exclude){+this.pattern=this.pattern.substring(1);+}++this.mustBeDir=this.pattern.endsWith("/");+if(this.mustBeDir){+this.pattern=this.pattern.substring(0,this.pattern.length()-1);+}+this.noDir=!this.pattern.contains("/");+this.noWildcard=no_wildcard(this.pattern);+this.endsWith=((this.pattern.charAt(0)=='*')&&no_wildcard(this.pattern+.substring(1)));++this.ignoreFileAbsolutePath=ignoreFileAbsolutePath;+this.lineNumber=lineNumber;+}++/*+*PrivateMethods+*/++privatestaticPatternwildcardPattern=Pattern+.compile("^.*[\\*\\?\\[\\{].*$");++/* dir.c::no_wildcard */+privatebooleanno_wildcard(finalStringstring){+return!wildcardPattern.matcher(string).matches();+}++/*+*Getters/Setters+*/++publicStringgetIgnoreFileAbsolutePath(){+returnignoreFileAbsolutePath;+}++publicintgetLineNumber(){+returnlineNumber;+}++/**+*@returnthebase+*/+publicStringgetBase(){+returnbase;+}++/**+*@returnthenoDir+*/+publicbooleanisNoDir(){+returnnoDir;+}++/**+*@returntheendsWith+*/+publicbooleanisEndsWith(){+returnendsWith;+}++/**+*@returnthenoWildcard+*/+publicbooleanisNoWildcard(){+returnnoWildcard;+}+}
@@ -0,0 +1,82 @@+/*******************************************************************************+*Copyright(C)2009,FerryHuberts<ferry.huberts@pelagic.nl>+*+*Allrightsreserved.Thisprogramandtheaccompanyingmaterials+*aremadeavailableunderthetermsoftheEclipsePublicLicensev1.0+*SeeLICENSEforthefulllicensetext,alsoavailable.+*******************************************************************************/+packageorg.spearce.egit.core.ignores;++importjava.io.BufferedReader;+importjava.io.IOException;+importjava.io.InputStreamReader;+importjava.util.LinkedList;++importorg.eclipse.core.resources.IFile;+importorg.eclipse.core.resources.IResource;+importorg.eclipse.core.runtime.CoreException;++/**+*Thisclassimplementsignorefilehelpers.+*/+classIgnoreFile{+/**+*Thismethodparsesanignorefile.+*+*@paramignoreFileBaseDir+*thedirectoryoftheignorefile,relativetothecheckout+*directoryandwithatrailingslash.SlashesareinUnix+*format:forwardslashes+*@paramignoreFile+*the.gitignorefile+*@returnreturnsasetofExcludesthatreflectstheignorepatterns.+*/+staticLinkedList<Exclude>parseIgnoreFile(finalStringignoreFileBaseDir,+finalIFileignoreFile){+finalLinkedList<Exclude>excludes=newLinkedList<Exclude>();++/* make sure that the resource is synchronized */+try{+if(!ignoreFile.isSynchronized(IResource.DEPTH_ZERO)){+ignoreFile.refreshLocal(IResource.DEPTH_ZERO,null);+}+}catch(finalExceptione){+returnexcludes;+}++Stringbase=ignoreFileBaseDir;+if(base.equals("/")){+base="";+}+finalStringignoreFileName=ignoreFile.getLocation().toOSString();+BufferedReadertxtIn=null;+intlineNumber=0;+try{+txtIn=newBufferedReader(newInputStreamReader(ignoreFile+.getContents()));+Stringline;+while((line=txtIn.readLine())!=null){+lineNumber++;+line=line.trim();+if(!line.startsWith("#")&&(line.length()>0)){+excludes.add(newExclude(line,base,ignoreFileName,+lineNumber));+}+}+}catch(finalCoreExceptione){+/* swallow */+}catch(finalIOExceptione){+/* swallow */+}finally{+try{+if(txtIn!=null){+txtIn.close();+txtIn=null;+}+}catch(finalIOExceptione1){+/* swallow */+}+}+returnexcludes;+}+}
@@ -0,0 +1,543 @@+/*******************************************************************************+*Copyright(C)2009,FerryHuberts<ferry.huberts@pelagic.nl>+*+*Allrightsreserved.Thisprogramandtheaccompanyingmaterials+*aremadeavailableunderthetermsoftheEclipsePublicLicensev1.0+*SeeLICENSEforthefulllicensetext,alsoavailable.+*******************************************************************************/+packageorg.spearce.egit.core.ignores;++importjava.io.File;+importjava.io.FileInputStream;+importjava.io.FileNotFoundException;+importjava.io.InputStream;+importjava.io.Reader;+importjava.net.URI;+importjava.util.Map;++importorg.eclipse.core.resources.IContainer;+importorg.eclipse.core.resources.IFile;+importorg.eclipse.core.resources.IFileState;+importorg.eclipse.core.resources.IMarker;+importorg.eclipse.core.resources.IProject;+importorg.eclipse.core.resources.IProjectDescription;+importorg.eclipse.core.resources.IResourceProxy;+importorg.eclipse.core.resources.IResourceProxyVisitor;+importorg.eclipse.core.resources.IResourceVisitor;+importorg.eclipse.core.resources.IWorkspace;+importorg.eclipse.core.resources.ResourceAttributes;+importorg.eclipse.core.runtime.CoreException;+importorg.eclipse.core.runtime.IPath;+importorg.eclipse.core.runtime.IProgressMonitor;+importorg.eclipse.core.runtime.IStatus;+importorg.eclipse.core.runtime.Path;+importorg.eclipse.core.runtime.QualifiedName;+importorg.eclipse.core.runtime.Status;+importorg.eclipse.core.runtime.content.IContentDescription;+importorg.eclipse.core.runtime.jobs.ISchedulingRule;+importorg.spearce.jgit.lib.Repository;++/**+*Thisclassisonlyusedtobeabletostorethe.gitignorefilesinthe+*ignorecachethatareoutsidetheprojects(upinthecheckoutdirectorytree+*fromtheprojectrootdirectory)+*/+classIgnoreFileOutsideimplementsIFile{+privateStringrelativeDir=null;++privateStringrelativePath=null;++privateStringresourceBaseName=null;++privateStringabsoluteDir=null;++privateStringfullPath=null;++privateFilefullPathFile=null;++privatelonglastModificationTime=0L;++/**+*Constructor+*+*@paramrepository+*therepository.whennullthentherelativeDirparameteris+*takentobeanabsolutepath+*@paramdirectory+*thedirectoryinwhichthepatternisanchored,relativeto+*thecheckoutdirectoryandwithatrailingslash(exceptwhen+*inthecheckoutdirectory,inwhichcaseitwillbeanempty+*string).SlashesareinUnixformat:forwardslashes.When+*repositoryisnullthenthisistakentobeanabsolute+*directorywithslashesinplatformformat.+*@paramresourceBasename+*thenameoftheignorefile.+*/+IgnoreFileOutside(finalRepositoryrepository,finalStringdirectory,+finalStringresourceBasename){+if((directory==null)||(resourceBasename==null)){+thrownewIllegalArgumentException("Can not handle NULL values: "++directory+", "+resourceBasename);+}++this.relativeDir=directory.replaceAll("/",File.separator);+this.resourceBaseName=resourceBasename+.replaceAll("/",File.separator);+this.relativePath=this.relativeDir+this.resourceBaseName;++StringrepoRoot="";+if(repository!=null){+repoRoot=repository.getWorkDir().getAbsolutePath()++File.separator;+}+this.absoluteDir=repoRoot+this.relativeDir;+this.fullPath=this.absoluteDir+this.resourceBaseName;+this.fullPathFile=newFile(this.fullPath);+}++/* used interface methods */++publicbooleanexists(){+returnthis.fullPathFile.exists();+}++publicInputStreamgetContents()throwsCoreException{+try{+returnnewFileInputStream(fullPath);+}catch(finalFileNotFoundExceptione){+/* FIXME: use actual plugin id */+thrownewCoreException(newStatus(IStatus.WARNING,"git plugin",e+.getLocalizedMessage()));+}+}++publicStringgetName(){+returnresourceBaseName;+}++publicIPathgetProjectRelativePath(){+returnnewPath(relativePath);+}++publicbooleanisSynchronized(finalintdepth){+return(lastModificationTime==this.fullPathFile.lastModified());+}++publicvoidrefreshLocal(finalintdepth,finalIProgressMonitormonitor)+throwsCoreException{+lastModificationTime=this.fullPathFile.lastModified();+return;+}++publicIPathgetLocation(){+returnnewPath(fullPath);+}++/*+*OverriddenMethods+*/++@Override+publicbooleanequals(finalObjectobj){+if(!(objinstanceofIgnoreFileOutside)){+thrownewIllegalArgumentException("Wrong type");+}+return((IgnoreFileOutside)obj).fullPath.equals(this.fullPath);+}++@Override+publicinthashCode(){+returnfullPath.hashCode();+}++@Override+publicStringtoString(){+returnfullPath;+}++/*+*UnusedInterfaceMethods+*/++publicvoidappendContents(finalInputStreamsource,finalintupdateFlags,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidappendContents(finalInputStreamsource,finalbooleanforce,+finalbooleankeepHistory,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoidcreate(finalInputStreamsource,finalbooleanforce,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidcreate(finalInputStreamsource,finalintupdateFlags,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidcreateLink(finalIPathlocalLocation,finalintupdateFlags,+finalIProgressMonitormonitor)throwsCoreException{++/** not used */+}++publicvoidcreateLink(finalURIlocation,finalintupdateFlags,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoiddelete(finalbooleanforce,finalbooleankeepHistory,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicStringgetCharset()throwsCoreException{+returnnull;+}++publicStringgetCharset(finalbooleancheckImplicit)throwsCoreException{+returnnull;+}++publicStringgetCharsetFor(finalReaderreader)throwsCoreException{+returnnull;+}++publicIContentDescriptiongetContentDescription()throwsCoreException{+returnnull;+}++publicInputStreamgetContents(finalbooleanforce)throwsCoreException{+returnnull;+}++publicintgetEncoding()throwsCoreException{+return0;+}++publicIPathgetFullPath(){+returnnull;+}++publicIFileState[]getHistory(finalIProgressMonitormonitor)+throwsCoreException{+returnnull;+}++publicbooleanisReadOnly(){+returnfalse;+}++publicvoidmove(finalIPathdestination,finalbooleanforce,+finalbooleankeepHistory,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoidsetCharset(finalStringnewCharset)throwsCoreException{+/** not used */+}++publicvoidsetCharset(finalStringnewCharset,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidsetContents(finalInputStreamsource,finalintupdateFlags,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidsetContents(finalIFileStatesource,finalintupdateFlags,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidsetContents(finalInputStreamsource,finalbooleanforce,+finalbooleankeepHistory,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoidsetContents(finalIFileStatesource,finalbooleanforce,+finalbooleankeepHistory,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoidaccept(finalIResourceVisitorvisitor)throwsCoreException{+/** not used */+}++publicvoidaccept(finalIResourceProxyVisitorvisitor,+finalintmemberFlags)throwsCoreException{+/** not used */+}++publicvoidaccept(finalIResourceVisitorvisitor,finalintdepth,+finalbooleanincludePhantoms)throwsCoreException{+/** not used */+}++publicvoidaccept(finalIResourceVisitorvisitor,finalintdepth,+finalintmemberFlags)throwsCoreException{+/** not used */+}++publicvoidclearHistory(finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoidcopy(finalIPathdestination,finalbooleanforce,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidcopy(finalIPathdestination,finalintupdateFlags,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidcopy(finalIProjectDescriptiondescription,+finalbooleanforce,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoidcopy(finalIProjectDescriptiondescription,+finalintupdateFlags,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicIMarkercreateMarker(finalStringtype)throwsCoreException{+returnnull;+}++publicIResourceProxycreateProxy(){+returnnull;+}++publicvoiddelete(finalbooleanforce,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoiddelete(finalintupdateFlags,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoiddeleteMarkers(finalStringtype,finalbooleanincludeSubtypes,+finalintdepth)throwsCoreException{+/** not used */+}++publicIMarkerfindMarker(finallongid)throwsCoreException{+returnnull;+}++publicIMarker[]findMarkers(finalStringtype,+finalbooleanincludeSubtypes,finalintdepth)+throwsCoreException{+returnnull;+}++publicintfindMaxProblemSeverity(finalStringtype,+finalbooleanincludeSubtypes,finalintdepth)+throwsCoreException{+return0;+}++publicStringgetFileExtension(){+returnnull;+}++publiclonggetLocalTimeStamp(){+return0;+}++publicURIgetLocationURI(){+returnnull;+}++publicIMarkergetMarker(finallongid){+returnnull;+}++publiclonggetModificationStamp(){+return0;+}++publicIContainergetParent(){+returnnull;+}++publicMapgetPersistentProperties()throwsCoreException{+returnnull;+}++publicStringgetPersistentProperty(finalQualifiedNamekey)+throwsCoreException{+returnnull;+}++publicIProjectgetProject(){+returnnull;+}++publicIPathgetRawLocation(){+returnnull;+}++publicURIgetRawLocationURI(){+returnnull;+}++publicResourceAttributesgetResourceAttributes(){+returnnull;+}++publicMapgetSessionProperties()throwsCoreException{+returnnull;+}++publicObjectgetSessionProperty(finalQualifiedNamekey)+throwsCoreException{+returnnull;+}++publicintgetType(){+return0;+}++publicIWorkspacegetWorkspace(){+returnnull;+}++publicbooleanisAccessible(){+returnfalse;+}++publicbooleanisDerived(){+returnfalse;+}++publicbooleanisDerived(finalintoptions){+returnfalse;+}++publicbooleanisHidden(){+returnfalse;+}++publicbooleanisLinked(){+returnfalse;+}++publicbooleanisLinked(finalintoptions){+returnfalse;+}++publicbooleanisLocal(finalintdepth){+returnfalse;+}++publicbooleanisPhantom(){+returnfalse;+}++publicbooleanisTeamPrivateMember(){+returnfalse;+}++publicvoidmove(finalIPathdestination,finalbooleanforce,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidmove(finalIPathdestination,finalintupdateFlags,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidmove(finalIProjectDescriptiondescription,+finalintupdateFlags,finalIProgressMonitormonitor)+throwsCoreException{+/** not used */+}++publicvoidmove(finalIProjectDescriptiondescription,+finalbooleanforce,finalbooleankeepHistory,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicvoidrevertModificationStamp(finallongvalue)throwsCoreException{+/** not used */+}++publicvoidsetDerived(finalbooleanisDerived)throwsCoreException{+/** not used */+}++publicvoidsetHidden(finalbooleanisHidden)throwsCoreException{+/** not used */+}++publicvoidsetLocal(finalbooleanflag,finalintdepth,+finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publiclongsetLocalTimeStamp(finallongvalue)throwsCoreException{+return0;+}++publicvoidsetPersistentProperty(finalQualifiedNamekey,+finalStringvalue)throwsCoreException{+/** not used */+}++publicvoidsetReadOnly(finalbooleanreadOnly){+/** not used */+}++publicvoidsetResourceAttributes(finalResourceAttributesattributes)+throwsCoreException{+/** not used */+}++publicvoidsetSessionProperty(finalQualifiedNamekey,finalObjectvalue)+throwsCoreException{+/** not used */+}++publicvoidsetTeamPrivateMember(finalbooleanisTeamPrivate)+throwsCoreException{+/** not used */+}++publicvoidtouch(finalIProgressMonitormonitor)throwsCoreException{+/** not used */+}++publicObjectgetAdapter(finalClassadapter){+returnnull;+}++publicbooleancontains(finalISchedulingRulerule){+returnfalse;+}++publicbooleanisConflicting(finalISchedulingRulerule){+returnfalse;+}++}
@@ -0,0 +1,201 @@+/*******************************************************************************+*Copyright(C)2009,FerryHuberts<ferry.huberts@pelagic.nl>+*+*Allrightsreserved.Thisprogramandtheaccompanyingmaterials+*aremadeavailableunderthetermsoftheEclipsePublicLicensev1.0+*SeeLICENSEforthefulllicensetext,alsoavailable.+*******************************************************************************/+packageorg.spearce.egit.core.ignores;++importjava.util.HashMap;+importjava.util.LinkedList;++importorg.eclipse.core.resources.IFile;+importorg.eclipse.core.resources.IFolder;+importorg.eclipse.core.resources.IResource;+importorg.eclipse.core.resources.IResourceDelta;+importorg.eclipse.core.runtime.CoreException;++/**+*ThisclassimplementsacacheofignorepatternsforanEclipseproject:it+*holdsalistofignorepatterns,storedin'ignoreFiles'againsttheignore+*filehandle.WealsokeepanignoreFilesIndexoftheignorefilehandle+*storedagainstthedirectorynames(relativetothe'checkoutDir')ofthe+*ignoreignoreFilesin'ignoreFilesIndex'.Weusethistoaccessthecacheand+*retrievethelistofignorepatterns.Thisisbecausewhentryingto+*determinewhetheraresourceisignoredwemustfirsttrytheignorefilein+*thedirectoryoftheresource,andifitdoesn'tmatch,theninthedirectory+*upfromthat,andsoon,althewayuptothecheckoutdirectory.+*/+classIgnoreProjectCache{+/**+*thedirectoryoftheproject,relativetothecheckout,withatrailing+*slash,exceptwheninthecheckoutdirectoryinwhichcaseitwillbe+*empty+*/+privateStringprojectDirInCheckout=null;++/**+*Mapusedtofind.gitignoreignorefilesinignoreFiles.key=directory+*pathofthe.gitignorefile,relativetothecheckoutdirectory,+*value=IFiletousetoignoreFilesIndexignoreFiles+*/+privatefinalHashMap<String,IFile>ignoreFilesIndex=newHashMap<String,IFile>();++/**+*Mapwith.gitignoreignorefilesandtheirexcludepatterns.+*key=.gitignorefilehandle,value=listwithitsignorepatterns+*/+privatefinalHashMap<IFile,LinkedList<Exclude>>ignoreFiles=newHashMap<IFile,LinkedList<Exclude>>();++/*+*Constructors+*/++/**+*Constructor+*+*@paramprojectDirInCheckout+*thedirectoryoftheproject,relativetothecheckout+*/+IgnoreProjectCache(finalStringprojectDirInCheckout){+if(projectDirInCheckout==null){+thrownewExceptionInInitializerError(+"NULL is not a valid project directory");+}++this.projectDirInCheckout=projectDirInCheckout;+if(!this.projectDirInCheckout.isEmpty()+&&!this.projectDirInCheckout.endsWith("/")){+this.projectDirInCheckout=this.projectDirInCheckout.concat("/");+}+}++/*+*Methods+*/++synchronizedvoidclear(){+ignoreFilesIndex.clear();++for(finalLinkedList<Exclude>excludeList:ignoreFiles.values()){+excludeList.clear();+}+ignoreFiles.clear();+}++synchronizedvoidimportProjectIgnores(finalIResource[]projectChildren){+if(projectChildren==null){+return;+}++for(finalIResourceprojectChild:projectChildren){+if(projectChild!=null){+if(projectChildinstanceofIFile){+if(projectChild.getName().equals(".gitignore")){+StringprojectRelativeDir=projectChild+.getProjectRelativePath().removeLastSegments(1)+.toString();+if(!projectRelativeDir.isEmpty()){+projectRelativeDir=projectRelativeDir+"/";+}+finalStringignoreFileBaseDir=projectDirInCheckout++projectRelativeDir;+processIgnoreFile((IFile)projectChild,+ignoreFileBaseDir,IResourceDelta.ADDED,+IResourceDelta.CONTENT);+}+}elseif(projectChildinstanceofIFolder){+try{+importProjectIgnores(((IFolder)projectChild).members());+}catch(finalCoreExceptione){+/* swallow */+}+}else{+/* FIXME: signal an error */+System.out.println("Unhandled resource type in"++" processResourceForIgnoreChild: "++projectChild.getClass().getName());+}+}+}+}++/**+*Thismethodparsesa.gitignorefileandstoresthepatternsforuseby+*theplugin.Itissharedbetweenstartupoftheworkspaceandchangesto+*theworkspace.+*+*@paramignoreFile+*the.gitignorefile+*@paramignoreFileBaseDir+*thedirectoryoftheignorefile,relativetothecheckout+*directoryandwithatrailingslash(exceptwheninthe+*checkoutdirectory,inwhichcaseitwillbeanemptystring).+*SlashesareinUnixformat:forwardslashes+*@paramchangeKind+*thekindofthechange+*@paramchangeFlags+*furtherinformationonthechange+*/+synchronizedvoidprocessIgnoreFile(finalIFileignoreFile,+finalStringignoreFileBaseDir,finalintchangeKind,+finalintchangeFlags){+if((ignoreFile==null)||(changeKind==IResourceDelta.NO_CHANGE)){+return;+}++if(((changeKind&IResourceDelta.ADDED)==IResourceDelta.ADDED)+||((changeKind&IResourceDelta.ADDED_PHANTOM)==IResourceDelta.ADDED_PHANTOM)+||(((changeKind&IResourceDelta.CHANGED)==IResourceDelta.CHANGED)&&((changeFlags&IResourceDelta.CONTENT)==IResourceDelta.CONTENT))){+ignoreFiles.put(ignoreFile,IgnoreFile.parseIgnoreFile(+ignoreFileBaseDir,ignoreFile));+ignoreFilesIndex.put(ignoreFileBaseDir,ignoreFile);+}elseif(((changeKind&IResourceDelta.REMOVED)==IResourceDelta.REMOVED)+||((changeKind&IResourceDelta.REMOVED_PHANTOM)==IResourceDelta.REMOVED_PHANTOM)){+ignoreFiles.remove(ignoreFile);+ignoreFilesIndex.remove(ignoreFileBaseDir);+}else{+System.out.println("Unhandled change combination kind/flags: "++changeKind+"/"+changeFlags);+}++// int changeKind = projectChild.getKind();+// int changeFlags = projectChild.getFlags();+// switch (changeKind) {+// case IResourceDelta.ADDED:+// case IResourceDelta.ADDED_PHANTOM:+// if ((changeFlags & IResourceDelta.MOVED_FROM) ==+// IResourceDelta.MOVED_FROM) {+// /*+// * The resource has moved: getMovedToPath will+// * return the path of where it was moved to.+// */+// break;+// }+//+// /* simply parse the content and process it */+// break;+//+// case IResourceDelta.REMOVED:+// case IResourceDelta.REMOVED_PHANTOM:+// /* remove the patterns from the file */+// break;+//+// case IResourceDelta.CHANGED:+// /* this one is more involved, also have to deal with moved+// ignoreFiles */+// if ((changeFlags & IResourceDelta.REPLACED) ==+// IResourceDelta.REPLACED) {+// /*+// * The resource has moved: getMovedToPath will+// * return the path of where it was moved to.+// */+// }+// break;+//+// default:+// break;+// }+}+}
@@ -0,0 +1,308 @@+/*******************************************************************************+*Copyright(C)2009,FerryHuberts<ferry.huberts@pelagic.nl>+*+*Allrightsreserved.Thisprogramandtheaccompanyingmaterials+*aremadeavailableunderthetermsoftheEclipsePublicLicensev1.0+*SeeLICENSEforthefulllicensetext,alsoavailable.+*******************************************************************************/+packageorg.spearce.egit.core.ignores;++importjava.io.File;+importjava.util.HashMap;++importorg.eclipse.core.resources.IProject;+importorg.eclipse.core.resources.IResource;+importorg.eclipse.core.resources.IResourceDelta;+importorg.eclipse.core.runtime.CoreException;+importorg.eclipse.core.runtime.IPath;+importorg.eclipse.core.runtime.Path;+importorg.spearce.egit.core.project.RepositoryMapping;+importorg.spearce.jgit.lib.Repository;+importorg.spearce.jgit.lib.RepositoryConfig;++/**+*Thisclassmanagestheignoredataforasinglerepository(andits+*checkout).Itimplementsthemodelthatasinglerepositorycancontain+*multipleprojects.+*+*Itcontainsacachethatholdsignoredataonaper-projectbasisanditalso+*containsacachethatholdsignoredatathatdoesnotbelongtoanyproject+*butstillbelongstotherepository.ThelattercacheweneedbecauseEclipse+*willnotsendchangeeventsforthosefiles;wehavetore-readthesefiles+*everytimethereisachange.+*/+classIgnoreRepositoryCache{+/** the repository */+privateRepositoryrepository=null;++/** the checkout directory for the repository */+privateStringcheckoutDir=null;++/** the cache that holds ignore data on a per-project basis */+privatefinalHashMap<IProject,IgnoreProjectCache>projects=newHashMap<IProject,IgnoreProjectCache>();++/** cache that holds ignore data that does not belong to any project */+privatefinalIgnoreProjectCacheoutside=newIgnoreProjectCache("");++/** cache that holds ignore data of the .git/info/exclude files */+privatefinalIgnoreProjectCacheinfoExclude=newIgnoreProjectCache("");++/** cache that holds ignore data of the core exclude setting */+privatefinalIgnoreProjectCachecoreExcludes=newIgnoreProjectCache("");++/** the .git/info/exclude file */+privateIgnoreFileOutsideinfoExcludesFile=null;++/** the core.excludes file setting from the config */+privateIgnoreFileOutsidecoreExcludesFile=null;++/** the core.excludesfile setting */+privateStringcoreExcludesSetting=null;++/**+*Retrieveaprojectmappingfromtheprojectscache.Whentheprojectis+*notyetinthecachethencreateanewmappingforitandstoreitinthe+*cachefirst.+*+*@paramproject+*theprojecttoretrievefromtheprojectscache+*@returntheprojectmappinginthecache+*/+privateIgnoreProjectCachegetProjectFromCache(finalIProjectproject){+IgnoreProjectCachecache=projects.get(project);+if(cache==null){+cache=newIgnoreProjectCache(RepositoryMapping+.getMapping(project).getRepoRelativePath(project));+projects.put(project,cache);+}+returncache;+}++/*+*Constructors+*/++/**+*Constructor+*+*@paramrepository+*therepository+*/+IgnoreRepositoryCache(finalRepositoryrepository){+if(repository==null){+thrownewExceptionInInitializerError(+"NULL is not a valid repository");+}+this.repository=repository;+this.checkoutDir=repository.getWorkDir().getAbsolutePath();+}++/*+*Methods+*/++synchronizedvoidclear(){+for(finalIgnoreProjectCacheprojectCache:projects.values()){+projectCache.clear();+}+projects.clear();+outside.clear();+infoExclude.clear();+coreExcludes.clear();+}++/**+*@paramproject+*theprojectforwhichtoremovetheignoredata+*/+synchronizedvoiduncacheProject(finalIProjectproject){+if(project==null){+return;+}++finalIgnoreProjectCachecache=projects.get(project);+if(cache==null){+return;+}++/*+*FIXME:removeeverythingthatisnot'outside'toremainingprojects+*/++cache.clear();++projects.remove(project);+}++/**+*Processallprojectchildren:lookfor.gitignorefilesandreadinthe+*ignorepatterns.+*+*@paramproject+*theproject+*@paramprojectChildren+*theprojectchildren+*/+synchronizedvoidimportProjectIgnores(finalIProjectproject,+finalIResource[]projectChildren){+if((project==null)||(projectChildren==null)){+return;+}++finalIgnoreProjectCacheprojectCache=getProjectFromCache(project);+projectCache.importProjectIgnores(projectChildren);+}++/*+*walkdirectorytreeuplookingfor.gitignorefilesuntilinthecheckout+*directory+*/+synchronizedbooleanimportProjectIgnoresOutside(finalIProjectproject){+booleanchanges=false;++StringprojectDirectory=RepositoryMapping.getMapping(project)+.getRepoRelativePath(project);+while(!projectDirectory.isEmpty()){+finalintpos=projectDirectory.lastIndexOf('/');+if(pos<0){+projectDirectory="";+}else{+projectDirectory=projectDirectory.substring(0,pos)+"/";+}++finalIgnoreFileOutsideignoreFile=newIgnoreFileOutside(+repository,projectDirectory,".gitignore");+if(!ignoreFile.isSynchronized(0)){+changes=true;+StringprojectRelativeDir=ignoreFile.getProjectRelativePath()+.removeLastSegments(1).toString();+if(!projectRelativeDir.isEmpty()){+projectRelativeDir=projectRelativeDir+"/";+}+finalStringignoreFileBaseDir=projectDirectory++projectRelativeDir;+outside.processIgnoreFile(ignoreFile,ignoreFileBaseDir,+IResourceDelta.ADDED,IResourceDelta.CONTENT);+}+}++returnchanges;+}++synchronizedbooleanimportRepositoryInfoExclude(){+readRepositoryInfoExcludesFile();++if((infoExcludesFile!=null)&&!infoExcludesFile.isSynchronized(0)){+try{+infoExcludesFile.refreshLocal(IResource.DEPTH_ZERO,null);+}catch(finalCoreExceptione){+/* swallow */+}+infoExclude.clear();+infoExclude.processIgnoreFile(infoExcludesFile,"",+IResourceDelta.ADDED,IResourceDelta.CONTENT);+returntrue;+}+returnfalse;+}++synchronizedbooleanimportRepositoryCoreExclude(){+readRepositoryCoreExcludesSetting();+if((coreExcludesFile!=null)&&!coreExcludesFile.isSynchronized(0)){+try{+coreExcludesFile.refreshLocal(IResource.DEPTH_ZERO,null);+}catch(finalCoreExceptione){+/* swallow */+}+coreExcludes.clear();+coreExcludes.processIgnoreFile(coreExcludesFile,"",+IResourceDelta.ADDED,IResourceDelta.CONTENT);+returntrue;+}+returnfalse;+}++/*+*PrivateMethods+*/++privatebooleanreadRepositoryInfoExcludesFile(){+booleanchanges=false;++if(infoExcludesFile==null){+infoExcludesFile=newIgnoreFileOutside(this.repository,"",+".git/info/exclude");+changes=true;+if(!infoExcludesFile.exists()){+infoExcludesFile=null;+changes=false;+}+}else{+if(!infoExcludesFile.exists()){+infoExcludesFile=null;+changes=true;+}+}++returnchanges;+}++privatebooleanreadRepositoryCoreExcludesSetting(){+finalRepositoryConfigconfig=repository.getConfig();+if(config==null){+returnfalse;+}++booleanchanges=false;+StringnewCoreExcludesSetting=config.getString("core",null,+"excludesfile");+if(newCoreExcludesSetting!=null){+/* FIXME check this! both for per-repo and global */+if(!newCoreExcludesSetting.equals(coreExcludesSetting)){+changes=true;++finalIPathnewCoreExcludesSettingPath=newPath(+newCoreExcludesSetting);+newCoreExcludesSetting=newCoreExcludesSettingPath+.toOSString();+if(!newCoreExcludesSettingPath.isAbsolute()){+newCoreExcludesSetting=repository.getWorkDir()+.getAbsolutePath().toString()++File.separator+newCoreExcludesSetting;+}++if(coreExcludesFile!=null){+coreExcludesFile=null;+}++finalintpos=newCoreExcludesSetting+.lastIndexOf(File.separatorChar);+finalStringdirectory=newCoreExcludesSetting.substring(0,+pos);+finalStringresourceBasename=newCoreExcludesSetting+.substring(pos+1);++coreExcludes.clear();+coreExcludesSetting=newCoreExcludesSetting;+coreExcludesFile=newIgnoreFileOutside(null,directory,+resourceBasename);+}+}else{+changes=(coreExcludesSetting!=null);+coreExcludesSetting=null;+coreExcludesFile=null;+}+returnchanges;+}++/*+*Getters/Setters+*/++/**+*@returnthecheckoutDir+*/+publicStringgetCheckoutDir(){+returncheckoutDir;+}+}
A quick reply (I might come up with more later): Ignore support should be mostly
in jgit, with only extensions into egit.
-- robin
I discussed this with shawn and proposed to first implement it in egit
and when we have it right then move it into jgit. I think shawn agreed
with that.
Ferry
From: Jonathan Gossage <hidden> Date: 2016-06-15 22:46:31
Ferry Huberts wrote:
This is the first - early - code that adds ignore functionality to EGit.
Currently it reads in all ignore patterns upon workspace startup into an
ignore cache. From this cache the ignore state of a resource is evaluated
in the same fashion as git does.
The code does not yet react to changes in ignore files but I'm planning to add
that soon and I can share a lot of code for that.
I send this code to receive feedback and to give you insight into what I'm
doing with it. I'm new both to EGit programming and Eclipse programming so
there might be things that could be done more elegantly :-)
A few notes:
- The patches are rebased on the current master (e3440623)
- The order of the patches must be re-arranged, but that is rather easy. The
correct order - once finished - would be:
Build up the ignore patterns cache upon workspace startup.
Use the ignore patterns cache to determine ignores
Enable the ignore handling of the plugin
Optimise ignore evaluation
Do not set .git as a Team ignore pattern
- The core.excludesfile code is currently untested, the other code seems to be
in a good state.
- There are a few FIXMEs in the code with questions and tasks. It's a work in
progress and these will disappear.
Ferry Huberts (5):
Build up the ignore patterns cache upon workspace startup.
Enable the ignore handling of the plugin
Optimise ignore evaluation
Do not set .git as a Team ignore pattern
Use the ignore patterns cache to determine ignores
org.spearce.egit.core/META-INF/MANIFEST.MF | 1 +
org.spearce.egit.core/plugin.xml | 6 -
.../src/org/spearce/egit/core/ignores/DType.java | 44 ++
.../src/org/spearce/egit/core/ignores/Exclude.java | 243 +++++++++
.../spearce/egit/core/ignores/GitIgnoreData.java | 180 +++++++
.../org/spearce/egit/core/ignores/IgnoreFile.java | 82 +++
.../egit/core/ignores/IgnoreFileOutside.java | 543 ++++++++++++++++++++
.../egit/core/ignores/IgnoreProjectCache.java | 245 +++++++++
.../egit/core/ignores/IgnoreRepositoryCache.java | 358 +++++++++++++
.../org/spearce/egit/core/op/TrackOperation.java | 7 +-
.../spearce/egit/core/project/GitProjectData.java | 8 +
.../decorators/DecoratableResourceAdapter.java | 11 +-
org.spearce.jgit/META-INF/MANIFEST.MF | 1 +
13 files changed, 1712 insertions(+), 17 deletions(-)
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/DType.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/Exclude.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/GitIgnoreData.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFile.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreFileOutside.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreProjectCache.java
create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/ignores/IgnoreRepositoryCache.java
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Eclipse supplies a repository-independent ignore file list as part of the repository-type independent Team support. A first step, which would provide useful functionality would be to populate your cache with this list and enable it's use in the Egit plugin. This would accomplish the goal of enabling EGit to use ignore lists in an immediately useful way with minimal effort. As a second stage you can add support for picking up Git specific files and updating them from Eclipse.
I think you will run into problems if you try to create a workspace wide
cache. It is quite possible that one workspace could have projects that
target different Git repositories. This means that your cache would need
to look at all projects in the workspace and potentially take into
account Eclipse working sets and other such complications. You also will
need to deal with projects being added and deleted from the Eclipse
workspace.
I think a better approach might be to go for lazy cache construction
where the cache is built only when actually needed by a user operation.
The cache would then be built only for a specific Git repository. JGit
should be responsible for assembling a merged list from the various Git
files. It should also be responsible for the actual updating of the
various Git ignore files. Since I believe that the Eclipse
repository-independent ignore file list should be the lowest priority in
the merged list, it will be necessary to pass the Eclipse list to JGit
as a parameter whenever a merged list is required.
In general, you should look to do Git specific things in JGit and do
Eclipse things in Eclipse. That way JGit continues to acquire the
functionality to support any IDE and EGit is kept as simple as possible.
HTH
Jonathan Gossage
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:46:31
"Ferry Huberts (Pelagic)" [off-list ref] wrote:
Robin Rosenberg wrote:
quoted
A quick reply (I might come up with more later): Ignore support should be mostly
in jgit, with only extensions into egit.
I discussed this with shawn and proposed to first implement it in egit
and when we have it right then move it into jgit. I think shawn agreed
with that.
I may have agreed with it. My memory isn't *that* good. :-)
In general principal I agree with Robin, Git specific handling
should be in JGit as much as possible so we can reuse the logic in
more applications than just EGit.
But it may have been easier to get a first working prototype by doing
the code in EGit, and later pulling some of it down into JGit as we
identity what isn't EGit specific.
The problem with that is the dual licenses; code in EGit can't
be pulled down to JGit without relicensing it under the BSD.
Only the original author of the code can do that. So if you
contribute ignore support to EGit under the EPL which is better
placed in JGit, Robin or myself can't pull it down ourselves,
we'd have to rewrite it.
But even rewriting may be difficult, as the rewrite may be too close
to the original (same language, same surrounding code, likely going
to produce a similar result).
--
Shawn.
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:46:31
måndag 30 mars 2009 02:40:59 skrev Jonathan Gossage [off-list ref]:
Ferry Huberts wrote:
Eclipse supplies a repository-independent ignore file list as part of the repository-type independent Team support. A first step, which would provide useful functionality would be to populate your cache with this list and enable it's use in the Egit plugin. This would accomplish the goal of enabling EGit to use ignore lists in an immediately useful way with minimal effort. As a second stage you can add support for picking up Git specific files and updating them from Eclipse.
The current EGit obeys the Eclipse ignore rules already, so no there is no need to hurry just for that feature.
-- robin
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:46:33
Ferry Huberts [off-list ref] wrote:
This is the first - early - code that adds ignore functionality to EGit.
Currently it reads in all ignore patterns upon workspace startup into an
ignore cache. From this cache the ignore state of a resource is evaluated
in the same fashion as git does.
The code does not yet react to changes in ignore files but I'm planning to add
that soon and I can share a lot of code for that.
I send this code to receive feedback and to give you insight into what I'm
doing with it. I'm new both to EGit programming and Eclipse programming so
there might be things that could be done more elegantly :-)
Ok, I finally got a chance to review this series.
We really want as much of the Git specific logic as we can in JGit
under the BSD license. This has already been raised elsewhere in
this thread.
JGit and EGit are holding the line on Java 5 support; that means
that String.isEmpty() must be spelled as String.length() == 0
(isEmpty was added in Java 6).
Style nit: Don't put /* Constructors */, /* Methods */ or
/ * Public Methods */ comments in code, e.g.
IgnoreProjectCache l.52-54 or GitIgnoreData l.58-61.
Style nit: Don't assign fields to their default values.
E.g. Exclude.java l.25,33,42,.. these are being set to the
same value that the JRE sets the field to if the field is not
explicitly initialized. We find it much easier to read code when
the defaults are assumed.
Style nit: Don't use "this." to refer to members.
Your IDE should highlight field references differently than
parameters, and a parameter should never shadow a field name,
thus "this." is unnecessary and makes the code much more verbose
to read. E.g. see Exclude.java 's constructor on l.87-108; I can't
see the forest (the code) due to all the trees (this.) appearing.
IgnoreFileOutside: Ugh, our own implementation of IFile ?
I'm worried about the long-term stability of the IFile API.
Is it really frozen enough that we can implement it ourselves?
Of course, this may be moot if much of the code was moved back
to JGit.
IgnoreRepositoryCache: Why not put this into RepositoryMapping?
Instead of caching it inside a static HashMap of GitIgnoreData,
wouldn't it be better to put it into RepositoryMapping?
The TrackOperation for example already has the RepositoryMapping
handle in scope, saving a few lookup operations, and avoiding
needing to manage this new additional static HashMap against leaks.
I kind of wanted to tie exclude processing (and attribute processing)
into a TreeWalk, so that we can do an n-way merge against trees and
working directories by tossing all of their AbstractTreeIterators
into a single walk, possibly apply a path filter, and let the walk
handle the per-directory ignore rules as it goes.
Most of your code seems to be built around the Eclipse IResource
model, and the idea that it gets called for a single file path
at a time, which may make it less efficient when we put it into a
TreeWalk and apply the notion of entering and exiting a subdirectory.
OK, that's about all I have for now. Its reasonable, but still an
early series.
--
Shawn.
This is the first - early - code that adds ignore functionality to EGit.
Currently it reads in all ignore patterns upon workspace startup into an
ignore cache. From this cache the ignore state of a resource is evaluated
in the same fashion as git does.
The code does not yet react to changes in ignore files but I'm planning to add
that soon and I can share a lot of code for that.
I send this code to receive feedback and to give you insight into what I'm
doing with it. I'm new both to EGit programming and Eclipse programming so
there might be things that could be done more elegantly :-)
Ok, I finally got a chance to review this series.
We really want as much of the Git specific logic as we can in JGit
under the BSD license. This has already been raised elsewhere in
this thread.
ack.
JGit and EGit are holding the line on Java 5 support; that means
that String.isEmpty() must be spelled as String.length() == 0
(isEmpty was added in Java 6).
ok
Style nit: Don't put /* Constructors */, /* Methods */ or
/ * Public Methods */ comments in code, e.g.
IgnoreProjectCache l.52-54 or GitIgnoreData l.58-61.
ok
Style nit: Don't assign fields to their default values.
E.g. Exclude.java l.25,33,42,.. these are being set to the
same value that the JRE sets the field to if the field is not
explicitly initialized. We find it much easier to read code when
the defaults are assumed.
ok
Style nit: Don't use "this." to refer to members.
Your IDE should highlight field references differently than
parameters, and a parameter should never shadow a field name,
thus "this." is unnecessary and makes the code much more verbose
to read. E.g. see Exclude.java 's constructor on l.87-108; I can't
see the forest (the code) due to all the trees (this.) appearing.
ok
IgnoreFileOutside: Ugh, our own implementation of IFile ?
I'm worried about the long-term stability of the IFile API.
Is it really frozen enough that we can implement it ourselves?
Of course, this may be moot if much of the code was moved back
to JGit.
when we convert the code to be in jgit we will not use this api.
this will disappear.
IgnoreRepositoryCache: Why not put this into RepositoryMapping?
Instead of caching it inside a static HashMap of GitIgnoreData,
wouldn't it be better to put it into RepositoryMapping?
The TrackOperation for example already has the RepositoryMapping
handle in scope, saving a few lookup operations, and avoiding
needing to manage this new additional static HashMap against leaks.
ok
I kind of wanted to tie exclude processing (and attribute processing)
into a TreeWalk, so that we can do an n-way merge against trees and
working directories by tossing all of their AbstractTreeIterators
into a single walk, possibly apply a path filter, and let the walk
handle the per-directory ignore rules as it goes.
ok. but I need much more input than that from you on how to go about
moving the code into jgit. If you have ideas on what the rough
architecture should be of the ignore processing in jgit then please let
me know, I don't know that code at all and also don't know the approach
you'd like to take there with its architecture.
I've been thinking on and off about how to free the ignore processing
from eclipse specific apis ever since the egit/jgit issue came up. I
think it's not that hard to do, depending on the approach you'd like to
take.
you talk about TreeWalk. but what is it's prupose, in what context is it
called? etc. You can probably much faster explain that to me than I can
find out from the code (the egit code is much easier to place in context
than the jgit code I think)
Most of your code seems to be built around the Eclipse IResource
model, and the idea that it gets called for a single file path
at a time, which may make it less efficient when we put it into a
TreeWalk and apply the notion of entering and exiting a subdirectory.
the way it works now is more efficient than git itself (most of the
time), since we have a cache of the ignore patterns. I compared traces
of git itself and the plugin against eachother.
OK, that's about all I have for now. Its reasonable, but still an
early series.
yep.
please let's discuss moving it into jgit asap. so that I can complete
the feature.
BTW
In the meantime I've also added ignore preferences to the plugin on
which the user can specify whether the Eclipse Team Ignored Resources
should be taken into account. It defaults to yes. When set to no the
ignore processing complies strict to git behaviour.
looking forward to your input!
Ferry
JGit and EGit are holding the line on Java 5 support; that means
that String.isEmpty() must be spelled as String.length() == 0
(isEmpty was added in Java 6).
just looked in the project settings for org.spearce.egit.core and it has
java 1.5 style specified _and_ eclipse does not give me a warning on the
*.isEmpty() calls. Am i missing something here?
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:46:34
"Ferry Huberts (Pelagic)" [off-list ref] wrote:
Shawn O. Pearce wrote:
quoted
JGit and EGit are holding the line on Java 5 support; that means
that String.isEmpty() must be spelled as String.length() == 0
(isEmpty was added in Java 6).
just looked in the project settings for org.spearce.egit.core and it has
java 1.5 style specified _and_ eclipse does not give me a warning on the
*.isEmpty() calls. Am i missing something here?
Your workspace default JRE must be set to a Java 6. Switch it to
Java 5 in the workspace settings.
--
Shawn.
Heya,
On Mon, Apr 6, 2009 at 18:51, Ferry Huberts (Pelagic)
[off-list ref] wrote:
just looked in the project settings for org.spearce.egit.core and it has
java 1.5 style specified _and_ eclipse does not give me a warning on the
*.isEmpty() calls. Am i missing something here?