mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-13080 DIRC checksum mismatch error in Settings Sync
GitOrigin-RevId: ffe305a7eef4187508363cfe0d11ae8e3da66dab
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6396314a8e
commit
82ff26ce77
@@ -49,10 +49,12 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
private val initialSnapshotProvider: (SettingsSnapshot) -> SettingsSnapshot
|
||||
) : SettingsLog, Disposable {
|
||||
|
||||
private lateinit var repository: Repository
|
||||
private val FIVE_SECONDS = 5000
|
||||
|
||||
private lateinit var repository: Repository
|
||||
private lateinit var git: Git
|
||||
|
||||
|
||||
private val master: Ref get() = repository.findRef(MASTER_REF_NAME)!!
|
||||
private val ide: Ref get() = repository.findRef(IDE_REF_NAME)!!
|
||||
private val cloud: Ref get() = repository.findRef(CLOUD_REF_NAME)!!
|
||||
@@ -66,10 +68,10 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
|
||||
override fun initialize() {
|
||||
configureJGit()
|
||||
initRepository()
|
||||
initAndCheckRepository(canRetry = true)
|
||||
}
|
||||
|
||||
private fun initRepository() {
|
||||
private fun initAndCheckRepository(canRetry: Boolean) {
|
||||
val dotGit: Path = settingsSyncStorage.resolve(".git")
|
||||
repository = FileRepositoryBuilder().setGitDir(dotGit.toFile()).setAutonomous(true).readEnvironment().build()
|
||||
git = Git(repository)
|
||||
@@ -79,17 +81,19 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
LOG.info("Initializing new Git repository for Settings Sync at $settingsSyncStorage")
|
||||
repository.create()
|
||||
addInitialCommit(repository)
|
||||
} else {
|
||||
val rootLockEntries = dotGit.listDirectoryEntries("*.lock")
|
||||
val headsLockEntries = (dotGit / "refs" / "heads").listDirectoryEntries("*.lock")
|
||||
val FIVE_SECONDS = 5000
|
||||
for(lock in rootLockEntries + headsLockEntries) {
|
||||
if (System.currentTimeMillis() - lock.getLastModifiedTime().toMillis() > FIVE_SECONDS) {
|
||||
FileUtil.delete(lock)
|
||||
} else {
|
||||
// Repository is currently in process of operating.
|
||||
// Shouldn't delete the lock, otherwise we'll damage the repo. Just log instead
|
||||
LOG.warn("Found new lock (${lock.fileName}) modified ${lock.getLastModifiedTime().toInstant()}. Repo initialization might fail")
|
||||
}
|
||||
else {
|
||||
checkLocks(dotGit)
|
||||
if (!isRepositoryConsistent()) {
|
||||
if (!canRetry) {
|
||||
throw RuntimeException("Settings Sync local repository is corrupted after retry. Will not enable settings sync")
|
||||
}
|
||||
LOG.warn("Settings Sync repository is corrupted, removing it")
|
||||
if (FileUtil.delete(settingsSyncStorage.toFile())) {
|
||||
initAndCheckRepository(canRetry = false)
|
||||
}
|
||||
else {
|
||||
throw RuntimeException("Settings Sync local repository is corrupted and cannot be deleted. Will not enable settings sync")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,6 +105,38 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
createBranchIfNeeded(IDE_REF_NAME, newRepository)
|
||||
}
|
||||
|
||||
private fun checkLocks(dotGit: Path) {
|
||||
val rootLockEntries = dotGit.listDirectoryEntries("*.lock")
|
||||
val headsLockEntries = (dotGit / "refs" / "heads").listDirectoryEntries("*.lock")
|
||||
for (lock in rootLockEntries + headsLockEntries) {
|
||||
if (System.currentTimeMillis() - lock.getLastModifiedTime().toMillis() > FIVE_SECONDS) {
|
||||
FileUtil.delete(lock)
|
||||
}
|
||||
else {
|
||||
// Repository is currently in process of operating.
|
||||
// Shouldn't delete the lock, otherwise we'll damage the repo. Just log instead
|
||||
LOG.warn("Found new lock (${lock.fileName}) modified ${lock.getLastModifiedTime().toInstant()}. Repo initialization might fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isRepositoryConsistent(): Boolean {
|
||||
try {
|
||||
LOG.info("Checking consistency of the repository...")
|
||||
val commits = git.log().call().toList()
|
||||
commits.forEach {
|
||||
LOG.debug("Found local commit ${it.short}")
|
||||
}
|
||||
val status = git.status().call()
|
||||
LOG.info("Local repository has ${commits.size}. Status: ${if (status.isClean) "Clean" else "Dirty"}")
|
||||
return true
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
LOG.warn("An exception occurred while checking repository consistency. Will delete the repository", ex)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureJGit() {
|
||||
GpgSigner.setDefault(MockGpgSigner())
|
||||
}
|
||||
@@ -127,7 +163,10 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
}
|
||||
|
||||
private fun addInitialCommit(repository: Repository) {
|
||||
val gitignore = settingsSyncStorage.resolve(".gitignore").createParentDirectories().createFile()
|
||||
val gitignore = settingsSyncStorage.resolve(".gitignore")
|
||||
if (!gitignore.exists()) {
|
||||
gitignore.createParentDirectories().createFile()
|
||||
}
|
||||
gitignore.write("""
|
||||
event-log-metadata
|
||||
jdbc-drivers
|
||||
@@ -382,9 +421,9 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
conflictingFiles -= pluginJsonPath
|
||||
}
|
||||
if (conflictingFiles.isNotEmpty()) {
|
||||
if (conflictingFiles.any { !it.startsWith(PathManager.OPTIONS_DIRECTORY) && !it.startsWith(METAINFO_FOLDER) })
|
||||
if (conflictingFiles.any { !it.startsWith(PathManager.OPTIONS_DIRECTORY) && !it.startsWith(METAINFO_FOLDER) })
|
||||
SettingsSyncEventsStatistics.MERGE_CONFLICT_OCCURRED.log(SettingsSyncEventsStatistics.MergeConflictType.SCHEMES)
|
||||
if (conflictingFiles.any { it.startsWith(PathManager.OPTIONS_DIRECTORY)}) {
|
||||
if (conflictingFiles.any { it.startsWith(PathManager.OPTIONS_DIRECTORY) }) {
|
||||
SettingsSyncEventsStatistics.MERGE_CONFLICT_OCCURRED.log(SettingsSyncEventsStatistics.MergeConflictType.OPTIONS)
|
||||
}
|
||||
}
|
||||
@@ -431,7 +470,8 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
|
||||
git.add().addFilepattern(".").call()
|
||||
commit(buildCommitMessage(getLocalApplicationInfo(), commitHash), null, false)
|
||||
} catch (e: GitAPIException) {
|
||||
}
|
||||
catch (e: GitAPIException) {
|
||||
NotificationService.getInstance().notifySateRestoreFailed()
|
||||
LOG.error("Failed to restore state to hash = $commitHash", e)
|
||||
}
|
||||
@@ -467,10 +507,12 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
return treeIterator
|
||||
}
|
||||
|
||||
private fun <T : Any> mergeSettingsProviderFile(settingsProvider: SettingsProvider<T>,
|
||||
relativePath: String,
|
||||
ideBranchTip: RevCommit,
|
||||
cloudBranchTip: RevCommit): String {
|
||||
private fun <T : Any> mergeSettingsProviderFile(
|
||||
settingsProvider: SettingsProvider<T>,
|
||||
relativePath: String,
|
||||
ideBranchTip: RevCommit,
|
||||
cloudBranchTip: RevCommit,
|
||||
): String {
|
||||
return smartMergeFile(relativePath, ideBranchTip, cloudBranchTip,
|
||||
deserializer = { settingsProvider.deserialize(it) },
|
||||
serializer = { settingsProvider.serialize(it) },
|
||||
@@ -492,7 +534,8 @@ class GitSettingsLog(private val settingsSyncStorage: Path,
|
||||
cloudBranchTip: RevCommit,
|
||||
serializer: (T) -> String,
|
||||
deserializer: (String) -> T,
|
||||
merger: (T?, T, T) -> T): String {
|
||||
merger: (T?, T, T) -> T,
|
||||
): String {
|
||||
val ideContent = getFileContentInBranch(relativePath, ideBranchTip)
|
||||
val ideState = deserializer(ideContent)
|
||||
val cloudContent = getFileContentInBranch(relativePath, cloudBranchTip)
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.intellij.testFramework.TemporaryDirectory
|
||||
import com.intellij.ui.JBAccountInfoService
|
||||
import com.intellij.util.io.createParentDirectories
|
||||
import com.intellij.util.io.write
|
||||
import org.eclipse.jgit.dircache.DirCache
|
||||
import org.eclipse.jgit.lib.Repository
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
@@ -496,6 +497,47 @@ internal class GitSettingsLogTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["IJPL-13080"])
|
||||
fun `drop and reinit settings sync if cannot init`() {
|
||||
val editorXml = (configDir / "options" / "editor.xml").createParentDirectories().createFile()
|
||||
val editorContent = "editorContent"
|
||||
val state1 = "State 1"
|
||||
|
||||
editorXml.writeText(editorContent)
|
||||
val settingsLog = initializeGitSettingsLog(editorXml)
|
||||
|
||||
settingsLog.applyIdeState(settingsSnapshot {
|
||||
fileState("options/editor.xml", state1)
|
||||
}, "Local changes")
|
||||
val indexFile = getRepository().indexFile
|
||||
|
||||
val size = 16384
|
||||
val zeroBytesArray = ByteArray(size)
|
||||
indexFile.writeBytes(zeroBytesArray)
|
||||
assertEquals(size.toLong(), indexFile.length())
|
||||
|
||||
val editorXmlSync = settingsSyncStorage / "options" / "editor.xml"
|
||||
assertEquals(state1, editorXmlSync.readText())
|
||||
try {
|
||||
DirCache.read(getRepository())
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
}
|
||||
|
||||
initializeGitSettingsLog(editorXml)
|
||||
getRepository().indexFile.length()
|
||||
assertNotEquals(size, indexFile.length())
|
||||
assertTrue(editorXmlSync.exists() && editorXmlSync.readText() == editorContent)
|
||||
|
||||
try {
|
||||
DirCache.read(getRepository())
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
fail("Shouldn't fail: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkUsernameEmail(expectedName: String, expectedEmail: String) {
|
||||
arrayOf<FileAttribute<*>>()
|
||||
val editorXml = (configDir / "options" / "editor.xml").createParentDirectories().createFile()
|
||||
|
||||
@@ -11,6 +11,7 @@ internal class MockSettingsSyncIdeMediator : SettingsSyncIdeMediator {
|
||||
internal val files = mutableMapOf<String, String>()
|
||||
|
||||
private var exceptionToThrowOnApply: Exception? = null
|
||||
private var exceptionToThrowOnGetInitial: Exception? = null
|
||||
|
||||
override suspend fun applyToIde(snapshot: SettingsSnapshot, settings: SettingsSyncState?) {
|
||||
if (exceptionToThrowOnApply != null) {
|
||||
@@ -34,6 +35,9 @@ internal class MockSettingsSyncIdeMediator : SettingsSyncIdeMediator {
|
||||
}
|
||||
|
||||
override fun getInitialSnapshot(appConfigPath: Path, lastSavedSnapshot: SettingsSnapshot): SettingsSnapshot {
|
||||
if (exceptionToThrowOnGetInitial != null) {
|
||||
throw exceptionToThrowOnGetInitial!!
|
||||
}
|
||||
return getAllFilesFromSettingsAsSnapshot(appConfigPath)
|
||||
}
|
||||
|
||||
@@ -41,6 +45,10 @@ internal class MockSettingsSyncIdeMediator : SettingsSyncIdeMediator {
|
||||
exceptionToThrowOnApply = exception
|
||||
}
|
||||
|
||||
fun throwOnGetInitial(exception: Exception) {
|
||||
exceptionToThrowOnGetInitial = exception
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getAllFilesFromSettingsAsSnapshot(appConfigPath: Path): SettingsSnapshot {
|
||||
val settingsSyncStorage = appConfigPath.resolve(SETTINGS_SYNC_STORAGE_FOLDER)
|
||||
|
||||
@@ -426,7 +426,9 @@ internal class SettingsSyncFlowTest : SettingsSyncTestBase() {
|
||||
git.add().addFilepattern(".gitignore").call()
|
||||
git.commit().setMessage("init").setNoVerify(true).setSign(false).call()
|
||||
|
||||
(dotGit / "index").write("aaaaaaa")
|
||||
val errorMessage = "Failed to collect initial snapshot"
|
||||
ideMediator.throwOnGetInitial(RuntimeException(errorMessage))
|
||||
|
||||
LoggedErrorProcessor.executeAndReturnLoggedError {
|
||||
initSettingsSync()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user