Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.jabref.gui.externalfiles.AutoLinkFilesAction;
import org.jabref.gui.externalfiles.DownloadFullTextAction;
import org.jabref.gui.externalfiles.FindUnlinkedFilesAction;
import org.jabref.gui.git.GitCommitAction;
import org.jabref.gui.git.GitShareToGitHubAction;
import org.jabref.gui.help.AboutAction;
import org.jabref.gui.help.ErrorConsoleAction;
Expand Down Expand Up @@ -182,6 +183,8 @@ private void createMenu() {

// TODO: Should be only enabled if not yet shared.
factory.createSubMenu(StandardActions.GIT,
factory.createMenuItem(StandardActions.GIT_COMMIT, new GitCommitAction(dialogService, stateManager)),
new SeparatorMenuItem(),
factory.createMenuItem(StandardActions.GIT_SHARE, new GitShareToGitHubAction(dialogService, stateManager))
),

Expand Down
43 changes: 43 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/git/GitCommitAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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.logic.git.GitHandler;
import org.jabref.logic.git.status.GitStatusChecker;
import org.jabref.logic.l10n.Localization;

public class GitCommitAction extends SimpleCommand {

private final DialogService dialogService;
private final StateManager stateManager;

public GitCommitAction(DialogService dialogService, StateManager stateManager) {
this.dialogService = dialogService;
this.stateManager = stateManager;

this.executable.bind(ActionHelper.needsDatabase(stateManager));
}

@Override
public void execute() {
if (hasNothingToCommit()) {
dialogService.notify(Localization.lang("Nothing to commit."));
return;
}

dialogService.showCustomDialogAndWait(
new GitCommitDialogView()
);
}

private boolean hasNothingToCommit() {
return stateManager.getActiveDatabase()
.flatMap(context -> context.getDatabasePath())
.flatMap(GitHandler::fromAnyPath)
.map(GitStatusChecker::checkStatus)
.map(status -> !status.uncommittedChanges())
.orElse(true);
}
}
66 changes: 66 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.jabref.gui.git;

import javafx.application.Platform;
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.gui.util.IconValidationDecorator;
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<Void> {

@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, 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;
});

Platform.runLater(() -> {
visualizer.setDecoration(new IconValidationDecorator());
visualizer.initVisualization(viewModel.commitMessageValidation(), commitMessage, true);
});
}
}
132 changes: 132 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogViewModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package org.jabref.gui.git;

import java.io.IOException;
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.logic.JabRefException;
import org.jabref.logic.git.GitHandler;
import org.jabref.logic.git.status.GitStatusChecker;
import org.jabref.logic.git.status.GitStatusSnapshot;
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;
import org.eclipse.jgit.api.errors.GitAPIException;

public class GitCommitDialogViewModel extends AbstractViewModel {

private final StateManager stateManager;
private final DialogService dialogService;
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,
TaskExecutor taskExecutor) {
this.stateManager = stateManager;
this.dialogService = dialogService;
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(_-> {
dialogService.notify(Localization.lang("Committed successfully"));
onSuccess.run();
})
.onFailure(ex ->
dialogService.showErrorDialogAndWait(
Localization.lang("Git Commit Failed"),
ex.getMessage(),
ex
)
)
.executeWith(taskExecutor);
}

public BackgroundTask<Void> commitTask() {
return BackgroundTask.wrap(() -> {
doCommit();
return null;
});
}

private void doCommit() throws JabRefException, GitAPIException, IOException {
Optional<BibDatabaseContext> activeDatabaseOpt = stateManager.getActiveDatabase();
if (activeDatabaseOpt.isEmpty()) {
throw new JabRefException(Localization.lang("No library open"));
}

BibDatabaseContext dbContext = activeDatabaseOpt.get();
Optional<Path> 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();
Optional<Path> repoRootOpt = GitHandler.findRepositoryRoot(bibFilePath);
if (repoRootOpt.isEmpty()) {
throw new JabRefException(Localization.lang("Commit aborted: Path is not inside a Git repository."));
}

GitHandler gitHandler = registry.get(repoRootOpt.get());

GitStatusSnapshot status = GitStatusChecker.checkStatus(gitHandler);
if (!status.tracking()) {
throw new JabRefException(Localization.lang("Commit aborted: The file is not under Git version control."));
}
if (status.conflict()) {
throw new JabRefException(Localization.lang("Commit aborted: Local repository has unresolved merge conflicts."));
}

String message = commitMessage.get();
if (message == null || message.isBlank()) {
message = Localization.lang("Update references");
}

boolean committed = gitHandler.createCommitOnCurrentBranch(message, amend.get());
// TODO: Replace control-flow-by-exception with a proper control structure
if (!committed) {
throw new JabRefException(Localization.lang("Nothing to commit."));
}
Comment on lines +116 to +118
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is control flow using exceptions; but changes will take much time --> can be done as follow-up

}

public StringProperty commitMessageProperty() {
return commitMessage;
}

public BooleanProperty amendProperty() {
return amend;
}

public ValidationStatus commitMessageValidation() {
return commitMessageValidator.getValidationStatus();
}
}
31 changes: 31 additions & 0 deletions jabgui/src/main/resources/org/jabref/gui/git/GitCommitDialog.fxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>


<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ButtonType?>
<?import javafx.scene.control.DialogPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.VBox?>
<DialogPane
xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jabref.gui.git.GitCommitDialogView"
prefHeight="400.0"
prefWidth="600.0">
<content>
<VBox spacing="15">
<padding>
<Insets top="20" right="20" bottom="20" left="20"/>
</padding>

<TextArea fx:id="commitMessage"
prefRowCount="6"
wrapText="true"
VBox.vgrow="ALWAYS"/>
</VBox>
</content>
<buttonTypes>
<ButtonType fx:id="commitButton" text="%Commit" buttonData="OK_DONE"/>
<ButtonType fx:constant="CANCEL"/>
</buttonTypes>
</DialogPane>
10 changes: 10 additions & 0 deletions jablib/src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3364,3 +3364,13 @@ Unexpected\ error\:\ %0=Unexpected error: %0
Create\ an\ empty\ repository\ on\ GitHub,\ then\ copy\ the\ HTTPS\ URL\ (ends\ with\ .git).\ Click\ to\ open\ GitHub.=Create an empty repository on GitHub, then copy the HTTPS URL (ends with .git). Click to open GitHub.
Merge\ completed\ with\ conflicts.=Merge completed with conflicts.
Successfully\ merged\ and\ updated.=Successfully merged and updated.
Commit\ message\ cannot\ be\ empty=Commit message cannot be empty
Committed\ successfully=Committed successfully
Enter\ commit\ message\ here=Enter commit message here
Git\ Commit=Git Commit
Git\ Commit\ Failed=Git Commit Failed
Nothing\ to\ commit.=Nothing to commit.
Commit\ aborted\:\ Local\ repository\ has\ unresolved\ merge\ conflicts.=Commit aborted: Local repository has unresolved merge conflicts.
Commit\ aborted\:\ Path\ is\ not\ inside\ a\ Git\ repository.=Commit aborted: Path is not inside a Git repository.
Commit\ aborted\:\ The\ file\ is\ not\ under\ Git\ version\ control.=Commit aborted: The file is not under Git version control.
Update\ references=Update references