Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add git support #10586

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
10a4779
test: push
marcelojunior1 Sep 22, 2023
2c25b5f
fix build error
lbenicio Sep 23, 2023
91a12f1
test: push
marcelojunior1 Sep 22, 2023
1126bc2
fix build error
lbenicio Sep 23, 2023
f3ae12e
refactor
marcelojunior1 Sep 23, 2023
c312817
fix: commit and push
marcelojunior1 Sep 26, 2023
7ffe7f1
code refactoring
marcelojunior1 Sep 26, 2023
905eda9
add silent exception on git pull and push
lbenicio Sep 26, 2023
b104b99
add username and password git credentials input
lbenicio Sep 26, 2023
bf33ca0
fix style and git logic to add one file only
lbenicio Oct 2, 2023
375e689
add git preferences files
lbenicio Oct 2, 2023
d3a8014
inject dialog service
lbenicio Oct 3, 2023
a1e8ca0
add initial support to git credentials dialog view
lbenicio Oct 3, 2023
e4dd331
fix: git failed silently and style
marcelojunior1 Oct 3, 2023
4c81e54
fix git http push
lbenicio Oct 3, 2023
df3e4bb
refactor git credentials dialog to single dialog with two inputs
lbenicio Oct 4, 2023
298d86e
user and password menu without Keyring
marcelojunior1 Oct 7, 2023
6109741
refact credentials view to other file
lbenicio Oct 7, 2023
27b95bf
fix git pref panel
lbenicio Oct 8, 2023
e935882
user git preferences when pushing new commits
lbenicio Oct 8, 2023
344775b
remove custom method used for validation
lbenicio Oct 8, 2023
7d0b6db
git preferences and test
marcelojunior1 Oct 16, 2023
f3313b1
add unit test for commiting creating
lbenicio Oct 18, 2023
6686905
unit test git prefernces and credentials
lbenicio Oct 18, 2023
a102257
push without auth
marcelojunior1 Oct 19, 2023
a2a5a71
add test git push with credentials
lbenicio Oct 19, 2023
fdebffa
git push without write on disk
marcelojunior1 Oct 19, 2023
45cd433
fix: start server
marcelojunior1 Oct 23, 2023
35949a9
fix: contextPath and req name
marcelojunior1 Oct 23, 2023
72c7532
fix: pushSingleFile
marcelojunior1 Oct 23, 2023
c184846
code style
lbenicio Oct 25, 2023
6dfac3f
add changelog
lbenicio Oct 25, 2023
f4941a0
Merge branch 'main' into fix-578
lbenicio Oct 25, 2023
c2e5d74
cleanup code
lbenicio Oct 30, 2023
a6682a0
organize imports
lbenicio Oct 31, 2023
b0351ab
fix code style, add git preferences to control workflow, add files to…
lbenicio Nov 4, 2023
dff72dd
init developing resolve conflicts
lbenicio Nov 18, 2023
bbf1829
resolve all erros from file init
lbenicio Nov 18, 2023
a6021ea
Merge branch 'main' into fix-578
koppor Nov 21, 2023
6b8700f
fix build error
lbenicio Nov 21, 2023
cfdd3c8
fix: git key store
marcelojunior1 Nov 24, 2023
f9abb2c
fix: git key store
marcelojunior1 Nov 24, 2023
75bce3c
Style
marcelojunior1 Nov 24, 2023
596275a
conflict resolution with file monitor
marcelojunior1 Dec 14, 2023
ce2e1d3
fix: push when status is clean
marcelojunior1 Dec 14, 2023
18a3373
fix: comments and forceGitPull
marcelojunior1 Dec 14, 2023
4ebfd85
fix: set credentials on test and style
marcelojunior1 Jan 13, 2024
0379fad
Merge branch 'main' into fix-578
marcelojunior1 Jan 13, 2024
ceab32c
fix: keys in language file and mock on SaveDatabaseActionTest
marcelojunior1 Jan 14, 2024
873a5cc
Merge branch 'main' into fix-578
marcelojunior1 Jan 14, 2024
bc0fd86
fix: changelog
marcelojunior1 Jan 14, 2024
6b5d9e1
Update src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java
koppor Feb 28, 2024
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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"java.configuration.updateBuildConfiguration": "interactive",
"java.configuration.updateBuildConfiguration": "automatic",
"java.format.settings.url": "/config/VSCode Code Style.xml",
"java.checkstyle.configuration": "${workspaceFolder}/config/checkstyle/checkstyle_reviewdog.xml",
"java.checkstyle.version": "10.3.4"
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
### Added

- When importing entries form the "Citation relations" tab, the field [cites](https://docs.jabref.org/advanced/entryeditor/entrylinks) is now filled according to the relationship between the entries. [#10572](https://github.com/JabRef/jabref/pull/10752)
- We added git support for backing up bib files with integration to local and remote repositories and support for SSH and username/password authentication [#578](https://github.com/koppor/jabref/issues/578)

### Changed

Expand Down
11 changes: 11 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ dependencies {
antlr4 'org.antlr:antlr4:4.13.1'
implementation 'org.antlr:antlr4-runtime:4.13.1'

implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit.ssh.apache', version: '6.7.0.202309050840-r'
implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.8.0.202311291450-r'

implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1'
Expand Down Expand Up @@ -253,6 +254,12 @@ dependencies {
testImplementation "org.testfx:testfx-junit5:4.0.16-alpha"
testImplementation "org.hamcrest:hamcrest-library:2.2"

testImplementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit.http.server', version: '6.7.0.202309050840-r'
testImplementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.51.v20230217'
testImplementation group: 'org.eclipse.jetty', name: 'jetty-security', version: '9.4.51.v20230217'

testCompileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'

checkstyle 'com.puppycrawl.tools:checkstyle:10.12.5'
// xjc needs the runtime as well for the ant task, otherwise it fails
xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '3.0.2'
Expand Down Expand Up @@ -722,3 +729,7 @@ jmh {
iterations = 10
fork = 2
}

configurations.all {
exclude group: "commons-logging", module: "commons-logging"
}
1 change: 1 addition & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
requires com.sun.jna.platform;

requires org.eclipse.jgit;
requires org.eclipse.jgit.ssh.apache;
uses org.eclipse.jgit.transport.SshSessionFactory;
uses org.eclipse.jgit.lib.GpgSigner;

Expand Down
81 changes: 81 additions & 0 deletions src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.jabref.logic.exporter.BibtexDatabaseWriter;
import org.jabref.logic.exporter.SaveException;
import org.jabref.logic.exporter.SelfContainedSaveConfiguration;
import org.jabref.logic.git.GitHandler;
import org.jabref.logic.l10n.Encodings;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.pdf.search.PdfIndexerManager;
Expand All @@ -45,6 +46,11 @@
import org.jabref.model.metadata.SelfContainedSaveOrder;
import org.jabref.preferences.PreferencesService;

import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.DetachedHeadException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -239,11 +245,23 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) {

boolean success = saveDatabase(targetPath, false, encoding, BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, getSaveOrder());

if (preferences.getGitPreferences().getAutoSync()) {
success = automaticGitPull(targetPath);
}

if (preferences.getGitPreferences().getAutoCommit()) {
boolean commited = automaticGitCommit(targetPath);
if (commited && preferences.getGitPreferences().getAutoSync()) {
automaticGitPush(targetPath);
}
}

if (success) {
libraryTab.getUndoManager().markUnchanged();
libraryTab.resetChangedProperties();
}
dialogService.notify(Localization.lang("Library saved"));

return success;
} catch (SaveException ex) {
LOGGER.error(String.format("A problem occurred when trying to save the file %s", targetPath), ex);
Expand Down Expand Up @@ -316,4 +334,67 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset
}
}
}

/**
* @param filePath of library
* @return true on successful git pull
*/
public boolean automaticGitPull(Path filePath) {
GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences());
try {
git.pullOnCurrentBranch();
} catch (CheckoutConflictException e) {
git.forceGitPull();
dialogService.showErrorDialogAndWait(Localization.lang("Git"), Localization.lang("Local repository is out of date, please review the library and save again"));
LOGGER.info("Failed to pull");
return false;
} catch (NoRemoteRepositoryException e) {
Copy link
Member

Choose a reason for hiding this comment

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

I think, all exceptions can be merged into one, because there is no "real" different behavior.

NoRemoteRepositoryException | DetachedHeadException etc..

Please use notifications instead of dialogs. Reason: The flow of the user should not be disturbed while working with git.

dialogService.showErrorDialogAndWait(Localization.lang("Git"), Localization.lang("No remote repository detected"));
LOGGER.info("No remote repository detected");
} catch (DetachedHeadException e) {
dialogService.showErrorDialogAndWait(Localization.lang("Git"), Localization.lang("Git detached head"));
LOGGER.info("Git detached head");
Copy link
Member

Choose a reason for hiding this comment

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

Why not returning "false" if something goes wrong?

} catch (TransportException e) {
dialogService.showErrorDialogAndWait(Localization.lang("Git"), Localization.lang("Git credentials error"));
LOGGER.info("Git credentials error");
} catch (GitAPIException e) {
LOGGER.info("Failed to pull");
throw new RuntimeException(e);
Copy link
Member

Choose a reason for hiding this comment

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

Do not crash complete JabRef on an error.

} catch (IOException e) {
dialogService.showErrorDialogAndWait(Localization.lang("Git"), Localization.lang("Failed to open repository"));
LOGGER.info("Failed to open repository");
}
return true;
}

/**
* @param filePath of library
* @return true on successful git commit
*/
public boolean automaticGitCommit(Path filePath) {
GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences());
String automaticCommitMsg = "Automatic update via JabRef";
if (preferences.getGitPreferences().getAutoCommit()) {
try {
return git.createCommitWithSingleFileOnCurrentBranch(filePath.getFileName().toString(), automaticCommitMsg);
} catch (GitAPIException | IOException e) {
dialogService.showErrorDialogAndWait(Localization.lang("Git"), Localization.lang("Failed to open repository"));
LOGGER.info("Failed to open repository");
}
}
return false;
}

/**
* @param filePath of library
*/
public void automaticGitPush(Path filePath) {
GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences());
try {
git.pushCommitsToRemoteRepository();
} catch (IOException e) {
dialogService.showErrorDialogAndWait(Localization.lang("Git"), Localization.lang("Failed to push file in remote repository"));
LOGGER.info("Failed to push file in remote repository");
}
}
}
74 changes: 74 additions & 0 deletions src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.jabref.gui.git;

import javafx.fxml.FXML;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

import org.jabref.gui.DialogService;
import org.jabref.gui.util.BaseDialog;
import org.jabref.logic.git.GitCredential;
import org.jabref.logic.l10n.Localization;

import com.airhacks.afterburner.injection.Injector;

public class GitCredentialsDialogView extends BaseDialog<Void> {

@FXML private ButtonType copyVersionButton;
@FXML private TextArea textAreaVersions;
private DialogService dialogService;
private DialogPane pane;

private ButtonType acceptButton;
private ButtonType cancelButton;
private TextField inputGitUsername;
private PasswordField inputGitPassword;

public GitCredentialsDialogView() {
this.setTitle(Localization.lang("Git credentials"));
this.dialogService = Injector.instantiateModelOrService(DialogService.class);

this.pane = new DialogPane();
VBox vBox = new VBox();
this.inputGitUsername = new TextField();
this.inputGitPassword = new PasswordField();
this.acceptButton = new ButtonType(Localization.lang("Accept"), ButtonBar.ButtonData.APPLY);
this.cancelButton = new ButtonType(Localization.lang("Cancel"), ButtonBar.ButtonData.CANCEL_CLOSE);

vBox.getChildren().add(new Label(Localization.lang("Git username")));
vBox.getChildren().add(this.inputGitUsername);
vBox.getChildren().add(new Label(Localization.lang("Git password")));
vBox.getChildren().add(this.inputGitPassword);

this.pane.setContent(vBox);
}

public void showGitCredentialsDialog() {
dialogService.showCustomDialogAndWait(Localization.lang("Git credentials"), this.pane, this.acceptButton, this.cancelButton);
}

public GitCredential getCredentials() {
dialogService.showCustomDialogAndWait(Localization.lang("Git credentials"), this.pane, this.acceptButton, this.cancelButton);
GitCredential gitCredentials = new GitCredential(this.inputGitUsername.getText(), this.inputGitPassword.getText());

return gitCredentials;
}

public String getGitPassword() {
return this.inputGitPassword.getText();
}

public String getGitUsername() {
return this.inputGitUsername.getText();
}

@FXML
private void initialize() {
this.setResizable(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.jabref.gui.preferences.external.ExternalTab;
import org.jabref.gui.preferences.externalfiletypes.ExternalFileTypesTab;
import org.jabref.gui.preferences.general.GeneralTab;
import org.jabref.gui.preferences.git.GitTab;
import org.jabref.gui.preferences.groups.GroupsTab;
import org.jabref.gui.preferences.journals.JournalAbbreviationsTab;
import org.jabref.gui.preferences.keybindings.KeyBindingsTab;
Expand Down Expand Up @@ -81,7 +82,8 @@ public PreferencesDialogViewModel(DialogService dialogService, PreferencesServic
new XmpPrivacyTab(),
new CustomImporterTab(),
new CustomExporterTab(),
new NetworkTab()
new NetworkTab(),
new GitTab()
);
}

Expand Down
25 changes: 25 additions & 0 deletions src/main/java/org/jabref/gui/preferences/git/GitTab.fxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.CheckBox?>
<fx:root spacing="10.0" type="VBox"
xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jabref.gui.preferences.git.GitTab">
<Label styleClass="titleHeader" text="%Git"/>
<Label styleClass="sectionHeader" text="%Credential"/>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<Label text="%Email"/>
<TextField fx:id="username" minWidth="150.0" HBox.hgrow="ALWAYS"/>
<Label text="%Password"/>
<PasswordField fx:id="password" minWidth="150.0" HBox.hgrow="ALWAYS"/>
</HBox>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<CheckBox fx:id="autoCommit" text="%Enable git auto commit" />
</HBox>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<CheckBox fx:id="autoSync" text="%Enable git auto push and merge" />
</HBox>
</fx:root>
41 changes: 41 additions & 0 deletions src/main/java/org/jabref/gui/preferences/git/GitTab.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.jabref.gui.preferences.git;

import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;

import org.jabref.gui.preferences.AbstractPreferenceTabView;
import org.jabref.logic.l10n.Localization;

import com.airhacks.afterburner.views.ViewLoader;

public class GitTab extends AbstractPreferenceTabView<GitTabViewModel> {

@FXML private TextField username;
@FXML private PasswordField password;
@FXML private CheckBox autoCommit;
@FXML private CheckBox autoSync;

public GitTab() {
ViewLoader.view(this)
.root(this)
.load();
initialize();
}

@Override
public String getTabName() {
return Localization.lang("Git");
}

@FXML
private void initialize() {
viewModel = new GitTabViewModel(preferencesService.getGitPreferences());

username.textProperty().bindBidirectional(viewModel.getUsernameProperty());
password.textProperty().bindBidirectional(viewModel.getPasswordProperty());
autoCommit.selectedProperty().bindBidirectional(viewModel.getAutoCommitProperty());
autoSync.selectedProperty().bindBidirectional(viewModel.getAutoSyncProperty());
}
}
71 changes: 71 additions & 0 deletions src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.jabref.gui.preferences.git;

import java.util.List;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;

import org.jabref.gui.preferences.PreferenceTabViewModel;
import org.jabref.logic.git.GitPreferences;

public class GitTabViewModel implements PreferenceTabViewModel {
private final StringProperty username;
private final StringProperty password;
private final BooleanProperty autoCommit;
private final BooleanProperty autoSync;
private final GitPreferences gitPreferences;

public GitTabViewModel(GitPreferences gitPreferences) {
this.gitPreferences = gitPreferences;
this.autoCommit = gitPreferences.getAutoCommitProperty();
this.autoSync = gitPreferences.getAutoSyncProperty();
this.username = gitPreferences.getUsernameProperty();
this.password = gitPreferences.getPasswordProperty();
}

@Override
public void setValues() {
this.username.setValue(this.gitPreferences.getUsername());
this.password.setValue(this.gitPreferences.getPassword());
this.autoCommit.setValue(this.gitPreferences.getAutoCommit() || this.gitPreferences.getAutoSync());
this.autoSync.setValue(this.gitPreferences.getAutoSync());
}

@Override
public void storeSettings() {
}

@Override
public boolean validateSettings() {
return PreferenceTabViewModel.super.validateSettings();
}

@Override
public List<String> getRestartWarnings() {
return PreferenceTabViewModel.super.getRestartWarnings();
}

public String getUsername() {
return this.username.get();
}

public StringProperty getUsernameProperty() {
return this.username;
}

public String getPassword() {
return this.password.get();
}

public StringProperty getPasswordProperty() {
return this.password;
}

public BooleanProperty getAutoCommitProperty() {
return this.autoCommit;
}

public BooleanProperty getAutoSyncProperty() {
return this.autoSync;
}
}
Loading