ignored.edit.radio.file=Ignore specified &file
ignored.edit.radio.directory=Ignore all files &under
ignored.edit.radio.mask=Ignore all files &matching
+ignored.file.manage.view=View
+ignored.file.manage.with.files.message=Ignored files detected. Manage VCS ignore files automatically.
ignored.file.manage.message=Manage VCS ignore files automatically
-ignored.file.manage.this.project=For this project
-ignored.file.manage.all.project=For all projects
-ignored.file.manage.notnow=Not now
+ignored.file.manage.this.project=This project
+ignored.file.manage.all.project=All projects
+ignored.file.manage.notmanage=Not manage
browse.changes.content.title=Changes under {0}
browse.changes.no.filter.prompt=You have not specified any filtering criteria. Are you sure you would like to view the entire history of the project?
browse.changes.title=Browse Changes
@NotNull
String buildUnignoreContent(@NotNull String ignorePattern);
+
+ @NotNull
+ String buildIgnoreGroupDescription(@NotNull IgnoredFileProvider ignoredFileProvider);
}
}
protected boolean parse_root_(IElementType root_, PsiBuilder builder_, int level_) {
- return gitignoreFile(builder_, level_ + 1);
+ return ignoreFile(builder_, level_ + 1);
}
public static final TokenSet[] EXTENDS_SETS_ = new TokenSet[] {
/* ********************************************************** */
// item_ *
- static boolean gitignoreFile(PsiBuilder builder_, int level_) {
- if (!recursion_guard_(builder_, level_, "gitignoreFile")) return false;
+ static boolean ignoreFile(PsiBuilder builder_, int level_) {
+ if (!recursion_guard_(builder_, level_, "ignoreFile")) return false;
int pos_ = current_position_(builder_);
while (true) {
if (!item_(builder_, level_ + 1)) break;
- if (!empty_element_parsed_guard_(builder_, "gitignoreFile", pos_)) break;
+ if (!empty_element_parsed_guard_(builder_, "ignoreFile", pos_)) break;
pos_ = current_position_(builder_);
}
return true;
private fun notAskedBefore() = !projectProperties.getBoolean(askedBeforeProperty, false)
- private fun needDoForCurrentProject() = projectProperties.getBoolean(doForCurrentProjectProperty, false)
+ protected open fun needDoForCurrentProject() = projectProperties.getBoolean(doForCurrentProjectProperty, false)
}
\ No newline at end of file
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.actions.VcsContextFactory;
import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vcs.changes.ignore.IgnoreFilesProcessorImpl;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.util.SmartList;
myProjectConfigurationFilesProcessor = createProjectConfigurationFilesProcessor();
myExternalFilesProcessor = createExternalFilesProcessor();
+ createIgnoredFilesProcessor();
}
@Override
protected abstract boolean isDirectoryVersioningSupported();
+ private void createIgnoredFilesProcessor() {
+ new IgnoreFilesProcessorImpl(myProject, this);
+ }
+
@SuppressWarnings("unchecked")
private FilesProcessor createExternalFilesProcessor() {
return new ExternallyAddedFilesProcessorImpl(myProject,
--- /dev/null
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.vcs.changes.ignore
+
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.runInEdt
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.diagnostic.logger
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.registry.Registry
+import com.intellij.openapi.vcs.FilesProcessorWithNotificationImpl
+import com.intellij.openapi.vcs.VcsApplicationSettings
+import com.intellij.openapi.vcs.VcsBundle
+import com.intellij.openapi.vcs.changes.ChangeListManagerImpl
+import com.intellij.openapi.vcs.changes.IgnoredFileContentProvider
+import com.intellij.openapi.vcs.changes.IgnoredFileDescriptor
+import com.intellij.openapi.vcs.changes.IgnoredFileProvider
+import com.intellij.openapi.vcs.changes.ignore.psi.util.addNewElementsToIgnoreBlock
+import com.intellij.openapi.vfs.LocalFileSystem
+import com.intellij.openapi.vfs.VfsUtilCore
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.newvfs.events.*
+import com.intellij.project.getProjectStoreDirectory
+import com.intellij.vcsUtil.VcsImplUtil
+import com.intellij.vcsUtil.VcsImplUtil.MANAGE_IGNORE_FILES_PROPERTY
+import com.intellij.vcsUtil.VcsUtil
+import com.intellij.vfs.AsyncVfsEventsListener
+import com.intellij.vfs.AsyncVfsEventsPostProcessor
+
+const val ASKED_MANAGE_IGNORE_FILES_PROPERTY = "ASKED_MANAGE_IGNORE_FILES"
+
+private val LOG = logger<IgnoreFilesProcessorImpl>()
+
+class IgnoreFilesProcessorImpl(project: Project, parentDisposable: Disposable)
+ : FilesProcessorWithNotificationImpl(project, parentDisposable), AsyncVfsEventsListener, Disposable {
+
+ private val changeListManager = ChangeListManagerImpl.getInstanceImpl(project)
+
+ init {
+ runReadAction {
+ if (!project.isDisposed) {
+ AsyncVfsEventsPostProcessor.getInstance().addListener(this, parentDisposable)
+ }
+ }
+ }
+
+ override fun filesChanged(events: List<VFileEvent>) {
+ if (!needProcessIgnoredFiles() || ApplicationManager.getApplication().isUnitTestMode) return
+
+ val potentiallyIgnoredFiles =
+ events.asSequence()
+ .mapNotNull(::getAffectedFile)
+ .filter { changeListManager.isPotentiallyIgnoredFile(it) }
+ .toList()
+
+ if (potentiallyIgnoredFiles.isEmpty()) return
+ LOG.debug("Got potentially ignored files from VFS events", potentiallyIgnoredFiles)
+
+ processFiles(potentiallyIgnoredFiles)
+ }
+
+ override fun doActionOnChosenFiles(files: Collection<VirtualFile>) {
+ runInEdt {
+ writeIgnores(project, files)
+ }
+ }
+
+ override fun dispose() {}
+
+ private fun writeIgnores(project: Project, potentiallyIgnoredFiles: Collection<VirtualFile>) {
+ if (potentiallyIgnoredFiles.isEmpty()) return
+
+ LOG.debug("Try to write potential ignored files", potentiallyIgnoredFiles)
+ val ignoreFileToContent = hashMapOf<VirtualFile, MutableList<IgnoreGroupContent>>()
+ val providerToDescriptorMap = IgnoredFileProvider.IGNORE_FILE.extensions.associate { it to it.getIgnoredFiles(project) }
+
+ for (potentiallyIgnoredFile in potentiallyIgnoredFiles) {
+ VcsUtil.getVcsFor(project, potentiallyIgnoredFile)?.let { vcs ->
+ VcsImplUtil.getIgnoredFileContentProvider(project, vcs)?.let { ignoredContentProvider ->
+ findOrCreateIgnoreFileByFile(project, ignoredContentProvider, potentiallyIgnoredFile)?.let { ignoreFile ->
+ for ((ignoredFileProvider, descriptors) in providerToDescriptorMap) {
+ for (ignoredFileDescriptor in descriptors.filter { it.matchesFile(potentiallyIgnoredFile) }) {
+ val ignoreFileContent = ignoreFileToContent.computeIfAbsent(ignoreFile) { mutableListOf() }
+ val groupDescription = " ${ignoredFileProvider.ignoredGroupDescription}"
+ val ignoreFileGroupContent = ignoreFileContent.getOrInitialize(groupDescription)
+ ignoreFileGroupContent.ignoredDescriptors.add(ignoredFileDescriptor)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for ((ignoreFile, newContent) in ignoreFileToContent) {
+ for (groupContent in newContent) {
+ val ignoredDescriptors = groupContent.ignoredDescriptors
+ LOG.debug("Write to ignore file ${ignoreFile} ignores: $ignoredDescriptors")
+ addNewElementsToIgnoreBlock(project, ignoreFile, groupContent.group, *ignoredDescriptors.toTypedArray())
+ }
+ }
+ }
+
+ private fun MutableList<IgnoreGroupContent>.getOrInitialize(group: String): IgnoreGroupContent =
+ find { it.group == group } ?: IgnoreGroupContent(group).apply { this@getOrInitialize.add(this) }
+
+ private data class IgnoreGroupContent(val group: String, val ignoredDescriptors: MutableSet<IgnoredFileDescriptor> = mutableSetOf())
+
+ private fun findOrCreateIgnoreFileByFile(project: Project,
+ ignoredContentProvider: IgnoredFileContentProvider,
+ file: VirtualFile): VirtualFile? {
+ val storeDir = findStoreDir(project)
+
+ val ignoreFileRoot =
+ if (storeDir != null && file.underProjectStoreDir(storeDir)) storeDir else VcsUtil.getVcsRootFor(project, file) ?: return null
+
+ return ignoreFileRoot.findChild(ignoredContentProvider.fileName) ?: runWriteAction {
+ ignoreFileRoot.createChildData(this, ignoredContentProvider.fileName)
+ }
+ }
+
+ private fun findStoreDir(project: Project): VirtualFile? {
+ val projectBasePath = project.basePath ?: return null
+ val projectBaseDir = LocalFileSystem.getInstance().findFileByPath(projectBasePath) ?: return null
+
+ return getProjectStoreDirectory(projectBaseDir) ?: return null
+ }
+
+ private fun VirtualFile.underProjectStoreDir(storeDir: VirtualFile): Boolean {
+ return VfsUtilCore.isAncestor(storeDir, this, true)
+ }
+
+ override fun doFilterFiles(files: Collection<VirtualFile>) = files.filter { shouldIgnore(it) }
+
+ override fun rememberForAllProjects() {
+ val applicationSettings = VcsApplicationSettings.getInstance()
+ applicationSettings.MANAGE_IGNORE_FILES = true
+ }
+
+ override val askedBeforeProperty = ASKED_MANAGE_IGNORE_FILES_PROPERTY
+
+ override val doForCurrentProjectProperty = MANAGE_IGNORE_FILES_PROPERTY
+ override val showActionText: String = VcsBundle.getString("ignored.file.manage.view")
+
+ override val forCurrentProjectActionText: String = VcsBundle.getString("ignored.file.manage.this.project")
+ override val forAllProjectsActionText: String? = VcsBundle.getString("ignored.file.manage.all.project")
+ override val muteActionText: String = VcsBundle.getString("ignored.file.manage.notmanage")
+
+ override fun notificationTitle() = ""
+ override fun notificationMessage(): String = VcsBundle.message("ignored.file.manage.with.files.message")
+
+ private fun shouldIgnore(file: VirtualFile) = changeListManager.isPotentiallyIgnoredFile(file)
+
+ override fun needDoForCurrentProject() = VcsApplicationSettings.getInstance().MANAGE_IGNORE_FILES || super.needDoForCurrentProject()
+
+ private fun getAffectedFile(event: VFileEvent): VirtualFile? =
+ runReadAction {
+ when {
+ event is VFileCreateEvent && event.parent.isValid -> event.file
+ event is VFileMoveEvent || event.isRename() -> event.file
+ event is VFileCopyEvent && event.newParent.isValid -> event.newParent.findChild(event.newChildName)
+ else -> null
+ }
+ }
+
+ private fun VFileEvent.isRename() = this is VFilePropertyChangeEvent && isRename
+
+ private fun needProcessIgnoredFiles() = Registry.`is`("vcs.ignorefile.generation", true)
+}
\ No newline at end of file
--- /dev/null
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.vcs.changes.ignore.psi.util
+
+import com.intellij.lang.ASTFactory
+import com.intellij.openapi.application.invokeAndWaitIfNeeded
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.application.runUndoTransparentWriteAction
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.vcs.changes.IgnoreSettingsType
+import com.intellij.openapi.vcs.changes.IgnoredFileDescriptor
+import com.intellij.openapi.vcs.changes.ignore.lang.IgnoreFileConstants.NEWLINE
+import com.intellij.openapi.vcs.changes.ignore.lang.IgnoreLanguage
+import com.intellij.openapi.vcs.changes.ignore.psi.IgnoreTypes
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.*
+import com.intellij.psi.impl.GeneratedMarkerVisitor
+import com.intellij.psi.impl.PsiFileFactoryImpl
+import com.intellij.psi.impl.source.DummyHolderFactory
+import com.intellij.psi.tree.IElementType
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.annotations.TestOnly
+
+@TestOnly
+fun updateIgnoreBlock(project: Project,
+ ignoreFile: VirtualFile,
+ ignoredGroupDescription: String,
+ vararg newEntries: IgnoredFileDescriptor): PsiFile? {
+ val ignoreFilePsi = ignoreFile.findIgnorePsi(project) ?: return null
+ val psiFactory = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl
+ val psiParserFacade = PsiParserFacade.SERVICE.getInstance(project)
+ invokeAndWaitIfNeeded {
+ runUndoTransparentWriteAction {
+ updateIgnoreBlock(psiParserFacade, ignoreFilePsi, ignoredGroupDescription,
+ newEntries.map { it.toPsiElement(psiFactory, ignoreFilePsi) })
+ }
+ }
+ return ignoreFilePsi
+}
+
+fun addNewElementsToIgnoreBlock(project: Project,
+ ignoreFile: VirtualFile,
+ ignoredGroupDescription: String,
+ vararg newEntries: IgnoredFileDescriptor): PsiFile? {
+ val ignoreFilePsi = ignoreFile.findIgnorePsi(project) ?: return null
+ val psiFactory = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl
+ val psiParserFacade = PsiParserFacade.SERVICE.getInstance(project)
+ invokeAndWaitIfNeeded {
+ runUndoTransparentWriteAction {
+ addNewElementsToIgnoreBlock(ignoredGroupDescription, psiParserFacade, ignoreFilePsi,
+ newEntries.map {
+ it.toPsiElement(psiFactory, ignoreFilePsi)
+ })
+ }
+ }
+ return ignoreFilePsi
+}
+
+private fun updateIgnoreBlock(psiParserFacade: PsiParserFacade,
+ ignoreFilePsi: PsiFile,
+ ignoredGroupDescription: String,
+ newEntries: List<PsiElement>) {
+ val ignoredGroupPsiElement = ignoreFilePsi.findOrCreateIgnoreBlockDescriptionPsi(ignoredGroupDescription, psiParserFacade)
+
+ var replacementCandidate = ignoredGroupPsiElement.nextIgnoreGroupElement()
+ var lastElementInBlock = if (ignoredGroupPsiElement.nextSibling.isNewLine()) ignoredGroupPsiElement.nextSibling else ignoredGroupPsiElement
+
+ for (newEntry in newEntries) {
+ if (replacementCandidate != null) {
+ lastElementInBlock = replacementCandidate.replace(newEntry)
+ }
+ else {
+ val newLinePsi = lastElementInBlock.createNewline()
+ lastElementInBlock = ignoreFilePsi.addAfter(newLinePsi, lastElementInBlock)
+ lastElementInBlock = ignoreFilePsi.addAfter(newEntry, lastElementInBlock)
+ }
+ replacementCandidate = replacementCandidate.nextIgnoreGroupElement()
+ }
+}
+
+private fun addNewElementsToIgnoreBlock(ignoredGroupDescription: String,
+ psiParserFacade: PsiParserFacade,
+ ignoreFilePsi: PsiFile,
+ newEntries: List<PsiElement>) {
+ val ignoredGroupPsiElement = ignoreFilePsi.findOrCreateIgnoreBlockDescriptionPsi(ignoredGroupDescription, psiParserFacade)
+
+ var nextIgnoreGroupElement = ignoredGroupPsiElement.nextIgnoreGroupElement()
+ var lastElementInBlock = if (ignoredGroupPsiElement.nextSibling.isNewLine()) ignoredGroupPsiElement.nextSibling else ignoredGroupPsiElement
+ val existingElements = mutableListOf<PsiElement>()
+
+ while (nextIgnoreGroupElement != null) {
+ val existingElement = newEntries.find { nextIgnoreGroupElement?.textMatches(it) == true }
+ if (existingElement != null) {
+ existingElements.add(existingElement)
+ }
+ lastElementInBlock = nextIgnoreGroupElement
+ nextIgnoreGroupElement = nextIgnoreGroupElement.nextIgnoreGroupElement()
+ }
+
+ for (elementToAdd in newEntries - existingElements) {
+ val newLinePsi = lastElementInBlock.createNewline()
+ lastElementInBlock = ignoreFilePsi.addAfter(newLinePsi, lastElementInBlock)
+ lastElementInBlock = ignoreFilePsi.addAfter(elementToAdd, lastElementInBlock)
+ }
+}
+
+private fun IgnoredFileDescriptor.toPsiElement(psiFactory: PsiFileFactoryImpl, ignorePsi: PsiFile): PsiElement {
+ val ignorePath = path
+ val ignoreMask = mask
+
+ val text =
+ if (ignorePath != null) {
+ val ignoreFileContainingDirPath = ignorePsi.virtualFile?.parent?.path ?: throw IllegalStateException(
+ "Cannot determine ignore file path for $ignorePsi")
+ "/${FileUtil.getRelativePath(ignoreFileContainingDirPath, ignorePath, '/')}"
+ }
+ else ignoreMask ?: throw IllegalStateException("IgnoredFileBean: path and mask cannot be null at the same time")
+
+ return ignorePsi.createElementFromText(text,
+ when (type) {
+ IgnoreSettingsType.UNDER_DIR -> IgnoreTypes.ENTRY_DIRECTORY
+ IgnoreSettingsType.FILE -> IgnoreTypes.ENTRY_FILE
+ IgnoreSettingsType.MASK -> IgnoreTypes.ENTRY
+ }, psiFactory)
+}
+
+private fun VirtualFile.findIgnorePsi(project: Project): PsiFile? =
+ runReadAction { PsiManager.getInstance(project).findFile(this).takeIf { it?.language is IgnoreLanguage } }
+
+private fun PsiFile.findOrCreateIgnoreBlockDescriptionPsi(ignoredGroupDescription: String, psiParserFacade: PsiParserFacade): PsiElement {
+ return PsiTreeUtil.findChildrenOfType(this, PsiComment::class.java)
+ .firstOrNull { it.text.contains(ignoredGroupDescription) }
+ ?: createIgnoreBlock(ignoredGroupDescription, psiParserFacade)
+}
+
+private fun PsiFile.createIgnoreBlock(ignoredGroupDescription: String, psiParserFacade: PsiParserFacade): PsiElement {
+ if (!prevSibling?.text.isNullOrBlank() && !prevSibling.isNewLine()) {
+ add(createNewline())
+ }
+ return add(psiParserFacade.createLineOrBlockCommentFromText(language, ignoredGroupDescription))
+}
+
+private fun PsiElement.createNewline(): PsiElement {
+ val holderElement = DummyHolderFactory.createHolder(PsiManager.getInstance(project), this).treeElement
+ val newElement = ASTFactory.leaf(IgnoreTypes.CRLF, holderElement.charTable.intern(NEWLINE))
+ holderElement.rawAddChildren(newElement)
+ GeneratedMarkerVisitor.markGenerated(newElement.psi)
+ return newElement.psi
+}
+
+private fun PsiElement.createElementFromText(text: String, type: IElementType, psiFactory: PsiFileFactoryImpl) =
+ psiFactory.createElementFromText(text, language, type, this)
+ ?: throw IllegalStateException("Cannot create PSI element for $text, $type, $this")
+
+private fun PsiElement?.nextIgnoreGroupElement(): PsiElement? {
+ var next = this?.nextSibling
+
+ while (next != null && next.isNewLine()) {
+ if (next is PsiComment) return null
+ if (next.nextSibling is PsiComment) return null
+
+ next = next.nextSibling
+ }
+ return next
+}
+
+private fun PsiElement?.isNewLine() = this?.text?.contains(NEWLINE) ?: false
import static com.intellij.openapi.vcs.FileStatus.IGNORED;
import static com.intellij.openapi.vcs.FileStatus.UNKNOWN;
+import static com.intellij.openapi.vcs.changes.ignore.IgnoreFilesProcessorImplKt.ASKED_MANAGE_IGNORE_FILES_PROPERTY;
import static com.intellij.vcsUtil.VcsUtil.isFileUnderVcs;
/**
if (canManageIgnoreFiles(project)) {
updateIgnoreFileIfNeeded(project, vcs, ignoreFileRoot, ignoreFile.exists());
}
- else {
+ else if (notAskedToManageIgnoreFiles(project)) {
notifyVcsIgnoreFileManage(project, () -> updateIgnoreFileAndOpen(project, vcs, ignoreFileRoot, ignoreFile));
}
}
VcsApplicationSettings applicationSettings = VcsApplicationSettings.getInstance();
VcsNotifier.getInstance(project).notifyMinorInfo(
+ true,
"",
VcsBundle.message("ignored.file.manage.message"),
NotificationAction.create(VcsBundle.message("ignored.file.manage.this.project"), (event, notification) -> {
manageIgnore.run();
propertiesComponent.setValue(MANAGE_IGNORE_FILES_PROPERTY, true);
+ propertiesComponent.setValue(ASKED_MANAGE_IGNORE_FILES_PROPERTY, true);
notification.expire();
}),
NotificationAction.create(VcsBundle.message("ignored.file.manage.all.project"), (event, notification) -> {
manageIgnore.run();
applicationSettings.MANAGE_IGNORE_FILES = true;
+ propertiesComponent.setValue(ASKED_MANAGE_IGNORE_FILES_PROPERTY, true);
notification.expire();
}),
- NotificationAction.create(VcsBundle.message("ignored.file.manage.notnow"), (event, notification) -> notification.expire()));
+ NotificationAction.create(VcsBundle.message("ignored.file.manage.notmanage"), (event, notification) -> {
+ propertiesComponent.setValue(ASKED_MANAGE_IGNORE_FILES_PROPERTY, true);
+ notification.expire();
+ }));
}
public static boolean generateIgnoreFileIfNeeded(@NotNull Project project,
}
}
- private static IgnoredFileContentProvider getIgnoredFileContentProvider(@NotNull Project project,
+ public static IgnoredFileContentProvider getIgnoredFileContentProvider(@NotNull Project project,
@NotNull AbstractVcs vcs) {
return IgnoredFileContentProvider.IGNORE_FILE_CONTENT_PROVIDER.extensions(project)
.filter((provider) -> provider.getSupportedVcs().equals(vcs.getKeyInstanceMethod()))
PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(project);
VcsApplicationSettings applicationSettings = VcsApplicationSettings.getInstance();
- return applicationSettings.MANAGE_IGNORE_FILES || propertiesComponent.getBoolean(MANAGE_IGNORE_FILES_PROPERTY, false);
+ return applicationSettings.MANAGE_IGNORE_FILES || (propertiesComponent.getBoolean(MANAGE_IGNORE_FILES_PROPERTY, false)
+ && Registry.is("vcs.ignorefile.generation"));
+ }
+
+ private static boolean notAskedToManageIgnoreFiles(@NotNull Project project) {
+ PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(project);
+
+ return !propertiesComponent.getBoolean(ASKED_MANAGE_IGNORE_FILES_PROPERTY, false);
}
private static boolean isFileSharedInVcs(@NotNull Project project, @NotNull ChangeListManagerEx changeListManager, @NotNull String filePath) {
val description = provider.ignoredGroupDescription
if (description.isNotBlank()) {
- content.append(prependCommentHashCharacterIfNeeded(description))
+ content.append(buildIgnoreGroupDescription(provider))
content.append(lineSeparator)
}
content.append(ignoredFiles.joinToString(lineSeparator))
append("!$ignorePattern")
}.toString()
+ override fun buildIgnoreGroupDescription(ignoredFileProvider: IgnoredFileProvider) =
+ prependCommentHashCharacterIfNeeded(ignoredFileProvider.ignoredGroupDescription)
+
private fun prependCommentHashCharacterIfNeeded(description: String): String =
if (description.startsWith("#")) description else "# $description"
}
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package git4idea.ignore
+import com.intellij.configurationStore.saveComponentManager
import com.intellij.openapi.application.WriteAction
+import com.intellij.openapi.application.invokeAndWaitIfNeeded
+import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.project.Project.DIRECTORY_STORE_FOLDER
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vcs.VcsConfiguration
+import com.intellij.openapi.vcs.changes.IgnoredBeanFactory
+import com.intellij.openapi.vcs.changes.ignore.psi.util.addNewElementsToIgnoreBlock
+import com.intellij.openapi.vcs.changes.ignore.psi.util.updateIgnoreBlock
import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.project.stateStore
import git4idea.GitUtil
import git4idea.repo.GitRepositoryFiles.GITIGNORE
-import git4idea.test.GitPlatformTest
-import git4idea.test.createRepository
+import git4idea.test.GitSingleRepoTest
import java.io.File
-import java.nio.file.Path
import java.nio.file.Paths
const val OUT = "out"
const val EXCLUDED_CHILD = "$EXCLUDED/$EXCLUDED_CHILD_DIR"
const val SHELF = "shelf"
-class GitIgnoredFileTest : GitPlatformTest() {
+class GitIgnoredFileTest : GitSingleRepoTest() {
- override fun getProjectDirOrFile(): Path {
- val projectRoot = File(testRoot, "project")
- val file: File = FileUtil.createTempDirectory(projectRoot, FileUtil.sanitizeFileName(name, true), "")
- val ideaDir = file.resolve(DIRECTORY_STORE_FOLDER)
- ideaDir.mkdir()
- return file.toPath()
- }
+ override fun getProjectDirOrFile() = getProjectDirOrFile(true)
override fun setUp() {
super.setUp()
Registry.get("vcs.ignorefile.generation").setValue(true, testRootDisposable)
- createRepository(project, projectPath)
+ }
+
+ override fun setUpProject() {
+ super.setUpProject()
+ invokeAndWaitIfNeeded { saveComponentManager(project) } //will create .idea directory
}
override fun setUpModule() {
- WriteAction.runAndWait<RuntimeException> {
+ runWriteAction {
myModule = createMainModule()
val moduleDir = myModule.moduleFile!!.parent
myModule.addContentRoot(moduleDir)
if (workspaceFilePath == null) fail("Cannot detect workspace file path")
val workspaceFile = File(workspaceFilePath!!)
val workspaceFileExist = FileUtil.createIfNotExists(workspaceFile)
- if (!workspaceFileExist || VfsUtil.findFileByIoFile(workspaceFile, true) == null) fail("Workspace file doesn't exist and cannot be created")
+ if (!workspaceFileExist || VfsUtil.findFileByIoFile(workspaceFile, true) == null)
+ fail("Workspace file doesn't exist and cannot be created")
GitUtil.generateGitignoreFileIfNeeded(project, VfsUtil.findFile(Paths.get("$projectPath/$DIRECTORY_STORE_FOLDER"), true)!!)
assertGitignoreValid(gitIgnore,
"""
- # Default ignored files
- /$SHELF/
- /${workspaceFile.name}
- """)
+ # Default ignored files
+ /$SHELF/
+ /${workspaceFile.name}
+ """)
}
fun `test gitignore content in project root`() {
""")
}
+ fun `test update first ignore block`() {
+ val projectCharset = EncodingProjectManager.getInstance(project).defaultCharset
+ val firstBlock = """
+ # first block
+ /$EXCLUDED/
+ /$EXCLUDED_CHILD/
+ /$OUT/
+ """
+ val middleBlock = """
+ # middle block
+ /middleBlockFolder/
+ /generatedMiddle/
+ /folder/*.txt
+ *.xml
+ """
+ val lastBlock = """
+ # last block
+ /testInBlock2/
+ /generated/
+ *.txt
+ """
+ val newFirstBlock = """
+ # first block
+ /test/
+ /file.txt
+ """
+ val gitIgnore = File("$projectPath/$GITIGNORE")
+ gitIgnore.writeText(
+ """
+ $firstBlock
+
+ $middleBlock
+
+ $lastBlock
+ """.trimIndent(), projectCharset
+ )
+
+ val ignoreVF = getVirtualFile(gitIgnore) ?: return
+ val ignoreGroup = "# first block"
+ val psiIgnore = updateIgnoreBlock(project, ignoreVF, ignoreGroup,
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/test", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file.txt", project))
+ gitIgnore.writeText(psiIgnore?.text ?: "", projectCharset)
+
+ assertGitignoreValid(gitIgnore, """
+ $newFirstBlock
+
+ $middleBlock
+
+ $lastBlock
+ """)
+ }
+
+ fun `test update middle ignore block`() {
+ val projectCharset = EncodingProjectManager.getInstance(project).defaultCharset
+ val firstBlock = """
+ # first block
+ /$EXCLUDED/
+ /$EXCLUDED_CHILD/
+ /$OUT/
+ """
+ val middleBlock = """
+ # middle block
+ /middleBlockFolder/
+ /generatedMiddle/
+ /folder/*.txt
+ *.xml
+ """
+ val lastBlock = """
+ # last block
+ /testInBlock2/
+ /generated/
+ *.txt
+ """
+ val newMiddleBlock = """
+ # middle block
+ /test/
+ /file.txt
+ """
+ val gitIgnore = File("$projectPath/$GITIGNORE")
+ gitIgnore.writeText(
+ """
+ $firstBlock
+
+ $middleBlock
+
+ $lastBlock
+ """.trimIndent(), projectCharset
+ )
+
+ val ignoreVF = getVirtualFile(gitIgnore) ?: return
+ val ignoreGroup = "# middle block"
+ val psiIgnore = updateIgnoreBlock(project, ignoreVF, ignoreGroup,
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/test", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file.txt", project))
+ gitIgnore.writeText(psiIgnore?.text ?: "", projectCharset)
+
+ assertGitignoreValid(gitIgnore, """
+ $firstBlock
+
+ $newMiddleBlock
+
+ $lastBlock
+ """)
+ }
+
+ fun `test update last ignore block`() {
+ val projectCharset = EncodingProjectManager.getInstance(project).defaultCharset
+ val firstBlock = """
+ # first block
+ /$EXCLUDED/
+ /$EXCLUDED_CHILD/
+ /$OUT/
+ """
+ val middleBlock = """
+ # middle block
+ /middleBlockFolder/
+ /generatedMiddle/
+ /folder/*.txt
+ *.xml
+ """
+ val lastBlock = """
+ # last block
+ /testInBlock2/
+ /generated/
+ *.txt
+ """
+ val newLastBlock = """
+ # last block
+ /test/
+ /file.txt
+ """
+ val gitIgnore = File("$projectPath/$GITIGNORE")
+ gitIgnore.writeText(
+ """
+ $firstBlock
+
+ $middleBlock
+
+ $lastBlock
+ """.trimIndent(), projectCharset
+ )
+
+ val ignoreVF = getVirtualFile(gitIgnore) ?: return
+ val ignoreGroup = "# last block"
+ val psiIgnore = updateIgnoreBlock(project, ignoreVF, ignoreGroup,
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/test", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file.txt", project))
+ gitIgnore.writeText(psiIgnore?.text ?: "", projectCharset)
+
+ assertGitignoreValid(gitIgnore, """
+ $firstBlock
+
+ $middleBlock
+
+ $newLastBlock
+ """)
+ }
+
+ fun `test add elements to first ignore block`() {
+ val projectCharset = EncodingProjectManager.getInstance(project).defaultCharset
+ val firstBlock = """
+ # first block
+ /$EXCLUDED/
+ /$EXCLUDED_CHILD/
+ /$OUT/
+ """
+ val middleBlock = """
+ # middle block
+ /middleBlockFolder/
+ /generatedMiddle/
+ /folder/*.txt
+ *.xml
+ """
+ val lastBlock = """
+ # last block
+ /testInBlock2/
+ /generated/
+ *.txt
+ """
+ val newFirstBlock = """
+ # first block
+ /$EXCLUDED/
+ /$EXCLUDED_CHILD/
+ /$OUT/
+ /test/
+ /file.txt
+ /file2.txt
+ /file3.txt
+ """
+ val gitIgnore = File("$projectPath/$GITIGNORE")
+ gitIgnore.writeText(
+ """
+ $firstBlock
+
+ $middleBlock
+
+ $lastBlock
+ """.trimIndent(), projectCharset
+ )
+
+ val ignoreVF = getVirtualFile(gitIgnore) ?: return
+ val ignoreGroup = "# first block"
+ val psiIgnore = addNewElementsToIgnoreBlock(project, ignoreVF, ignoreGroup,
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/test", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file.txt", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file2.txt", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file3.txt", project),
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/$EXCLUDED_CHILD/", project),
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/$EXCLUDED", project))
+ gitIgnore.writeText(psiIgnore?.text ?: "", projectCharset)
+
+ assertGitignoreValid(gitIgnore, """
+ $newFirstBlock
+
+ $middleBlock
+
+ $lastBlock
+ """)
+ }
+
+ fun `test add elements to middle ignore block`() {
+ val projectCharset = EncodingProjectManager.getInstance(project).defaultCharset
+ val firstBlock = """
+ # first block
+ /$EXCLUDED/
+ /$EXCLUDED_CHILD/
+ /$OUT/
+ """
+ val middleBlock = """
+ # middle block
+ /middleBlockFolder/
+ /generatedMiddle/
+ /folder/*.txt
+ *.xml
+ """
+ val lastBlock = """
+ # last block
+ /testInBlock2/
+ /generated/
+ *.txt
+ """
+ val newMiddleBlock = """
+ # middle block
+ /middleBlockFolder/
+ /generatedMiddle/
+ /folder/*.txt
+ *.xml
+ /test/
+ /file.txt
+ /file2.txt
+ /file3.txt
+ /$EXCLUDED_CHILD/
+ /$EXCLUDED/
+ """
+ val gitIgnore = File("$projectPath/$GITIGNORE")
+ gitIgnore.writeText(
+ """
+ $firstBlock
+
+ $middleBlock
+
+ $lastBlock
+ """.trimIndent(), projectCharset
+ )
+
+ val ignoreVF = getVirtualFile(gitIgnore) ?: return
+ val ignoreGroup = "# middle block"
+ val psiIgnore = addNewElementsToIgnoreBlock(project, ignoreVF, ignoreGroup,
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/test", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file.txt", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file2.txt", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file3.txt", project),
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/$EXCLUDED_CHILD", project),
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/$EXCLUDED", project))
+ gitIgnore.writeText(psiIgnore?.text ?: "", projectCharset)
+
+ assertGitignoreValid(gitIgnore, """
+ $firstBlock
+
+ $newMiddleBlock
+
+ $lastBlock
+ """)
+ }
+
+ fun `test add elements to last ignore block`() {
+ val projectCharset = EncodingProjectManager.getInstance(project).defaultCharset
+ val firstBlock = """
+ # first block
+ /$EXCLUDED/
+ /$EXCLUDED_CHILD/
+ /$OUT/
+ """
+ val middleBlock = """
+ # middle block
+ /middleBlockFolder/
+ /generatedMiddle/
+ /folder/*.txt
+ *.xml
+ """
+ val lastBlock = """
+ # last block
+ /testInBlock2/
+ /generated/
+ *.txt
+ """
+ val newLastBlock = """
+ # last block
+ /testInBlock2/
+ /generated/
+ *.txt
+ /test/
+ /file.txt
+ /file2.txt
+ /file3.txt
+ /file4.txt
+ """
+ val gitIgnore = File("$projectPath/$GITIGNORE")
+ gitIgnore.writeText(
+ """
+ $firstBlock
+
+ $middleBlock
+
+ $lastBlock
+ """.trimIndent(), projectCharset
+ )
+
+ val ignoreVF = getVirtualFile(gitIgnore) ?: return
+ val ignoreGroup = "# last block"
+ val psiIgnore = addNewElementsToIgnoreBlock(project, ignoreVF, ignoreGroup,
+ IgnoredBeanFactory.ignoreUnderDirectory("$projectPath/test", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file.txt", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file2.txt", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file3.txt", project),
+ IgnoredBeanFactory.ignoreFile("$projectPath/file4.txt", project))
+ gitIgnore.writeText(psiIgnore?.text ?: "", projectCharset)
+
+ assertGitignoreValid(gitIgnore, """
+ $firstBlock
+
+ $middleBlock
+
+ $newLastBlock
+ """)
+ }
+
fun `test do not add already ignored directories to gitignore`() {
val shelfDir = WriteAction.computeAndWait<VirtualFile, RuntimeException> {
val moduleDir = myModule.moduleFile!!.parent