[git-index] add a commit message panel

GitOrigin-RevId: b8cc6b50a4c0634ec0d4630f79a48d0cda382272
This commit is contained in:
Julia Beliaeva
2020-05-09 05:25:10 +00:00
committed by intellij-monorepo-bot
parent a6f2c944cd
commit 8ab3f0e225
9 changed files with 232 additions and 11 deletions
@@ -16,6 +16,7 @@ vcs.revision.name.current=Current
message.text.could.not.load.file.content=Could Not Load File Content
confirmation.text.check.in.with.empty.comment=Commit with empty comment?
confirmation.title.check.in.with.empty.comment=Comment Is Empty
commit.message.placeholder=Commit Message
label.commit.comment=&Commit Message
border.standard.checkin.options.group=Before {0}
border.standard.after.checkin.options.group=After {0}
@@ -64,7 +64,13 @@ private val MAC_COMMIT_ACTION_SHORTCUT = CustomShortcutSet(getKeyStroke(KeyEvent
private fun panel(layout: LayoutManager): JBPanel<*> = JBPanel<JBPanel<*>>(layout)
private fun JBOptionButton.getBottomInset(): Int =
fun showEmptyCommitMessageConfirmation() = Messages.YES == Messages.showYesNoDialog(
message("confirmation.text.check.in.with.empty.comment"),
message("confirmation.title.check.in.with.empty.comment"),
Messages.getWarningIcon()
)
fun JBOptionButton.getBottomInset(): Int =
border?.getBorderInsets(this)?.bottom
?: (components.firstOrNull() as? JComponent)?.insets?.bottom
?: 0
@@ -118,7 +124,7 @@ open class ChangesViewCommitPanel(private val changesView: ChangesListView, priv
private val commitMessage = CommitMessage(project, false, false, true).apply {
editorField.addSettingsProvider { it.setBorder(emptyLeft(6)) }
editorField.setPlaceholder("Commit Message")
editorField.setPlaceholder(message("commit.message.placeholder"))
}
private val defaultCommitAction = object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) = fireDefaultExecutorCalled()
@@ -383,12 +389,7 @@ open class ChangesViewCommitPanel(private val changesView: ChangesListView, priv
override fun addInclusionListener(listener: InclusionListener, parent: Disposable) =
inclusionEventDispatcher.addListener(listener, parent)
override fun confirmCommitWithEmptyMessage(): Boolean =
Messages.YES == Messages.showYesNoDialog(
message("confirmation.text.check.in.with.empty.comment"),
message("confirmation.title.check.in.with.empty.comment"),
Messages.getWarningIcon()
)
override fun confirmCommitWithEmptyMessage(): Boolean = showEmptyCommitMessageConfirmation()
override fun startBeforeCommitChecks() = Unit
override fun endBeforeCommitChecks(result: CheckinHandler.ReturnResult) = Unit
@@ -563,6 +563,7 @@ stage.tree.node.unstaged=Not staged for commit
stage.tree.node.unmerged=Unmerged
stage.tree.node.untracked=Untracked
stage.tree.node.ignored=Ignored
stage.commit.process=Committing Staged Changes...
stage.loading.status=Loading changes...
branches.selected.branches.updated.title=Selected {0,choice,1#branch|2#branches} Updated
@@ -979,8 +979,8 @@ public class GitCheckinEnvironment implements CheckinEnvironment, AmendCommitAwa
return file;
}
private static void runWithMessageFile(@NotNull Project project, @NotNull VirtualFile root, @NotNull String message,
@NotNull ThrowableConsumer<? super File, ? extends VcsException> task) throws VcsException {
public static void runWithMessageFile(@NotNull Project project, @NotNull VirtualFile root, @NotNull String message,
@NotNull ThrowableConsumer<? super File, ? extends VcsException> task) throws VcsException {
File messageFile;
try {
messageFile = createCommitMessageFile(project, root, message);
@@ -0,0 +1,49 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package git4idea.index
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task.Backgroundable
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vfs.VirtualFile
import git4idea.checkin.GitCheckinEnvironment
import git4idea.commands.Git
import git4idea.commands.GitCommand
import git4idea.commands.GitLineHandler
import git4idea.i18n.GitBundle
import git4idea.repo.GitRepositoryManager
import java.io.File
private val LOG = Logger.getInstance("#git4idea.index.GitCommitUtil")
fun performCommit(project: Project, roots: Collection<VirtualFile>, commitMessage: String, amend: Boolean = false) {
object : Backgroundable(project, GitBundle.message("stage.commit.process")) {
override fun run(indicator: ProgressIndicator) {
for (root in roots) {
try {
GitCheckinEnvironment.runWithMessageFile(project, root, commitMessage) { commitMessageFile: File ->
doCommit(project, root, commitMessageFile, amend)
}
GitRepositoryManager.getInstance(project).getRepositoryForRoot(root)?.repositoryFiles?.refresh()
}
catch (e: VcsException) {
LOG.error("Error while committing $root", e)
}
}
}
}.queue()
}
@Throws(VcsException::class)
private fun doCommit(project: Project, root: VirtualFile, commitMessageFile: File, amend: Boolean) {
val handler = GitLineHandler(project, root, GitCommand.COMMIT)
handler.setStdoutSuppressed(false)
handler.addParameters("-F")
handler.addAbsoluteFile(commitMessageFile)
if (amend) {
handler.addParameters("--amend")
}
handler.endOptions()
Git.getInstance().runCommand(handler).getOutputOrThrow()
}
@@ -99,6 +99,16 @@ class GitStageTracker(val project: Project) : Disposable {
data class State(val gitState: Map<VirtualFile, List<GitFileStatus>>) {
private val roots: Collection<VirtualFile>
get() = gitState.keys
val stagedRoots: Set<VirtualFile>
get() {
return gitState.filterValues {
it.any { line -> line.getStagedStatus() != null }
}.keys
}
fun hasStagedRoots(): Boolean {
return gitState.values.flatten().any { it.getStagedStatus() != null }
}
fun updatedWith(newState: State, roots: Collection<VirtualFile> = this.roots.union(newState.roots)): State {
val gitState = hashMapOf<VirtualFile, List<GitFileStatus>>()
@@ -0,0 +1,121 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package git4idea.index.ui
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.CheckboxAction
import com.intellij.openapi.editor.colors.EditorColorsListener
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComponentContainer
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vcs.VcsBundle
import com.intellij.openapi.vcs.ui.CommitMessage
import com.intellij.ui.JBColor
import com.intellij.ui.components.JBOptionButton
import com.intellij.ui.components.JBPanel
import com.intellij.ui.components.panels.HorizontalLayout
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.components.BorderLayoutPanel
import com.intellij.vcs.commit.getBottomInset
import git4idea.i18n.GitBundle
import java.awt.Component
import java.awt.event.ActionEvent
import javax.swing.AbstractAction
import javax.swing.JButton
import javax.swing.JComponent
import javax.swing.SwingConstants
import javax.swing.border.Border
import javax.swing.border.EmptyBorder
abstract class GitCommitPanel(project: Project,
parent: Disposable) : BorderLayoutPanel(), EditorColorsListener, ComponentContainer, DataProvider {
var isAmend: Boolean = false
val commitMessage = CommitMessage(project, false, false, true)
val commitButton = CommitButton()
init {
addToCenter(buildPanel())
withPreferredHeight(85)
Disposer.register(this, commitMessage)
Disposer.register(parent, this)
}
private fun buildPanel(): Component {
val centerPanel = JBUI.Panels.simplePanel()
val amendActionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN,
DefaultActionGroup(AmendAction()), true)
amendActionToolbar.setTargetComponent(this)
amendActionToolbar.setReservePlaceAutoPopupIcon(false)
amendActionToolbar.component.isOpaque = false
amendActionToolbar.component.border = null
amendActionToolbar.component.background = getButtonPanelBackground()
val bottomPanel = JBPanel<JBPanel<*>>(HorizontalLayout(JBUI.scale(4), SwingConstants.CENTER))
bottomPanel.background = getButtonPanelBackground()
bottomPanel.border = getButtonPanelBorder()
bottomPanel.add(commitButton)
bottomPanel.add(amendActionToolbar.component)
commitMessage.editorField.addSettingsProvider { it.setBorder(JBUI.Borders.emptyLeft(6)) }
commitMessage.editorField.setPlaceholder(VcsBundle.message("commit.message.placeholder"))
centerPanel.addToCenter(commitMessage).addToBottom(bottomPanel)
return centerPanel
}
protected abstract fun performCommit()
protected abstract fun isFocused(): Boolean
private fun getButtonPanelBorder(): Border =
EmptyBorder(0, JBUI.scale(4), 0, 0)
private fun getButtonPanelBackground() =
JBColor { (commitMessage.editorField.editor as? EditorEx)?.backgroundColor ?: UIUtil.getTreeBackground() }
override fun globalSchemeChange(scheme: EditorColorsScheme?) {
// todo
}
override fun getComponent(): JComponent = this
override fun getPreferredFocusableComponent(): JComponent = commitMessage.editorField
override fun getData(dataId: String) = commitMessage.getData(dataId)
override fun dispose() {
}
private fun getCommitText(): String {
if (isAmend) return ActionsBundle.message("action.Vcs.ToggleAmendCommitMode.text")
else return GitBundle.getString("commit.action.name")
}
inner class CommitButton : JButton(object : AbstractAction(getCommitText()) {
override fun actionPerformed(e: ActionEvent) {
performCommit()
}
}) {
init {
background = getButtonPanelBackground()
}
override fun isDefaultButton(): Boolean = isFocused()
}
inner class AmendAction : CheckboxAction(VcsBundle.messagePointer("checkbox.amend")) {
override fun isSelected(e: AnActionEvent): Boolean = isAmend
override fun setSelected(e: AnActionEvent, state: Boolean) {
isAmend = state
commitButton.text = getCommitText()
}
}
}
@@ -8,8 +8,11 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vcs.changes.ui.ChangesTree
import com.intellij.openapi.vcs.changes.ui.TreeActionsToolbarPanel
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.ui.OnePixelSplitter
import com.intellij.ui.ScrollPaneFactory
import com.intellij.ui.SideBorder
import com.intellij.vcs.commit.showEmptyCommitMessageConfirmation
import com.intellij.vcs.log.ui.frame.ProgressStripe
import git4idea.i18n.GitBundle
import git4idea.index.GitStageTracker
@@ -24,6 +27,7 @@ internal class GitStagePanel(private val tracker: GitStageTracker, disposablePar
private val project = tracker.project
private val tree: GitStageTree
private val commitPanel: GitCommitPanel
private val progressStripe: ProgressStripe
private val state: GitStageTracker.State
@@ -31,6 +35,7 @@ internal class GitStagePanel(private val tracker: GitStageTracker, disposablePar
init {
tree = MyChangesTree(project)
commitPanel = MyGitCommitPanel()
val toolbarGroup = DefaultActionGroup()
toolbarGroup.add(ActionManager.getInstance().getAction("Git.Stage.Toolbar"))
@@ -43,8 +48,12 @@ internal class GitStagePanel(private val tracker: GitStageTracker, disposablePar
val scrolledTree = ScrollPaneFactory.createScrollPane(tree, SideBorder.TOP)
progressStripe = ProgressStripe(scrolledTree, this, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS)
val treeMessageSplitter = OnePixelSplitter(true, "git.stage.tree.message.splitter", 0.7f)
treeMessageSplitter.firstComponent = progressStripe
treeMessageSplitter.secondComponent = commitPanel
add(toolbar.component, BorderLayout.NORTH)
add(progressStripe, BorderLayout.CENTER)
add(treeMessageSplitter, BorderLayout.CENTER)
tracker.addListener(MyGitStageTrackerListener(), this)
if (tracker.isRefreshInProgress) {
@@ -56,8 +65,18 @@ internal class GitStagePanel(private val tracker: GitStageTracker, disposablePar
Disposer.register(disposableParent, this)
}
private fun performCommit(amend: Boolean) {
val rootsToCommit = state.stagedRoots
if (rootsToCommit.isEmpty()) return
if (commitPanel.commitMessage.text.isBlank() && !showEmptyCommitMessageConfirmation()) return
git4idea.index.performCommit(project, rootsToCommit, commitPanel.commitMessage.text, amend)
}
fun update() {
tree.update()
commitPanel.commitButton.isEnabled = state.hasStagedRoots()
}
override fun getData(dataId: String): Any? {
@@ -73,6 +92,16 @@ internal class GitStagePanel(private val tracker: GitStageTracker, disposablePar
get() = this@GitStagePanel.state
}
inner class MyGitCommitPanel : GitCommitPanel(project, this) {
override fun isFocused(): Boolean {
return IdeFocusManager.getInstance(project).getFocusedDescendantFor(this@GitStagePanel) != null
}
override fun performCommit() {
performCommit(isAmend)
}
}
inner class MyGitStageTrackerListener : GitStageTrackerListener {
override fun update() {
this@GitStagePanel.update()
@@ -352,6 +352,15 @@ public class GitRepositoryFiles {
return path.equals(myExcludePath);
}
/**
* Refresh all .git repository files asynchronously and recursively.
*
* @see #refreshTagsFiles() if you need the "main" data (branches, HEAD, etc.) to be updated synchronously.
*/
public void refresh() {
VfsUtil.markDirtyAndRefresh(true, true, false, myMainDir, myWorktreeDir);
}
/**
* Refresh that part of .git repository files, which is not covered by {@link GitRepository#update()}, e.g. the {@code refs/tags/} dir.
*