1. IDEA-326189 Cannot invoke "org.eclipse.jgit.lib.AnyObjectId.hashCode()" because "id" is null (Add init commit, if repo is empty)

2. Disable sync, if init failed

GitOrigin-RevId: ec3e290d5b4a9aa926a0cfd4812a11edaf134f3d
This commit is contained in:
Sergey Pak
2023-07-27 19:59:52 +00:00
committed by intellij-monorepo-bot
parent edb8edf72c
commit 656937249f
3 changed files with 73 additions and 20 deletions
@@ -12,7 +12,6 @@ import com.intellij.ui.JBAccountInfoService
import com.intellij.util.io.createFile
import com.intellij.util.io.readText
import com.intellij.util.io.write
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.eclipse.jgit.api.Git
@@ -60,8 +59,11 @@ internal class GitSettingsLog(private val settingsSyncStorage: Path,
override fun initialize() {
configureJGit()
initRepository()
}
val dotGit = settingsSyncStorage.resolve(".git")
private fun initRepository() {
val dotGit: Path = settingsSyncStorage.resolve(".git")
repository = FileRepositoryBuilder().setGitDir(dotGit.toFile()).setAutonomous(true).readEnvironment().build()
git = Git(repository)
@@ -69,9 +71,11 @@ internal class GitSettingsLog(private val settingsSyncStorage: Path,
if (newRepository) {
LOG.info("Initializing new Git repository for Settings Sync at $settingsSyncStorage")
repository.create()
initRepository(repository)
addInitialCommit(repository)
}
if (!repository.headCommitExists()) {
addInitialCommit(repository)
}
createBranchIfNeeded(MASTER_REF_NAME, newRepository)
createBranchIfNeeded(CLOUD_REF_NAME, newRepository)
createBranchIfNeeded(IDE_REF_NAME, newRepository)
@@ -102,7 +106,7 @@ internal class GitSettingsLog(private val settingsSyncStorage: Path,
applyState(IDE_REF_NAME, snapshot, "Copy current configs", warnAboutEmptySnapshot = false)
}
private fun initRepository(repository: Repository?) {
private fun addInitialCommit(repository: Repository) {
val gitignore = settingsSyncStorage.resolve(".gitignore").createFile()
gitignore.write("""
event-log-metadata
@@ -397,8 +401,7 @@ internal 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)
@@ -527,7 +530,7 @@ internal class GitSettingsLog(private val settingsSyncStorage: Path,
override fun toString(): String = id.substring(0, 8)
}
private companion object {
companion object {
val LOG = logger<GitSettingsLog>()
const val MASTER_REF_NAME = "master"
@@ -550,7 +553,12 @@ internal class GitSettingsLog(private val settingsSyncStorage: Path,
}
}
internal fun Repository.headCommitExists(): Boolean {
val ref = this.findRef(Constants.HEAD) ?: return false
return ref.objectId != null
}
internal fun Repository.headCommit(): RevCommit {
val ref = this.findRef(Constants.HEAD)
val ref = this.findRef(Constants.HEAD) ?: throw RuntimeException("No HEAD commit found for the repo")
return this.parseCommit(ref.objectId)
}
@@ -62,17 +62,21 @@ class SettingsSyncBridge(parentDisposable: Disposable,
@RequiresBackgroundThread
internal fun initialize(initMode: InitMode) {
saveIdeSettings()
try {
saveIdeSettings()
settingsLog.initialize()
settingsLog.initialize()
// the queue is not activated initially => events will be collected but not processed until we perform all initialization tasks
SettingsSyncEvents.getInstance().addListener(settingsChangeListener)
ideMediator.activateStreamProvider()
// the queue is not activated initially => events will be collected but not processed until we perform all initialization tasks
SettingsSyncEvents.getInstance().addListener(settingsChangeListener)
ideMediator.activateStreamProvider()
applyInitialChanges(initMode)
applyInitialChanges(initMode)
queue.activate()
queue.activate()
} catch (ex: Exception) {
stopSyncingAndRollback(null, ex)
}
}
private fun saveIdeSettings() {
@@ -276,16 +280,20 @@ class SettingsSyncBridge(parentDisposable: Disposable,
val idePosition: SettingsLog.Position,
val cloudPosition: SettingsLog.Position,
val knownServerId: String?
)
) {
override fun toString(): String {
return "CurrentState(masterPosition=$masterPosition, idePosition=$idePosition, cloudPosition=$cloudPosition, knownServerId=$knownServerId)"
}
}
private fun collectCurrentState(): CurrentState = CurrentState(settingsLog.getMasterPosition(),
settingsLog.getIdePosition(),
settingsLog.getCloudPosition(),
SettingsSyncLocalSettings.getInstance().knownAndAppliedServerId)
private fun stopSyncingAndRollback(previousState: CurrentState, exception: Throwable? = null) {
private fun stopSyncingAndRollback(previousState: CurrentState?, exception: Throwable? = null) {
if (exception != null) {
LOG.error("Couldn't apply settings. Disabling sync and rolling back.", exception)
LOG.error("Couldn't apply settings. Settings sync will be disabled.", exception)
SettingsSyncEventsStatistics.DISABLED_AUTOMATICALLY.log(SettingsSyncEventsStatistics.AutomaticDisableReason.EXCEPTION)
}
else {
@@ -299,12 +307,15 @@ class SettingsSyncBridge(parentDisposable: Disposable,
ideMediator.removeStreamProvider()
SettingsSyncEvents.getInstance().removeListener(settingsChangeListener)
pendingEvents.clear()
rollback(previousState)
if (previousState != null) {
rollback(previousState)
}
queue.deactivate() // for tests it is important to have it the last statement, otherwise waitForAllExecuted can finish before rollback
}
private fun rollback(previousState: CurrentState) {
try {
LOG.warn("Rolling back to previous state: $previousState")
SettingsSyncLocalSettings.getInstance().knownAndAppliedServerId = previousState.knownServerId
settingsLog.setIdePosition(previousState.idePosition)
settingsLog.setCloudPosition(previousState.cloudPosition)
@@ -1,9 +1,12 @@
package com.intellij.settingsSync
import com.intellij.idea.TestFor
import com.intellij.openapi.components.SettingsCategory
import com.intellij.testFramework.LoggedErrorProcessor
import com.intellij.testFramework.utils.io.createDirectory
import com.intellij.util.ConcurrencyUtil
import com.intellij.util.concurrency.AppExecutorUtil.createBoundedScheduledExecutorService
import com.intellij.util.io.createFile
import com.intellij.util.io.readText
import com.intellij.util.io.write
import org.eclipse.jgit.api.Git
@@ -393,6 +396,37 @@ internal class SettingsSyncFlowTest : SettingsSyncTestBase() {
}
}
@TestFor(issues = ["IDEA-326189"])
@Test fun `create initial commit for empty repo`(){
val dotGit: Path = settingsSyncStorage.resolve(".git")
val repository = FileRepositoryBuilder().setGitDir(dotGit.toFile()).setAutonomous(true).readEnvironment().build()
repository.create()
initSettingsSync()
assertNotNull(repository.findRef(GitSettingsLog.CLOUD_REF_NAME))
assertNotNull(repository.findRef(GitSettingsLog.IDE_REF_NAME))
}
@Test fun `disable sync if init failed`(){
SettingsSyncSettings.getInstance().syncEnabled = true
val dotGit: Path = settingsSyncStorage.resolve(".git")
val repository = FileRepositoryBuilder().setGitDir(dotGit.toFile()).setAutonomous(true).readEnvironment().build()
repository.create()
val gitignore = settingsSyncStorage.resolve(".gitignore").createFile()
gitignore.write("""
.idea/workspace.xml
""".trimIndent())
val git = Git(repository)
git.add().addFilepattern(".gitignore").call()
git.commit().setMessage("init").setNoVerify(true).setSign(false).call()
(dotGit / "index").write("aaaaaaa")
LoggedErrorProcessor.executeAndReturnLoggedError{
initSettingsSync()
}
assertFalse(SettingsSyncSettings.getInstance().syncEnabled)
}
private fun syncSettingsAndWait() {
fireSettingsChanged()
bridge.waitForAllExecuted()