From 8a39c286a27dabcb61877a63410b4b71f1b9d48a Mon Sep 17 00:00:00 2001 From: Wanling Fu Date: Sun, 24 Aug 2025 19:31:55 +0100 Subject: [PATCH 01/12] feat: support committing local changes --- .../org/jabref/gui/git/GitCommitAction.java | 29 +++++ .../jabref/gui/git/GitCommitDialogView.java | 59 +++++++++ .../gui/git/GitCommitDialogViewModel.java | 122 ++++++++++++++++++ .../org/jabref/gui/git/GitCommitDialog.fxml | 31 +++++ 4 files changed, 241 insertions(+) create mode 100644 jabgui/src/main/java/org/jabref/gui/git/GitCommitAction.java create mode 100644 jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogView.java create mode 100644 jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogViewModel.java create mode 100644 jabgui/src/main/resources/org/jabref/gui/git/GitCommitDialog.fxml diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitCommitAction.java b/jabgui/src/main/java/org/jabref/gui/git/GitCommitAction.java new file mode 100644 index 00000000000..bb776ecc788 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/git/GitCommitAction.java @@ -0,0 +1,29 @@ +package org.jabref.gui.git; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.ActionHelper; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.preferences.GuiPreferences; + +public class GitCommitAction extends SimpleCommand { + + private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; + + public GitCommitAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) { + this.dialogService = dialogService; + this.stateManager = stateManager; + this.preferences = preferences; + + this.executable.bind(ActionHelper.needsDatabase(stateManager)); + } + + @Override + public void execute() { + dialogService.showCustomDialogAndWait( + new GitCommitDialogView() + ); + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogView.java b/jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogView.java new file mode 100644 index 00000000000..a339ef16738 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogView.java @@ -0,0 +1,59 @@ +package org.jabref.gui.git; + +import javafx.fxml.FXML; +import javafx.scene.control.ButtonType; +import javafx.scene.control.TextArea; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.util.BaseDialog; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.TaskExecutor; + +import com.airhacks.afterburner.views.ViewLoader; +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; +import jakarta.inject.Inject; + +public class GitCommitDialogView extends BaseDialog { + + @FXML private TextArea commitMessage; + @FXML private ButtonType commitButton; + + private GitCommitDialogViewModel viewModel; + + @Inject + private StateManager stateManager; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + @Inject + private TaskExecutor taskExecutor; + + private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); + + public GitCommitDialogView() { + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + } + + @FXML + private void initialize() { + setTitle(Localization.lang("Git Commit")); + this.viewModel = new GitCommitDialogViewModel(stateManager, dialogService, preferences, taskExecutor); + + commitMessage.textProperty().bindBidirectional(viewModel.commitMessageProperty()); + commitMessage.setPromptText(Localization.lang("Enter commit message here")); + + this.setResultConverter(button -> { + if (button != ButtonType.CANCEL) { + viewModel.commit(() -> this.close()); + } + return null; + }); + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogViewModel.java new file mode 100644 index 00000000000..1c13b51274a --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogViewModel.java @@ -0,0 +1,122 @@ +package org.jabref.gui.git; + +import java.nio.file.Path; +import java.util.Optional; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import org.jabref.gui.AbstractViewModel; +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.logic.JabRefException; +import org.jabref.logic.git.GitSyncService; +import org.jabref.logic.git.merge.GitSemanticMergeExecutorImpl; +import org.jabref.logic.git.util.GitHandlerRegistry; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.model.database.BibDatabaseContext; + +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; + +public class GitCommitDialogViewModel extends AbstractViewModel { + + private final StateManager stateManager; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final TaskExecutor taskExecutor; + + private final StringProperty commitMessage = new SimpleStringProperty(""); + private final BooleanProperty amend = new SimpleBooleanProperty(false); + + private final Validator commitMessageValidator; + + public GitCommitDialogViewModel( + StateManager stateManager, + DialogService dialogService, + GuiPreferences preferences, + TaskExecutor taskExecutor) { + this.stateManager = stateManager; + this.dialogService = dialogService; + this.preferences = preferences; + this.taskExecutor = taskExecutor; + + this.commitMessageValidator = new FunctionBasedValidator<>( + commitMessage, + message -> message != null && !message.trim().isEmpty(), + ValidationMessage.error(Localization.lang("Commit message cannot be empty")) + ); + } + + public void commit(Runnable onSuccess) { + commitTask() + .onSuccess(ignore -> { + dialogService.notify(Localization.lang("Committed successfully.")); + onSuccess.run(); + }) + .onFailure(ex -> + dialogService.showErrorDialogAndWait( + Localization.lang("Git Commit Failed"), + ex.getMessage(), + ex + ) + ) + .executeWith(taskExecutor); + } + + public BackgroundTask commitTask() { + return BackgroundTask.wrap(() -> { + Optional activeDatabaseOpt = stateManager.getActiveDatabase(); + if (activeDatabaseOpt.isEmpty()) { + throw new JabRefException(Localization.lang("No library open")); + } + + BibDatabaseContext dbContext = activeDatabaseOpt.get(); + Optional bibFilePathOpt = dbContext.getDatabasePath(); + if (bibFilePathOpt.isEmpty()) { + throw new JabRefException(Localization.lang("No library file path. Please save the library to a file first.")); + } + + Path bibFilePath = bibFilePathOpt.get(); + + GitHandlerRegistry registry = new GitHandlerRegistry(); + GitSyncService gitSyncService = new GitSyncService( + preferences.getImportFormatPreferences(), + registry, + null, + new GitSemanticMergeExecutorImpl(preferences.getImportFormatPreferences()) + ); + + boolean committed = gitSyncService.commitLocalChanges( + bibFilePath, + commitMessage.get().trim(), + amend.get() + ); + + if (!committed) { + throw new JabRefException(Localization.lang("Nothing to commit.")); + } + + return null; + }); + } + + public StringProperty commitMessageProperty() { + return commitMessage; + } + + public BooleanProperty amendProperty() { + return amend; + } + + public ValidationStatus commitMessageValidation() { + return commitMessageValidator.getValidationStatus(); + } +} diff --git a/jabgui/src/main/resources/org/jabref/gui/git/GitCommitDialog.fxml b/jabgui/src/main/resources/org/jabref/gui/git/GitCommitDialog.fxml new file mode 100644 index 00000000000..3fe7de19dc5 --- /dev/null +++ b/jabgui/src/main/resources/org/jabref/gui/git/GitCommitDialog.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + +