diff --git a/plugins/settings-repository/src/BaseRepositoryManager.kt b/plugins/settings-repository/src/BaseRepositoryManager.kt index 04e185a32ad8..1112919d9139 100644 --- a/plugins/settings-repository/src/BaseRepositoryManager.kt +++ b/plugins/settings-repository/src/BaseRepositoryManager.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.jetbrains.settingsRepository import com.intellij.openapi.application.ApplicationManager @@ -6,18 +21,15 @@ import com.intellij.openapi.fileTypes.StdFileTypes import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vcs.merge.MergeDialogCustomizer import com.intellij.openapi.vcs.merge.MergeProvider2 -import com.intellij.openapi.vcs.merge.MergeSession import com.intellij.openapi.vcs.merge.MultipleFileMergeDialog import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightVirtualFile import com.intellij.util.PathUtilRt -import org.jetbrains.annotations.TestOnly import java.io.File import java.io.FileInputStream import java.io.InputStream import java.io.OutputStream -import java.util.Arrays import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.read import kotlin.concurrent.write @@ -158,38 +170,25 @@ fun File.removeWithParentsIfEmpty(root: File, isFile: Boolean = true) { } } } -// kotlin bug, cannot be val (.NoSuchMethodError: org.jetbrains.settingsRepository.SettingsRepositoryPackage.getMARKER_ACCEPT_MY()[B) -TestOnly object AM { - val MARKER_ACCEPT_MY: ByteArray = "__accept my__".toByteArray() - val MARKER_ACCEPT_THEIRS: ByteArray = "__accept theirs__".toByteArray() -} + +var conflictResolver: ((files: List, mergeProvider: MergeProvider2) -> Unit)? = null fun resolveConflicts(files: List, mergeProvider: MergeProvider2): List { if (ApplicationManager.getApplication()!!.isUnitTestMode()) { - val mergeSession = mergeProvider.createMergeSession(files) - for (file in files) { - val mergeData = mergeProvider.loadRevisions(file) - if (Arrays.equals(mergeData.CURRENT, AM.MARKER_ACCEPT_MY) || Arrays.equals(mergeData.LAST, AM.MARKER_ACCEPT_THEIRS)) { - mergeSession.conflictResolvedForFile(file, MergeSession.Resolution.AcceptedYours) - } - else if (Arrays.equals(mergeData.CURRENT, AM.MARKER_ACCEPT_THEIRS) || Arrays.equals(mergeData.LAST, AM.MARKER_ACCEPT_MY)) { - mergeSession.conflictResolvedForFile(file, MergeSession.Resolution.AcceptedTheirs) - } - else if (Arrays.equals(mergeData.LAST, AM.MARKER_ACCEPT_MY)) { - file.setBinaryContent(mergeData.LAST!!) - mergeProvider.conflictResolvedForFile(file) - } - else { - throw UnsupportedOperationException() - } + if (conflictResolver == null) { + throw CannotResolveConflictInTestMode() + } + else { + conflictResolver!!(files, mergeProvider) } - return files } var processedFiles: List? = null invokeAndWaitIfNeed { - val fileMergeDialog = MultipleFileMergeDialog(null, files, mergeProvider, MergeDialogCustomizer()) + val fileMergeDialog = MultipleFileMergeDialog(null, files, mergeProvider, object : MergeDialogCustomizer() { + override fun getMultipleFileDialogTitle() = "Settings Repository: Files Merged with Conflicts" + }) fileMergeDialog.show() processedFiles = fileMergeDialog.getProcessedFiles() } diff --git a/plugins/settings-repository/src/IcsManager.kt b/plugins/settings-repository/src/IcsManager.kt index c99af441899e..27ff98ad2fb9 100644 --- a/plugins/settings-repository/src/IcsManager.kt +++ b/plugins/settings-repository/src/IcsManager.kt @@ -289,6 +289,4 @@ class IcsApplicationLoadListener : ApplicationLoadListener { icsManager.beforeApplicationLoaded(application) } -} - -class NoRemoteRepositoryException(cause: Throwable) : RuntimeException(cause.getMessage(), cause) \ No newline at end of file +} \ No newline at end of file diff --git a/plugins/settings-repository/src/git/JGitMergeProvider.kt b/plugins/settings-repository/src/git/JGitMergeProvider.kt index e240fb435441..7a96d319e90e 100644 --- a/plugins/settings-repository/src/git/JGitMergeProvider.kt +++ b/plugins/settings-repository/src/git/JGitMergeProvider.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.jetbrains.settingsRepository.git import com.intellij.openapi.fileEditor.FileDocumentManager @@ -6,9 +21,8 @@ import com.intellij.openapi.vcs.merge.MergeProvider2 import com.intellij.openapi.vcs.merge.MergeSession import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.ArrayUtil import com.intellij.util.ui.ColumnInfo -import org.eclipse.jgit.diff.RawText -import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.Repository import org.jetbrains.jgit.dirCache.deletePath import org.jetbrains.jgit.dirCache.writePath @@ -16,7 +30,7 @@ import org.jetbrains.settingsRepository.RepositoryVirtualFile import java.nio.CharBuffer import java.util.ArrayList -private fun conflictsToVirtualFiles(map: Map>): MutableList { +private fun conflictsToVirtualFiles(map: Map): MutableList { val result = ArrayList(map.size()) for (path in map.keySet()) { result.add(RepositoryVirtualFile(path)) @@ -24,7 +38,12 @@ private fun conflictsToVirtualFiles(map: Map>) : MergeProvider2 { +/** + * If content null: + * Ours or Theirs - deleted. + * Base - missed (no base). + */ +class JGitMergeProvider(private val repository: Repository, private val conflicts: Map, private val pathToContent: Map.(path: String, index: Int) -> ByteArray?) : MergeProvider2 { override fun createMergeSession(files: List) = JGitMergeSession() override fun conflictResolvedForFile(file: VirtualFile) { @@ -42,22 +61,23 @@ class JGitMergeProvider(private val repository: Repository, private val myCommit } } - // cannot be private due to Kotlin bug - fun addFile(bytes: ByteArray, file: VirtualFile, size: Int = bytes.size()) { + private fun addFile(bytes: ByteArray, file: VirtualFile, size: Int = bytes.size()) { repository.writePath(file.getPath(), bytes, size) } override fun isBinary(file: VirtualFile) = file.getFileType().isBinary() override fun loadRevisions(file: VirtualFile): MergeData { - val sequences = conflicts[file.getPath()]!!.getSequences() + val path = file.getPath() val mergeData = MergeData() - mergeData.ORIGINAL = (sequences[0] as RawText).getContent() - mergeData.CURRENT = (sequences[1] as RawText).getContent() - mergeData.LAST = (sequences[2] as RawText).getContent() + mergeData.ORIGINAL = getContentOrEmpty(path, 0) + mergeData.CURRENT = getContentOrEmpty(path, 1) + mergeData.LAST = getContentOrEmpty(path, 2) return mergeData } + private fun getContentOrEmpty(path: String, index: Int) = conflicts.pathToContent(path, index) ?: ArrayUtil.EMPTY_BYTE_ARRAY + private inner class JGitMergeSession : MergeSession { override fun getMergeInfoColumns(): Array> { return arrayOf(StatusColumn(false), StatusColumn(true)) @@ -71,19 +91,19 @@ class JGitMergeProvider(private val repository: Repository, private val myCommit } else { val content = getContent(file, resolution == MergeSession.Resolution.AcceptedTheirs) - if (content == RawText.EMPTY_TEXT) { + if (content == null) { repository.deletePath(file.getPath()) } else { - addFile(content.getContent(), file) + addFile(content, file) } } } - private fun getContent(file: VirtualFile, isTheirs: Boolean) = conflicts[file.getPath()]!!.getSequences()[if (isTheirs) 2 else 1] as RawText + private fun getContent(file: VirtualFile, isTheirs: Boolean) = conflicts.pathToContent(file.getPath(), if (isTheirs) 2 else 1) inner class StatusColumn(private val isTheirs: Boolean) : ColumnInfo(if (isTheirs) "Theirs" else "Yours") { - override fun valueOf(file: VirtualFile?) = if (getContent(file!!, isTheirs) == RawText.EMPTY_TEXT) "Deleted" else "Modified" + override fun valueOf(file: VirtualFile?) = if (getContent(file!!, isTheirs) == null) "Deleted" else "Modified" override fun getMaxStringValue() = "Modified" diff --git a/plugins/settings-repository/src/git/pull.kt b/plugins/settings-repository/src/git/pull.kt index 38ca8ced5923..b5a286166783 100644 --- a/plugins/settings-repository/src/git/pull.kt +++ b/plugins/settings-repository/src/git/pull.kt @@ -17,6 +17,8 @@ package org.jetbrains.settingsRepository.git import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.SmartList +import com.intellij.util.containers.hash.LinkedHashMap import org.eclipse.jgit.api.MergeCommand.FastForwardMode import org.eclipse.jgit.api.MergeResult import org.eclipse.jgit.api.MergeResult.MergeStatus @@ -24,6 +26,8 @@ import org.eclipse.jgit.api.errors.CheckoutConflictException import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException import org.eclipse.jgit.api.errors.JGitInternalException import org.eclipse.jgit.api.errors.NoHeadException +import org.eclipse.jgit.diff.RawText +import org.eclipse.jgit.diff.Sequence import org.eclipse.jgit.dircache.DirCacheCheckout import org.eclipse.jgit.internal.JGitText import org.eclipse.jgit.lib.* @@ -38,7 +42,6 @@ import org.eclipse.jgit.treewalk.FileTreeIterator import org.jetbrains.settingsRepository.* import java.io.IOException import java.text.MessageFormat -import java.util.ArrayList open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndicator?, val commitMessageFormatter: CommitMessageFormatter = IdeaCommitMessageFormatter()) { val repository = manager.repository @@ -53,13 +56,30 @@ open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndica LOG.debug("Pull") val repository = manager.repository - val repositoryState = repository.getRepositoryState() + var repositoryState = repository.getRepositoryState() + if (repositoryState == RepositoryState.MERGING) { + val conflicts = LinkedHashMap>() + val dirCache = repository.readDirCache() + val reader = repository.newObjectReader() + reader.use { + for (i in 0..(dirCache.getEntryCount() - 1)) { + val entry = dirCache.getEntry(i) + if (!entry.isMerged()) { + conflicts.getOrPut(entry.getPathString(), { arrayOfNulls(3) })[entry.getStage() - 1] = reader.open(entry.getObjectId(), Constants.OBJ_BLOB).getCachedBytes() + } + } + } + + resolveConflicts(JGitMergeProvider(repository, conflicts, { path, index -> get(path)!!.get(index) }), conflictsToVirtualFiles(conflicts), repository) + // compute new state + repositoryState = repository.getRepositoryState() + } + if (repositoryState != RepositoryState.SAFE) { LOG.warn(MessageFormat.format(JGitText.get().cannotPullOnARepoWithState, repositoryState.name())) } - val refToMerge = prefetchedRefToMerge ?: fetch() ?: return null - + var refToMerge = prefetchedRefToMerge ?: fetch() ?: return null val mergeResult = merge(refToMerge, mergeStrategy, commitMessage = commitMessage) val mergeStatus = mergeResult.mergeStatus if (LOG.isDebugEnabled()) { @@ -67,31 +87,7 @@ open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndica } if (mergeStatus == MergeStatus.CONFLICTING) { - val mergedCommits = mergeResult.mergedCommits - assert(mergedCommits.size() == 2) - val conflicts = mergeResult.conflicts!! - var unresolvedFiles = conflictsToVirtualFiles(conflicts) - val mergeProvider = JGitMergeProvider(repository, mergedCommits[0]!!, mergedCommits[1]!!, conflicts) - val resolvedFiles: List - val mergedFiles = ArrayList() - while (true) { - resolvedFiles = resolveConflicts(unresolvedFiles, mergeProvider) - - for (file in resolvedFiles) { - mergedFiles.add(file.getPath()) - } - - if (resolvedFiles.size() == unresolvedFiles.size()) { - break - } - else { - unresolvedFiles.removeAll(mergedFiles) - } - } - - repository.commit() - - return mergeResult.result.toMutable().addChanged(mergedFiles) + return resolveConflicts(mergeResult, repository) } else if (!mergeStatus.isSuccessful()) { throw IllegalStateException(mergeResult.toString()) @@ -104,9 +100,7 @@ open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndica fun fetch(prevRefUpdateResult: RefUpdate.Result? = null): Ref? { indicator?.checkCanceled() - val repository = manager.repository - - val fetchResult = repository.fetch(remoteConfig, manager.credentialsProvider, indicator.asProgressMonitor()) ?: return null + val fetchResult = manager.repository.fetch(remoteConfig, manager.credentialsProvider, indicator.asProgressMonitor()) ?: return null if (LOG.isDebugEnabled()) { printMessages(fetchResult) @@ -129,10 +123,10 @@ open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndica // we can have more than one fetch ref spec, but currently we don't worry about it if (refUpdateResult == RefUpdate.Result.LOCK_FAILURE || refUpdateResult == RefUpdate.Result.IO_FAILURE) { if (prevRefUpdateResult == refUpdateResult) { - throw IOException("Ref update result " + refUpdateResult.name() + ", we have already tried to fetch again, but no luck") + throw IOException("Ref update result ${refUpdateResult.name()}, we have already tried to fetch again, but no luck") } - LOG.warn("Ref update result " + refUpdateResult.name() + ", trying again after 500 ms") + LOG.warn("Ref update result ${refUpdateResult.name()}, trying again after 500 ms") Thread.sleep(500) return fetch(refUpdateResult) } @@ -165,17 +159,13 @@ open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndica val head = repository.getRef(Constants.HEAD) ?: throw NoHeadException(JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported) + // handle annotated tags + val ref = repository.peel(unpeeledRef) + val objectId = ref.getPeeledObjectId() ?: ref.getObjectId() // Check for FAST_FORWARD, ALREADY_UP_TO_DATE val revWalk = RevWalk(repository) var dirCacheCheckout: DirCacheCheckout? = null try { - // handle annotated tags - val ref = repository.peel(unpeeledRef) - var objectId = ref.getPeeledObjectId() - if (objectId == null) { - objectId = ref.getObjectId() - } - val srcCommit = revWalk.lookupCommit(objectId) val headId = head.getObjectId() if (headId == null) { @@ -235,11 +225,11 @@ open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndica else { mergeMessage = commitMessageFormatter.mergeMessage(listOf(ref), head) repository.writeMergeCommitMsg(mergeMessage) - repository.writeMergeHeads(arrayListOf(ref.getObjectId())) + repository.writeMergeHeads(listOf(ref.getObjectId())) } val merger = mergeStrategy.newMerger(repository) val noProblems: Boolean - var lowLevelResults: Map>? = null + var lowLevelResults: Map>? = null var failingPaths: Map? = null var unmergedPaths: List? = null if (merger is ResolveMerger) { @@ -297,7 +287,7 @@ open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndica } } catch (e: org.eclipse.jgit.errors.CheckoutConflictException) { - throw CheckoutConflictException(if (dirCacheCheckout == null) listOf() else dirCacheCheckout.getConflicts(), e) + throw CheckoutConflictException(dirCacheCheckout?.getConflicts() ?: listOf(), e) } finally { revWalk.close() @@ -305,7 +295,7 @@ open class Pull(val manager: GitRepositoryManager, val indicator: ProgressIndica } } -class MergeResultEx(val newHead: ObjectId?, val mergeStatus: MergeStatus, val mergedCommits: Array, val result: ImmutableUpdateResult, val conflicts: Map>? = null) +class MergeResultEx(val newHead: ObjectId?, val mergeStatus: MergeStatus, val mergedCommits: Array, val result: ImmutableUpdateResult, val conflicts: Map>? = null) private fun updateHead(refLogMessage: StringBuilder, newHeadId: ObjectId, oldHeadID: ObjectId, repository: Repository) { val refUpdate = repository.updateRef(Constants.HEAD) @@ -320,3 +310,37 @@ private fun updateHead(refLogMessage: StringBuilder, newHeadId: ObjectId, oldHea } } +private fun resolveConflicts(mergeResult: MergeResultEx, repository: Repository): MutableUpdateResult { + assert(mergeResult.mergedCommits.size() == 2) + val conflicts = mergeResult.conflicts!! + val mergeProvider = JGitMergeProvider(repository, conflicts, { path, index -> + val rawText = get(path)!!.getSequences().get(index) as RawText + // RawText.EMPTY_TEXT if content is null - deleted + if (rawText == RawText.EMPTY_TEXT) null else rawText.getContent() + }) + val mergedFiles = resolveConflicts(mergeProvider, conflictsToVirtualFiles(conflicts), repository) + return mergeResult.result.toMutable().addChanged(mergedFiles) +} + +private fun resolveConflicts(mergeProvider: JGitMergeProvider, unresolvedFiles: MutableList, repository: Repository): List { + val resolvedFiles: List + val mergedFiles = SmartList() + while (true) { + resolvedFiles = resolveConflicts(unresolvedFiles, mergeProvider) + + for (file in resolvedFiles) { + mergedFiles.add(file.getPath()) + } + + if (resolvedFiles.size() == unresolvedFiles.size()) { + break + } + else { + unresolvedFiles.removeAll(mergedFiles) + } + } + + repository.commit() + + return mergedFiles +} diff --git a/plugins/settings-repository/src/git/reset.kt b/plugins/settings-repository/src/git/reset.kt index 2127ace092d2..7ed2d50e3647 100644 --- a/plugins/settings-repository/src/git/reset.kt +++ b/plugins/settings-repository/src/git/reset.kt @@ -24,8 +24,9 @@ import org.jetbrains.settingsRepository.UpdateResult class Reset(manager: GitRepositoryManager, indicator: ProgressIndicator) : Pull(manager, indicator) { fun reset(toTheirs: Boolean, localRepositoryInitializer: (() -> Unit)? = null): UpdateResult { + val message = if (toTheirs) "Overwrite local to ${manager.getUpstream()}" else "Overwrite remote ${manager.getUpstream()} to local" if (LOG.isDebugEnabled()) { - LOG.debug("Reset to ${if (toTheirs) "theirs" else "my"}") + LOG.debug(message) } val resetResult = repository.resetHard() @@ -33,7 +34,7 @@ class Reset(manager: GitRepositoryManager, indicator: ProgressIndicator) : Pull( indicator?.checkCanceled() - val commitMessage = commitMessageFormatter.message(if (toTheirs) "Overwrite local to ${manager.getUpstream()}" else "Overwrite remote ${manager.getUpstream()} to local") + val commitMessage = commitMessageFormatter.message(message) // grab added/deleted/renamed/modified files val mergeStrategy = if (toTheirs) MergeStrategy.THEIRS else MergeStrategy.OURS val firstMergeResult = pull(mergeStrategy, commitMessage) diff --git a/plugins/settings-repository/src/sync.kt b/plugins/settings-repository/src/sync.kt index 1e90e7bbe139..d677b477240e 100644 --- a/plugins/settings-repository/src/sync.kt +++ b/plugins/settings-repository/src/sync.kt @@ -59,7 +59,7 @@ class SyncManager(private val icsManager: IcsManager, private val autoSyncManage // well, we cannot commit? No problem, upcoming action must do something smart and solve the situation } catch (e: ProcessCanceledException) { - LOG.debug("Canceled") + LOG.warn("Canceled") return } catch (e: Throwable) { @@ -104,7 +104,7 @@ class SyncManager(private val icsManager: IcsManager, private val autoSyncManage return } catch (e: Throwable) { - if (e !is AuthenticationException && e !is NoRemoteRepositoryException) { + if (e !is AuthenticationException && e !is NoRemoteRepositoryException && e !is CannotResolveConflictInTestMode) { LOG.error(e) } exception = e @@ -188,4 +188,8 @@ enum class SyncType { MERGE, OVERWRITE_LOCAL, OVERWRITE_REMOTE -} \ No newline at end of file +} + +class NoRemoteRepositoryException(cause: Throwable) : RuntimeException(cause.getMessage(), cause) + +class CannotResolveConflictInTestMode() : RuntimeException() \ No newline at end of file diff --git a/plugins/settings-repository/testSrc/GitTest.kt b/plugins/settings-repository/testSrc/GitTest.kt index cd677eba4b2b..b9f307f7bfd3 100644 --- a/plugins/settings-repository/testSrc/GitTest.kt +++ b/plugins/settings-repository/testSrc/GitTest.kt @@ -17,9 +17,12 @@ package org.jetbrains.settingsRepository.test import com.intellij.mock.MockVirtualFileSystem import com.intellij.openapi.components.RoamingType +import com.intellij.openapi.components.impl.stores.StreamProvider import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vcs.merge.MergeSession import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.openapi.vfs.VirtualFile +import com.intellij.testFramework.path import com.intellij.util.PathUtilRt import org.assertj.core.api.Assertions.assertThat import org.eclipse.jgit.api.Git @@ -28,16 +31,24 @@ import org.jetbrains.jgit.dirCache.AddFile import org.jetbrains.jgit.dirCache.deletePath import org.jetbrains.jgit.dirCache.edit import org.jetbrains.jgit.dirCache.writePath -import org.jetbrains.settingsRepository.AM +import org.jetbrains.settingsRepository.CannotResolveConflictInTestMode import org.jetbrains.settingsRepository.SyncType +import org.jetbrains.settingsRepository.conflictResolver import org.jetbrains.settingsRepository.git.GitRepositoryManager import org.jetbrains.settingsRepository.git.commit import org.jetbrains.settingsRepository.git.computeIndexDiff import org.jetbrains.settingsRepository.git.resetHard import org.junit.Test import java.io.File +import java.util.Arrays import kotlin.properties.Delegates +// kotlin bug, cannot be val (.NoSuchMethodError: org.jetbrains.settingsRepository.SettingsRepositoryPackage.getMARKER_ACCEPT_MY()[B) +object AM { + val MARKER_ACCEPT_MY: ByteArray = "__accept my__".toByteArray() + val MARKER_ACCEPT_THEIRS: ByteArray = "__accept theirs__".toByteArray() +} + class GitTest : TestCase() { private val repositoryManager: GitRepositoryManager get() = icsManager.repositoryManager as GitRepositoryManager @@ -49,6 +60,28 @@ class GitTest : TestCase() { tempDirManager.createRepository("upstream") } + init { + conflictResolver = { files, mergeProvider -> + val mergeSession = mergeProvider.createMergeSession(files) + for (file in files) { + val mergeData = mergeProvider.loadRevisions(file) + if (Arrays.equals(mergeData.CURRENT, AM.MARKER_ACCEPT_MY) || Arrays.equals(mergeData.LAST, AM.MARKER_ACCEPT_THEIRS)) { + mergeSession.conflictResolvedForFile(file, MergeSession.Resolution.AcceptedYours) + } + else if (Arrays.equals(mergeData.CURRENT, AM.MARKER_ACCEPT_THEIRS) || Arrays.equals(mergeData.LAST, AM.MARKER_ACCEPT_MY)) { + mergeSession.conflictResolvedForFile(file, MergeSession.Resolution.AcceptedTheirs) + } + else if (Arrays.equals(mergeData.LAST, AM.MARKER_ACCEPT_MY)) { + file.setBinaryContent(mergeData.LAST!!) + mergeProvider.conflictResolvedForFile(file) + } + else { + throw CannotResolveConflictInTestMode() + } + } + } + } + private fun addAndCommit(path: String): FileInfo { val data = FileUtil.loadFileBytes(File(testDataPath, PathUtilRt.getFileName(path))) provider.save(path, data) @@ -284,15 +317,24 @@ class GitTest : TestCase() { Test fun `commit if unmerged`() { createLocalRepository(null) + provider.saveContent("remote.xml", "") + + try { + sync(SyncType.MERGE) + } + catch (e: CannotResolveConflictInTestMode) { + } + + // repository in unmerged state + conflictResolver = {files, mergeProvider -> + assertThat(files).hasSize(1) + assertThat(files.first().path).isEqualTo("remote.xml") + val mergeSession = mergeProvider.createMergeSession(files) + mergeSession.conflictResolvedForFile(files.first(), MergeSession.Resolution.AcceptedTheirs) + } sync(SyncType.MERGE) - provider.delete("remote.xml", RoamingType.PER_USER) - repositoryManager.commit() - - sync(SyncType.MERGE) - restoreRemoteAfterPush() - - compareFiles(fs()) + compareFiles(fs("remote.xml")) } // remote is uninitialized (empty - initial commit is not done) @@ -364,4 +406,9 @@ class GitTest : TestCase() { private fun sync(syncType: SyncType) { icsManager.sync(syncType, fixtureManager.projectFixture.getProject()) } +} + +fun StreamProvider.saveContent(fileSpec: String, content: String) { + val data = content.toByteArray() + saveContent(fileSpec, data, data.size(), RoamingType.PER_USER) } \ No newline at end of file