mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[git] IJPL-213010 Use reset --keep instead of update-ref in operations where the commit tree of a new head might change
(cherry picked from commit 7f50e16413efb27c2d45abf7be1bc944d5d91eb3) IJ-CR-181037 GitOrigin-RevId: 26cdfa96dea406d5441c42f08a05eb2960edd59f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e7713d68c7
commit
cc191b2b3d
@@ -99,9 +99,9 @@ public final class GitUtil {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public static void updateHead(@NotNull GitRepository repository,
|
||||
@NotNull Hash newObjectId,
|
||||
@Nullable String reflogMessage) throws VcsException {
|
||||
public static void updateHeadReference(@NotNull GitRepository repository,
|
||||
@NotNull Hash newObjectId,
|
||||
@Nullable String reflogMessage) throws VcsException {
|
||||
Git.getInstance().updateReference(repository, HEAD, newObjectId, reflogMessage).throwOnError();
|
||||
}
|
||||
|
||||
|
||||
@@ -161,8 +161,18 @@ public interface Git {
|
||||
@NotNull String newName,
|
||||
GitLineHandlerListener @NotNull ... listeners);
|
||||
|
||||
default @NotNull GitCommandResult reset(@NotNull GitRepository repository,
|
||||
@NotNull GitResetMode mode,
|
||||
@NotNull String target,
|
||||
GitLineHandlerListener @NotNull ... listeners) {
|
||||
return reset(repository, mode, target, null, listeners);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
GitCommandResult reset(@NotNull GitRepository repository, @NotNull GitResetMode mode, @NotNull String target,
|
||||
GitCommandResult reset(@NotNull GitRepository repository,
|
||||
@NotNull GitResetMode mode,
|
||||
@NotNull String target,
|
||||
@Nullable String reflogMessage,
|
||||
GitLineHandlerListener @NotNull ... listeners);
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -88,6 +88,11 @@ public final class GitCommand {
|
||||
*/
|
||||
public static final @NonNls String IJ_HANDLER_MARKER_ENV = "INTELLIJ_GIT_EXECUTABLE";
|
||||
|
||||
/**
|
||||
* Environment variable that allows to specify the descriptive text written to the reflog
|
||||
*/
|
||||
public static final @NonNls String GIT_REFLOG_ACTION_ENV = "GIT_REFLOG_ACTION";
|
||||
|
||||
@ApiStatus.Internal
|
||||
public enum LockingPolicy {
|
||||
READ,
|
||||
|
||||
@@ -445,23 +445,32 @@ public class GitImpl extends GitImplBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull GitCommandResult reset(@NotNull GitRepository repository, @NotNull GitResetMode mode, @NotNull String target,
|
||||
public @NotNull GitCommandResult reset(@NotNull GitRepository repository,
|
||||
@NotNull GitResetMode mode,
|
||||
@NotNull String target,
|
||||
@Nullable String reflogMessage,
|
||||
GitLineHandlerListener @NotNull ... listeners) {
|
||||
return reset(repository, mode.getArgument(), target, listeners);
|
||||
return reset(repository, mode.getArgument(), target, reflogMessage, listeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision) {
|
||||
return reset(repository, "--merge", revision);
|
||||
return reset(repository, "--merge", revision, null);
|
||||
}
|
||||
|
||||
private @NotNull GitCommandResult reset(@NotNull GitRepository repository, @NotNull String argument, @Nullable String target,
|
||||
private @NotNull GitCommandResult reset(@NotNull GitRepository repository,
|
||||
@NotNull String argument,
|
||||
@Nullable String target,
|
||||
@Nullable String reflogMessage,
|
||||
GitLineHandlerListener @NotNull ... listeners) {
|
||||
final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET);
|
||||
handler.addParameters(argument);
|
||||
if (target != null) {
|
||||
handler.addParameters(target);
|
||||
}
|
||||
if (reflogMessage != null) {
|
||||
handler.addCustomEnvironmentVariable(GitCommand.GIT_REFLOG_ACTION_ENV, reflogMessage);
|
||||
}
|
||||
addListeners(handler, listeners);
|
||||
return runCommand(handler);
|
||||
}
|
||||
|
||||
+2
-1
@@ -67,7 +67,8 @@ internal class GitInMemoryInteractiveRebaseProcess(
|
||||
baseCommit = objectRepo.findCommit(objectRepo.commitTree(objectRepo.emptyTree.oid, listOf(), byteArrayOf()))
|
||||
}
|
||||
|
||||
return CommitEditingResult(baseCommit.oid)
|
||||
val modifiesTree = baseCommit.treeOid != baseToHeadCommitsRange.last().treeOid
|
||||
return CommitEditingResult(baseCommit.oid, requiresWorkingTreeUpdate = modifiesTree)
|
||||
}
|
||||
|
||||
private fun processEntry(
|
||||
|
||||
+46
-16
@@ -5,8 +5,10 @@ import com.intellij.openapi.application.ApplicationNamesInfo
|
||||
import com.intellij.openapi.vcs.VcsException
|
||||
import com.intellij.openapi.vcs.VcsNotifier
|
||||
import com.intellij.vcs.log.VcsCommitMetadata
|
||||
import com.intellij.vcs.log.impl.HashImpl
|
||||
import git4idea.GitNotificationIdsHolder
|
||||
import git4idea.GitUtil
|
||||
import git4idea.commands.Git
|
||||
import git4idea.i18n.GitBundle
|
||||
import git4idea.inMemory.GitObjectRepository
|
||||
import git4idea.inMemory.findCommitsRange
|
||||
@@ -15,16 +17,17 @@ import git4idea.inMemory.objects.Oid
|
||||
import git4idea.inMemory.objects.toHash
|
||||
import git4idea.rebase.interactive.getRebaseUpstreamFor
|
||||
import git4idea.rebase.log.GitCommitEditingOperationResult
|
||||
import git4idea.reset.GitResetMode
|
||||
import org.jetbrains.annotations.NonNls
|
||||
|
||||
internal abstract class GitInMemoryCommitEditingOperation(
|
||||
protected val objectRepo: GitObjectRepository,
|
||||
private val baseCommitMetadata: VcsCommitMetadata,
|
||||
) {
|
||||
companion object {
|
||||
@NonNls
|
||||
private val REFLOG_MESSAGE_SUFFIX = "by ${ApplicationNamesInfo.getInstance().fullProductName} Git plugin"
|
||||
}
|
||||
protected abstract suspend fun editCommits(): CommitEditingResult
|
||||
|
||||
protected abstract val reflogMessage: @NonNls String
|
||||
protected abstract val failureTitle: @NonNls String
|
||||
|
||||
protected lateinit var initialHeadPosition: String
|
||||
|
||||
@@ -42,9 +45,14 @@ internal abstract class GitInMemoryCommitEditingOperation(
|
||||
try {
|
||||
val result = editCommits()
|
||||
assertCurrentRevMatchesInitialHead()
|
||||
GitUtil.updateHead(objectRepo.repository,
|
||||
result.newHead.toHash(),
|
||||
"$reflogMessage $REFLOG_MESSAGE_SUFFIX")
|
||||
|
||||
if (result.requiresWorkingTreeUpdate) {
|
||||
resetToNewHead(result.newHead)
|
||||
}
|
||||
else {
|
||||
updateRefToNewHead(result.newHead)
|
||||
}
|
||||
|
||||
objectRepo.repository.update()
|
||||
val upstream = getRebaseUpstreamFor(baseCommitMetadata)
|
||||
|
||||
@@ -57,10 +65,26 @@ internal abstract class GitInMemoryCommitEditingOperation(
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract suspend fun editCommits(): CommitEditingResult
|
||||
private fun updateRefToNewHead(newHead: Oid) {
|
||||
GitUtil.updateHeadReference(objectRepo.repository,
|
||||
newHead.toHash(),
|
||||
fullReflogMessage)
|
||||
}
|
||||
|
||||
protected abstract val reflogMessage: @NonNls String
|
||||
protected abstract val failureTitle: @NonNls String
|
||||
/**
|
||||
* Both index and working tree are updated on files that are different between current and new head
|
||||
* If some of these files also have local changes, reset fails
|
||||
*/
|
||||
private fun resetToNewHead(newHead: Oid) {
|
||||
Git.getInstance().reset(objectRepo.repository,
|
||||
GitResetMode.KEEP,
|
||||
newHead.hex(),
|
||||
fullReflogMessage).throwOnError()
|
||||
GitUtil.refreshChangedVfs(objectRepo.repository, HashImpl.build(initialHeadPosition))
|
||||
}
|
||||
|
||||
private val fullReflogMessage
|
||||
get() = "$reflogMessage $REFLOG_MESSAGE_SUFFIX"
|
||||
|
||||
private fun notifyOperationFailed(exception: VcsException) {
|
||||
VcsNotifier.getInstance(objectRepo.repository.project).notifyError(
|
||||
@@ -70,12 +94,6 @@ internal abstract class GitInMemoryCommitEditingOperation(
|
||||
)
|
||||
}
|
||||
|
||||
protected data class CommitEditingResult(
|
||||
val newHead: Oid,
|
||||
val commitToFocus: Oid? = null,
|
||||
val commitToFocusOnUndo: Oid? = null,
|
||||
)
|
||||
|
||||
protected fun assertCurrentRevMatchesInitialHead(performUpdate: Boolean = true) {
|
||||
if (performUpdate) {
|
||||
objectRepo.repository.update()
|
||||
@@ -85,4 +103,16 @@ internal abstract class GitInMemoryCommitEditingOperation(
|
||||
throw VcsException(GitBundle.message("in.memory.rebase.fail.head.move"))
|
||||
}
|
||||
}
|
||||
|
||||
protected data class CommitEditingResult(
|
||||
val newHead: Oid,
|
||||
val requiresWorkingTreeUpdate: Boolean,
|
||||
val commitToFocus: Oid? = null,
|
||||
val commitToFocusOnUndo: Oid? = null,
|
||||
)
|
||||
|
||||
companion object {
|
||||
@NonNls
|
||||
private val REFLOG_MESSAGE_SUFFIX = "by ${ApplicationNamesInfo.getInstance().fullProductName} Git plugin"
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -61,7 +61,8 @@ internal class GitExtractSelectedChangesOperation(
|
||||
message = newMessage.toByteArray())
|
||||
val newHead = objectRepo.chainCommits(secondCommit, baseToHeadCommitsRange.drop(1))
|
||||
LOG.info("Finish computing new head for extract operation")
|
||||
return CommitEditingResult(newHead, secondCommit, targetCommit.oid)
|
||||
|
||||
return CommitEditingResult(newHead, requiresWorkingTreeUpdate = false, commitToFocus = secondCommit, commitToFocusOnUndo = targetCommit.oid)
|
||||
}
|
||||
|
||||
private fun splitTreeByPaths(
|
||||
|
||||
+1
-1
@@ -30,6 +30,6 @@ internal class GitInMemoryRewordOperation(
|
||||
val newHead = objectRepo.chainCommits(rewordedTargetCommit, baseToHeadCommitsRange.drop(1))
|
||||
|
||||
LOG.info("Finish computing new head for reword operation")
|
||||
return CommitEditingResult(newHead, rewordedTargetCommit)
|
||||
return CommitEditingResult(newHead, requiresWorkingTreeUpdate = false, commitToFocus = rewordedTargetCommit)
|
||||
}
|
||||
}
|
||||
+66
-2
@@ -197,7 +197,7 @@ internal class GitInMemoryInteractiveRebaseProcessTest : GitInMemoryOperationTes
|
||||
}
|
||||
assertCommitted(3) {
|
||||
added("src/main/java/service/UserService.java", "public class UserService {}")
|
||||
added("src/main/java/model/User.java", "public class User {}")
|
||||
added("src/main/java/model/User.java", "public class User {}")
|
||||
added("config/application.yml", "server:\n port: 8080")
|
||||
}
|
||||
assertCommitted(4) {
|
||||
@@ -444,4 +444,68 @@ internal class GitInMemoryInteractiveRebaseProcessTest : GitInMemoryOperationTes
|
||||
assertEquals("Commit hashes should not change for commits that were not modified", listOf(commitAHashBefore, commitBHashBefore),
|
||||
listOf(commitAHashAfter, commitBHashAfter))
|
||||
}
|
||||
}
|
||||
|
||||
fun `test drop commit that adds two files updates working tree and index`() {
|
||||
file("a.txt").create("content a").add()
|
||||
val firstCommit = commitDetails(commit("Add a"))
|
||||
|
||||
file("a.txt").write("local modified content")
|
||||
|
||||
file("b.txt").create("content b").add()
|
||||
file("c.txt").create("content c").add()
|
||||
commit("Add b, c")
|
||||
|
||||
file("a.txt").add()
|
||||
|
||||
logData.refreshAndWait(repo, true)
|
||||
updateChangeListManager()
|
||||
|
||||
val entries = getEntriesUsingLog(repo, firstCommit, logData)
|
||||
val model = convertToModel(entries)
|
||||
|
||||
model.drop(listOf(1)) // drop "Add b, c" commit
|
||||
|
||||
val validationResult = GitInMemoryRebaseData.createValidatedRebaseData(model, firstCommit, entries.last().commitDetails.id) as GitInMemoryRebaseData.Companion.ValidationResult.Valid
|
||||
|
||||
GitInMemoryInteractiveRebaseProcess(objectRepo, validationResult.rebaseData).run() as GitCommitEditingOperationResult.Complete
|
||||
|
||||
repo.assertLatestHistory(
|
||||
"Add a",
|
||||
"initial"
|
||||
)
|
||||
|
||||
updateChangeListManager()
|
||||
|
||||
assertEquals("local modified content", file("a.txt").read())
|
||||
file("b.txt").assertNotExists()
|
||||
file("c.txt").assertNotExists()
|
||||
}
|
||||
|
||||
fun `test drop commit fails when local changes would be overwritten`() {
|
||||
file("a.txt").create("content a").add()
|
||||
val firstCommit = commitDetails(commit("Add a"))
|
||||
file("b.txt").create("content b").addCommit("Add b")
|
||||
|
||||
file("b.txt").write("local modified content")
|
||||
|
||||
logData.refreshAndWait(repo, true)
|
||||
updateChangeListManager()
|
||||
|
||||
val entries = getEntriesUsingLog(repo, firstCommit, logData)
|
||||
val model = convertToModel(entries)
|
||||
|
||||
model.drop(listOf(1))
|
||||
|
||||
val validationResult = GitInMemoryRebaseData.createValidatedRebaseData(model, firstCommit, entries.last().commitDetails.id) as GitInMemoryRebaseData.Companion.ValidationResult.Valid
|
||||
|
||||
val lastCommitHashBefore = repo.last()
|
||||
val result = GitInMemoryInteractiveRebaseProcess(objectRepo, validationResult.rebaseData).run()
|
||||
|
||||
assertTrue(result is GitCommitEditingOperationResult.Incomplete)
|
||||
|
||||
val lastCommitHashAfter = repo.last()
|
||||
assertEquals(lastCommitHashBefore, lastCommitHashAfter)
|
||||
file("b.txt").assertExists()
|
||||
assertEquals("local modified content", file("b.txt").read())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user