IJPL-13080 DIRC checksum mismatch error in Settings Sync

GitOrigin-RevId: ffe305a7eef4187508363cfe0d11ae8e3da66dab
This commit is contained in:
Sergey Pak
2024-07-03 22:36:20 +00:00
committed by intellij-monorepo-bot
parent 6396314a8e
commit 82ff26ce77
4 changed files with 119 additions and 24 deletions
@@ -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()
}