diff --git a/plugins/settings-sync/src/com/intellij/settingsSync/GitSettingsLog.kt b/plugins/settings-sync/src/com/intellij/settingsSync/GitSettingsLog.kt index 39d32fa8f090..3451e503e89a 100644 --- a/plugins/settings-sync/src/com/intellij/settingsSync/GitSettingsLog.kt +++ b/plugins/settings-sync/src/com/intellij/settingsSync/GitSettingsLog.kt @@ -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() 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) } diff --git a/plugins/settings-sync/src/com/intellij/settingsSync/SettingsSyncBridge.kt b/plugins/settings-sync/src/com/intellij/settingsSync/SettingsSyncBridge.kt index fb24c837c925..4d72ec76c388 100644 --- a/plugins/settings-sync/src/com/intellij/settingsSync/SettingsSyncBridge.kt +++ b/plugins/settings-sync/src/com/intellij/settingsSync/SettingsSyncBridge.kt @@ -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) diff --git a/plugins/settings-sync/tests/com/intellij/settingsSync/SettingsSyncFlowTest.kt b/plugins/settings-sync/tests/com/intellij/settingsSync/SettingsSyncFlowTest.kt index 9327d7f92b1b..13a1678edc3b 100644 --- a/plugins/settings-sync/tests/com/intellij/settingsSync/SettingsSyncFlowTest.kt +++ b/plugins/settings-sync/tests/com/intellij/settingsSync/SettingsSyncFlowTest.kt @@ -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()