From 10a4779e25f1dbb7e9fed81585006b78897bd986 Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Thu, 21 Sep 2023 21:40:31 -0300 Subject: [PATCH 01/48] test: push --- .../gui/exporter/SaveDatabaseAction.java | 5 ++ .../gui/exporter/SaveGitDatabaseAction.java | 20 +++++++ .../org/jabref/logic/git/MyGitHandler.java | 57 +++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java create mode 100644 src/main/java/org/jabref/logic/git/MyGitHandler.java diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 87cb097b734..ee465eb04b2 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -243,6 +243,11 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { libraryTab.resetChangedProperties(); } dialogService.notify(Localization.lang("Library saved")); + + if (success) { + SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath.getParent()); + saveGit.automaticUpdate(); + } return success; } catch (SaveException ex) { LOGGER.error(String.format("A problem occurred when trying to save the file %s", targetPath), ex); diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java new file mode 100644 index 00000000000..c04e8e6222e --- /dev/null +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -0,0 +1,20 @@ +package org.jabref.gui.exporter; + +import java.nio.file.Path; + +import org.jabref.logic.git.MyGitHandler; + +public class SaveGitDatabaseAction { + final Path repositoryPath; + final String automaticCommitMsg = "Automatic update via JabRef"; + + public SaveGitDatabaseAction(Path repositoryPath) { + this.repositoryPath = repositoryPath; + } + + public boolean automaticUpdate() { + MyGitHandler git = new MyGitHandler(repositoryPath); + git.createCommitOnCurrentBranch(automaticCommitMsg, false); + return true; + } +} diff --git a/src/main/java/org/jabref/logic/git/MyGitHandler.java b/src/main/java/org/jabref/logic/git/MyGitHandler.java new file mode 100644 index 00000000000..84d5b3a2876 --- /dev/null +++ b/src/main/java/org/jabref/logic/git/MyGitHandler.java @@ -0,0 +1,57 @@ +package org.jabref.logic.git; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.RmCommand; +import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.api.errors.GitAPIException; + +public class MyGitHandler { + final Path repositoryPath; + final File repositoryPathAsFile; + public MyGitHandler(Path repositoryPath) { + this.repositoryPath = repositoryPath; + this.repositoryPathAsFile = this.repositoryPath.toFile(); + // TODO: init .git + } + + boolean isLocalGitRepository() { + Path gitFolderPath = Path.of(repositoryPath.toString(), ".git"); + return Files.exists(gitFolderPath) && Files.isDirectory(gitFolderPath); + } + + public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) { + boolean commitCreated = false; + try (Git git = Git.open(this.repositoryPathAsFile)) { + Status status = git.status().call(); + if (!status.isClean()) { + commitCreated = true; + // Add new and changed files to index + git.add() + .addFilepattern(".") + .call(); + // Add all removed files to index + if (!status.getMissing().isEmpty()) { + RmCommand removeCommand = git.rm() + .setCached(true); + status.getMissing().forEach(removeCommand::addFilepattern); + removeCommand.call(); + } + git.commit() + .setAmend(amend) + .setAllowEmpty(false) + .setMessage(commitMessage) + .call(); + } + } catch ( + IOException | + GitAPIException e) { + throw new RuntimeException(e); + } + return commitCreated; + } +} From 2c25b5f684e5538ed3a0569eaab2dacc72cd694a Mon Sep 17 00:00:00 2001 From: Leonardo Benicio Date: Sat, 23 Sep 2023 16:51:56 -0300 Subject: [PATCH 02/48] fix build error --- .vscode/settings.json | 2 +- build.gradle | 1 + src/main/java/module-info.java | 2 + .../gui/exporter/SaveGitDatabaseAction.java | 1 + .../org/jabref/logic/git/MyGitHandler.java | 25 +++++++++ .../logic/git/SshTransportConfigCallback.java | 51 +++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java diff --git a/.vscode/settings.json b/.vscode/settings.json index 2094775de07..0daf1453fa0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" diff --git a/build.gradle b/build.gradle index ba9434b63f4..3e24383203a 100644 --- a/build.gradle +++ b/build.gradle @@ -141,6 +141,7 @@ dependencies { implementation 'org.antlr:antlr4-runtime:4.13.1' implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.7.0.202309050840-r' + implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit.ssh.jsch', version: '6.7.0.202309050840-r' implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.15.2' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.15.2' diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index bdb442a3f75..a85af9ad644 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -143,4 +143,6 @@ requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; requires de.saxsys.mvvmfx.validation; + requires jsch; + requires org.eclipse.jgit.ssh.jsch; } diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index c04e8e6222e..a0d4b318f3a 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -15,6 +15,7 @@ public SaveGitDatabaseAction(Path repositoryPath) { public boolean automaticUpdate() { MyGitHandler git = new MyGitHandler(repositoryPath); git.createCommitOnCurrentBranch(automaticCommitMsg, false); + return true; } } diff --git a/src/main/java/org/jabref/logic/git/MyGitHandler.java b/src/main/java/org/jabref/logic/git/MyGitHandler.java index 84d5b3a2876..0583de2122a 100644 --- a/src/main/java/org/jabref/logic/git/MyGitHandler.java +++ b/src/main/java/org/jabref/logic/git/MyGitHandler.java @@ -8,11 +8,17 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.api.TransportConfigCallback; import org.eclipse.jgit.api.errors.GitAPIException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class MyGitHandler { final Path repositoryPath; final File repositoryPathAsFile; + static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); + public MyGitHandler(Path repositoryPath) { this.repositoryPath = repositoryPath; this.repositoryPathAsFile = this.repositoryPath.toFile(); @@ -54,4 +60,23 @@ public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) } return commitCreated; } + + /** + * Pushes all commits made to the branch that is tracked by the currently checked out branch. + * If pushing to remote fails, it fails silently. + */ + public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { + TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback( + "-----BEGIN RSA PRIVATE KEY-----\n" + + // PRIVATE KEY + "-----END RSA PRIVATE KEY-----", + "PUBLIC KEY"); + + Git git = Git.open(this.repositoryPathAsFile); + git.verifySignature(); + git.push() + .setTransportConfigCallback(transportConfigCallback) + .call(); + LOGGER.info("OK to push"); + } } diff --git a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java new file mode 100644 index 00000000000..7bbf649c9c3 --- /dev/null +++ b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java @@ -0,0 +1,51 @@ +package org.jabref.logic.git; + +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import org.eclipse.jgit.api.TransportConfigCallback; +import org.eclipse.jgit.transport.SshSessionFactory; +import org.eclipse.jgit.transport.SshTransport; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.ssh.jsch.JschConfigSessionFactory; +import org.eclipse.jgit.transport.ssh.jsch.OpenSshConfig; +import org.eclipse.jgit.util.FS; + +/** + * A custom TransportConfigCallback class that loads private key and public key from the provided strings in constructor. + * An instance of this class will be used as follows: + * TransportConfigCallback = new SshTransportConfigCallback(PVT_KEY_STRING, PUB_KEY_STRING); + * Git.open(gitRepoDirFile) // gitRepoDirFile is an instance of File + * .push() + * .setTransportConfigCallback(transportConfigCallback) + * .call(); + */ +public class SshTransportConfigCallback implements TransportConfigCallback { + private final String privateKey; + private final String publicKey; + + public SshTransportConfigCallback(String privateKey, String publicKey) { + this.privateKey = privateKey; + this.publicKey = publicKey; + } + + private final SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() { + @Override + protected void configure(OpenSshConfig.Host hc, Session session) { + session.setConfig("StrictHostKeyChecking", "no"); + } + + @Override + protected JSch createDefaultJSch(FS fs) throws JSchException { + JSch jSch = super.createDefaultJSch(fs); + jSch.addIdentity("/home/null/.ssh/id_rsa", privateKey.getBytes(), publicKey.getBytes(), "".getBytes()); + return jSch; + } + }; + + @Override + public void configure(Transport transport) { + SshTransport sshTransport = (SshTransport) transport; + sshTransport.setSshSessionFactory(sshSessionFactory); + } +} From 91a12f1342716f54c4614ee03688afa1828f8b1e Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Thu, 21 Sep 2023 21:40:31 -0300 Subject: [PATCH 03/48] test: push --- .../gui/exporter/SaveGitDatabaseAction.java | 1 - .../org/jabref/logic/git/MyGitHandler.java | 25 ------------------- 2 files changed, 26 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index a0d4b318f3a..c04e8e6222e 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -15,7 +15,6 @@ public SaveGitDatabaseAction(Path repositoryPath) { public boolean automaticUpdate() { MyGitHandler git = new MyGitHandler(repositoryPath); git.createCommitOnCurrentBranch(automaticCommitMsg, false); - return true; } } diff --git a/src/main/java/org/jabref/logic/git/MyGitHandler.java b/src/main/java/org/jabref/logic/git/MyGitHandler.java index 0583de2122a..84d5b3a2876 100644 --- a/src/main/java/org/jabref/logic/git/MyGitHandler.java +++ b/src/main/java/org/jabref/logic/git/MyGitHandler.java @@ -8,17 +8,11 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; -import org.eclipse.jgit.api.TransportConfigCallback; import org.eclipse.jgit.api.errors.GitAPIException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class MyGitHandler { final Path repositoryPath; final File repositoryPathAsFile; - static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); - public MyGitHandler(Path repositoryPath) { this.repositoryPath = repositoryPath; this.repositoryPathAsFile = this.repositoryPath.toFile(); @@ -60,23 +54,4 @@ public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) } return commitCreated; } - - /** - * Pushes all commits made to the branch that is tracked by the currently checked out branch. - * If pushing to remote fails, it fails silently. - */ - public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { - TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback( - "-----BEGIN RSA PRIVATE KEY-----\n" + - // PRIVATE KEY - "-----END RSA PRIVATE KEY-----", - "PUBLIC KEY"); - - Git git = Git.open(this.repositoryPathAsFile); - git.verifySignature(); - git.push() - .setTransportConfigCallback(transportConfigCallback) - .call(); - LOGGER.info("OK to push"); - } } From 1126bc297076be013e0f2b80c37a72d88ea4b07a Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Sat, 23 Sep 2023 16:51:56 -0300 Subject: [PATCH 04/48] fix build error --- .../gui/exporter/SaveGitDatabaseAction.java | 1 + .../org/jabref/logic/git/MyGitHandler.java | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index c04e8e6222e..a0d4b318f3a 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -15,6 +15,7 @@ public SaveGitDatabaseAction(Path repositoryPath) { public boolean automaticUpdate() { MyGitHandler git = new MyGitHandler(repositoryPath); git.createCommitOnCurrentBranch(automaticCommitMsg, false); + return true; } } diff --git a/src/main/java/org/jabref/logic/git/MyGitHandler.java b/src/main/java/org/jabref/logic/git/MyGitHandler.java index 84d5b3a2876..0583de2122a 100644 --- a/src/main/java/org/jabref/logic/git/MyGitHandler.java +++ b/src/main/java/org/jabref/logic/git/MyGitHandler.java @@ -8,11 +8,17 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.api.TransportConfigCallback; import org.eclipse.jgit.api.errors.GitAPIException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class MyGitHandler { final Path repositoryPath; final File repositoryPathAsFile; + static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); + public MyGitHandler(Path repositoryPath) { this.repositoryPath = repositoryPath; this.repositoryPathAsFile = this.repositoryPath.toFile(); @@ -54,4 +60,23 @@ public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) } return commitCreated; } + + /** + * Pushes all commits made to the branch that is tracked by the currently checked out branch. + * If pushing to remote fails, it fails silently. + */ + public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { + TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback( + "-----BEGIN RSA PRIVATE KEY-----\n" + + // PRIVATE KEY + "-----END RSA PRIVATE KEY-----", + "PUBLIC KEY"); + + Git git = Git.open(this.repositoryPathAsFile); + git.verifySignature(); + git.push() + .setTransportConfigCallback(transportConfigCallback) + .call(); + LOGGER.info("OK to push"); + } } From f3ae12e63d6f285922fc0f4b18ac1d96aa1b34da Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Sat, 23 Sep 2023 17:42:12 -0300 Subject: [PATCH 05/48] refactor --- .../org/jabref/logic/git/MyGitHandler.java | 82 ------------------- 1 file changed, 82 deletions(-) delete mode 100644 src/main/java/org/jabref/logic/git/MyGitHandler.java diff --git a/src/main/java/org/jabref/logic/git/MyGitHandler.java b/src/main/java/org/jabref/logic/git/MyGitHandler.java deleted file mode 100644 index 0583de2122a..00000000000 --- a/src/main/java/org/jabref/logic/git/MyGitHandler.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.jabref.logic.git; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.RmCommand; -import org.eclipse.jgit.api.Status; -import org.eclipse.jgit.api.TransportConfigCallback; -import org.eclipse.jgit.api.errors.GitAPIException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MyGitHandler { - final Path repositoryPath; - final File repositoryPathAsFile; - static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); - - public MyGitHandler(Path repositoryPath) { - this.repositoryPath = repositoryPath; - this.repositoryPathAsFile = this.repositoryPath.toFile(); - // TODO: init .git - } - - boolean isLocalGitRepository() { - Path gitFolderPath = Path.of(repositoryPath.toString(), ".git"); - return Files.exists(gitFolderPath) && Files.isDirectory(gitFolderPath); - } - - public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) { - boolean commitCreated = false; - try (Git git = Git.open(this.repositoryPathAsFile)) { - Status status = git.status().call(); - if (!status.isClean()) { - commitCreated = true; - // Add new and changed files to index - git.add() - .addFilepattern(".") - .call(); - // Add all removed files to index - if (!status.getMissing().isEmpty()) { - RmCommand removeCommand = git.rm() - .setCached(true); - status.getMissing().forEach(removeCommand::addFilepattern); - removeCommand.call(); - } - git.commit() - .setAmend(amend) - .setAllowEmpty(false) - .setMessage(commitMessage) - .call(); - } - } catch ( - IOException | - GitAPIException e) { - throw new RuntimeException(e); - } - return commitCreated; - } - - /** - * Pushes all commits made to the branch that is tracked by the currently checked out branch. - * If pushing to remote fails, it fails silently. - */ - public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { - TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback( - "-----BEGIN RSA PRIVATE KEY-----\n" + - // PRIVATE KEY - "-----END RSA PRIVATE KEY-----", - "PUBLIC KEY"); - - Git git = Git.open(this.repositoryPathAsFile); - git.verifySignature(); - git.push() - .setTransportConfigCallback(transportConfigCallback) - .call(); - LOGGER.info("OK to push"); - } -} From c3128171126ad1d182a849042a4c4244ab070eeb Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Tue, 26 Sep 2023 00:03:43 -0300 Subject: [PATCH 06/48] fix: commit and push --- build.gradle | 9 ++-- src/main/java/module-info.java | 3 +- .../gui/exporter/SaveDatabaseAction.java | 2 +- .../gui/exporter/SaveGitDatabaseAction.java | 26 ++++++++--- .../logic/git/CustomSshSessionFactory.java | 27 ++++++++++++ .../java/org/jabref/logic/git/GitHandler.java | 33 +++++++------- .../logic/git/SshTransportConfigCallback.java | 44 ++++--------------- 7 files changed, 82 insertions(+), 62 deletions(-) create mode 100644 src/main/java/org/jabref/logic/git/CustomSshSessionFactory.java diff --git a/build.gradle b/build.gradle index 3e24383203a..318c70707e4 100644 --- a/build.gradle +++ b/build.gradle @@ -128,7 +128,7 @@ dependencies { // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 implementation 'org.bouncycastle:bcprov-jdk18on:1.76' - implementation 'commons-cli:commons-cli:1.5.0' + implementation ('commons-cli:commons-cli:1.5.0') implementation 'org.libreoffice:unoloader:7.6.1' implementation 'org.libreoffice:libreoffice:7.6.1' @@ -140,8 +140,11 @@ 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', version: '6.7.0.202309050840-r' - implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit.ssh.jsch', version: '6.7.0.202309050840-r' + implementation(group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.7.0.202309050840-r') + implementation(group: 'org.eclipse.jgit', name: 'org.eclipse.jgit.ssh.apache', version: '6.7.0.202309050840-r') + configurations.all { + exclude group: "commons-logging", module: "commons-logging" + } implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.15.2' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.15.2' diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index a85af9ad644..dd13e7dd5ba 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -143,6 +143,5 @@ requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; requires de.saxsys.mvvmfx.validation; - requires jsch; - requires org.eclipse.jgit.ssh.jsch; + requires org.eclipse.jgit.ssh.apache; } diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index ee465eb04b2..cd314dd4220 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -245,7 +245,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { dialogService.notify(Localization.lang("Library saved")); if (success) { - SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath.getParent()); + SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath.getParent(), dialogService); saveGit.automaticUpdate(); } return success; diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index a0d4b318f3a..84bc8c22617 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -1,21 +1,37 @@ package org.jabref.gui.exporter; +import java.io.IOException; import java.nio.file.Path; -import org.jabref.logic.git.MyGitHandler; +import org.jabref.gui.DialogService; +import org.jabref.logic.git.GitHandler; +import org.jabref.logic.l10n.Localization; + +import org.eclipse.jgit.api.errors.GitAPIException; public class SaveGitDatabaseAction { final Path repositoryPath; final String automaticCommitMsg = "Automatic update via JabRef"; - public SaveGitDatabaseAction(Path repositoryPath) { + private final DialogService dialogService; + + public SaveGitDatabaseAction(Path repositoryPath, DialogService dialogService) { this.repositoryPath = repositoryPath; + this.dialogService = dialogService; } public boolean automaticUpdate() { - MyGitHandler git = new MyGitHandler(repositoryPath); - git.createCommitOnCurrentBranch(automaticCommitMsg, false); - + try { + GitHandler git = new GitHandler(repositoryPath); + git.createCommitOnCurrentBranch(automaticCommitMsg, false); + git.pushCommitsToRemoteRepository(); + } catch ( + GitAPIException | + IOException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), e); + throw new RuntimeException(e); + } + return true; } } diff --git a/src/main/java/org/jabref/logic/git/CustomSshSessionFactory.java b/src/main/java/org/jabref/logic/git/CustomSshSessionFactory.java new file mode 100644 index 00000000000..0eda6efe870 --- /dev/null +++ b/src/main/java/org/jabref/logic/git/CustomSshSessionFactory.java @@ -0,0 +1,27 @@ +package org.jabref.logic.git; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jgit.transport.sshd.SshdSessionFactory; + +public final class CustomSshSessionFactory extends SshdSessionFactory { + private Path sshDir; + + public CustomSshSessionFactory(Path sshDir) { + this.sshDir = sshDir; + } + + @Override + public File getSshDirectory() { + try { + return Files.createDirectories(sshDir).toFile(); + } catch ( + IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index e865e9b9299..1aaa0165d20 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -12,11 +12,10 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.api.TransportConfigCallback; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.merge.MergeStrategy; -import org.eclipse.jgit.transport.CredentialsProvider; -import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,9 +27,7 @@ public class GitHandler { static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); final Path repositoryPath; final File repositoryPathAsFile; - String gitUsername = Optional.ofNullable(System.getenv("GIT_EMAIL")).orElse(""); - String gitPassword = Optional.ofNullable(System.getenv("GIT_PW")).orElse(""); - final CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); + private File sshDirectory = new File("/home/null/.ssh"); /** * Initialize the handler for the given repository @@ -81,7 +78,8 @@ void setupGitIgnore() { boolean isGitRepository() { // For some reason the solution from https://www.eclipse.org/lists/jgit-dev/msg01892.html does not work // This solution is quite simple but might not work in special cases, for us it should suffice. - return Files.exists(Path.of(repositoryPath.toString(), ".git")); + Path gitFolderPath = Path.of(repositoryPath.toString(), ".git"); + return Files.exists(gitFolderPath) && Files.isDirectory(gitFolderPath); } /** @@ -175,15 +173,19 @@ public void mergeBranches(String targetBranch, String sourceBranch, MergeStrateg * Pushes all commits made to the branch that is tracked by the currently checked out branch. * If pushing to remote fails, it fails silently. */ - public void pushCommitsToRemoteRepository() throws IOException { - try (Git git = Git.open(this.repositoryPathAsFile)) { - try { - git.push() - .setCredentialsProvider(credentialsProvider) - .call(); - } catch (GitAPIException e) { - LOGGER.info("Failed to push"); - } + public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { + try { + TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(sshDirectory); + Git git = Git.open(this.repositoryPathAsFile); + git.verifySignature(); + git.push() + .setTransportConfigCallback(transportConfigCallback) + .call(); + } catch ( + IOException | + GitAPIException e) { + LOGGER.info("Failed to push"); + throw new RuntimeException(e); } } @@ -191,7 +193,6 @@ public void pullOnCurrentBranch() throws IOException { try (Git git = Git.open(this.repositoryPathAsFile)) { try { git.pull() - .setCredentialsProvider(credentialsProvider) .call(); } catch (GitAPIException e) { LOGGER.info("Failed to push"); diff --git a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java index 7bbf649c9c3..d10e197d849 100644 --- a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java +++ b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java @@ -1,51 +1,25 @@ package org.jabref.logic.git; -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; +import java.io.File; +import java.nio.file.Path; + import org.eclipse.jgit.api.TransportConfigCallback; -import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.SshTransport; import org.eclipse.jgit.transport.Transport; -import org.eclipse.jgit.transport.ssh.jsch.JschConfigSessionFactory; -import org.eclipse.jgit.transport.ssh.jsch.OpenSshConfig; +import org.eclipse.jgit.transport.sshd.SshdSessionFactory; import org.eclipse.jgit.util.FS; -/** - * A custom TransportConfigCallback class that loads private key and public key from the provided strings in constructor. - * An instance of this class will be used as follows: - * TransportConfigCallback = new SshTransportConfigCallback(PVT_KEY_STRING, PUB_KEY_STRING); - * Git.open(gitRepoDirFile) // gitRepoDirFile is an instance of File - * .push() - * .setTransportConfigCallback(transportConfigCallback) - * .call(); - */ public class SshTransportConfigCallback implements TransportConfigCallback { - private final String privateKey; - private final String publicKey; + private Path sshDir = new File(FS.DETECTED.userHome(), "/.ssh").toPath(); + private SshdSessionFactory sshSessionFactory; - public SshTransportConfigCallback(String privateKey, String publicKey) { - this.privateKey = privateKey; - this.publicKey = publicKey; + public SshTransportConfigCallback(File sshDirectory) { + this.sshSessionFactory = new CustomSshSessionFactory(sshDir); } - private final SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() { - @Override - protected void configure(OpenSshConfig.Host hc, Session session) { - session.setConfig("StrictHostKeyChecking", "no"); - } - - @Override - protected JSch createDefaultJSch(FS fs) throws JSchException { - JSch jSch = super.createDefaultJSch(fs); - jSch.addIdentity("/home/null/.ssh/id_rsa", privateKey.getBytes(), publicKey.getBytes(), "".getBytes()); - return jSch; - } - }; - @Override public void configure(Transport transport) { SshTransport sshTransport = (SshTransport) transport; - sshTransport.setSshSessionFactory(sshSessionFactory); + sshTransport.setSshSessionFactory(this.sshSessionFactory); } } From 7ffe7f1db0d8b54a0d743603759966ac9ec80b46 Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Tue, 26 Sep 2023 00:11:08 -0300 Subject: [PATCH 07/48] code refactoring --- .../logic/git/CustomSshSessionFactory.java | 27 ------------------- .../java/org/jabref/logic/git/GitHandler.java | 3 +-- .../logic/git/SshTransportConfigCallback.java | 23 +++++++++++++++- 3 files changed, 23 insertions(+), 30 deletions(-) delete mode 100644 src/main/java/org/jabref/logic/git/CustomSshSessionFactory.java diff --git a/src/main/java/org/jabref/logic/git/CustomSshSessionFactory.java b/src/main/java/org/jabref/logic/git/CustomSshSessionFactory.java deleted file mode 100644 index 0eda6efe870..00000000000 --- a/src/main/java/org/jabref/logic/git/CustomSshSessionFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.jabref.logic.git; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.eclipse.jgit.transport.sshd.SshdSessionFactory; - -public final class CustomSshSessionFactory extends SshdSessionFactory { - private Path sshDir; - - public CustomSshSessionFactory(Path sshDir) { - this.sshDir = sshDir; - } - - @Override - public File getSshDirectory() { - try { - return Files.createDirectories(sshDir).toFile(); - } catch ( - IOException e) { - e.printStackTrace(); - } - return null; - } -} diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 1aaa0165d20..8a5d00431aa 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -27,7 +27,6 @@ public class GitHandler { static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); final Path repositoryPath; final File repositoryPathAsFile; - private File sshDirectory = new File("/home/null/.ssh"); /** * Initialize the handler for the given repository @@ -175,7 +174,7 @@ public void mergeBranches(String targetBranch, String sourceBranch, MergeStrateg */ public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { try { - TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(sshDirectory); + TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); Git git = Git.open(this.repositoryPathAsFile); git.verifySignature(); git.push() diff --git a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java index d10e197d849..aed1b9394be 100644 --- a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java +++ b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java @@ -1,6 +1,8 @@ package org.jabref.logic.git; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import org.eclipse.jgit.api.TransportConfigCallback; @@ -13,7 +15,7 @@ public class SshTransportConfigCallback implements TransportConfigCallback { private Path sshDir = new File(FS.DETECTED.userHome(), "/.ssh").toPath(); private SshdSessionFactory sshSessionFactory; - public SshTransportConfigCallback(File sshDirectory) { + public SshTransportConfigCallback() { this.sshSessionFactory = new CustomSshSessionFactory(sshDir); } @@ -22,4 +24,23 @@ public void configure(Transport transport) { SshTransport sshTransport = (SshTransport) transport; sshTransport.setSshSessionFactory(this.sshSessionFactory); } + + public final class CustomSshSessionFactory extends SshdSessionFactory { + private Path sshDir; + + public CustomSshSessionFactory(Path sshDir) { + this.sshDir = sshDir; + } + + @Override + public File getSshDirectory() { + try { + return Files.createDirectories(sshDir).toFile(); + } catch ( + IOException e) { + e.printStackTrace(); + } + return null; + } + } } From 905eda9fc491fe746c494114f9cd9bbb9dff7cf0 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:24:37 -0300 Subject: [PATCH 08/48] add silent exception on git pull and push --- .../java/org/jabref/logic/git/GitHandler.java | 31 +++++++++++++++---- .../logic/git/SshTransportConfigCallback.java | 5 +-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 8a5d00431aa..f091c3391fe 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -175,30 +175,49 @@ public void mergeBranches(String targetBranch, String sourceBranch, MergeStrateg public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { try { TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); + System.out.println(transportConfigCallback); Git git = Git.open(this.repositoryPathAsFile); + System.out.println(git); git.verifySignature(); + System.out.println("signature veridfied"); git.push() .setTransportConfigCallback(transportConfigCallback) .call(); - } catch ( - IOException | - GitAPIException e) { - LOGGER.info("Failed to push"); - throw new RuntimeException(e); + System.out.println("pushed"); + } catch (IOException | GitAPIException e) { + if (e.getMessage().equals("origin: not found")) { + LOGGER.info("No remote repository detected. Push skiped."); + } else { + LOGGER.info("Failed to push"); + throw new RuntimeException(e); + } } } + /** + * Pulls all commits made to the branch that is tracked by the currently checked out branch. + * If pulling to remote fails, it fails silently. + */ public void pullOnCurrentBranch() throws IOException { try (Git git = Git.open(this.repositoryPathAsFile)) { try { git.pull() .call(); } catch (GitAPIException e) { - LOGGER.info("Failed to push"); + if (e.getMessage().equals("origin: not found")) { + LOGGER.info("No remote repository detected. Push skiped."); + } else { + LOGGER.info("Failed to pull"); + throw new RuntimeException(e); + } } } } + /** + * Get currently checked out branch. + * If checking out fails, it fails silently. + */ public String getCurrentlyCheckedOutBranch() throws IOException { try (Git git = Git.open(this.repositoryPathAsFile)) { return git.getRepository().getBranch(); diff --git a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java index aed1b9394be..caaea9e93f7 100644 --- a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java +++ b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java @@ -17,6 +17,8 @@ public class SshTransportConfigCallback implements TransportConfigCallback { public SshTransportConfigCallback() { this.sshSessionFactory = new CustomSshSessionFactory(sshDir); + System.out.println(this.sshSessionFactory); + System.out.println(sshDir); } @Override @@ -36,8 +38,7 @@ public CustomSshSessionFactory(Path sshDir) { public File getSshDirectory() { try { return Files.createDirectories(sshDir).toFile(); - } catch ( - IOException e) { + } catch (IOException e) { e.printStackTrace(); } return null; From b104b99315ab50c18c34c551a5df621a54cce2ee Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Tue, 26 Sep 2023 14:20:42 -0300 Subject: [PATCH 09/48] add username and password git credentials input --- .../gui/exporter/SaveGitDatabaseAction.java | 2 +- .../java/org/jabref/logic/git/GitHandler.java | 43 +++++++++++++++---- .../logic/git/SshTransportConfigCallback.java | 2 - 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index 84bc8c22617..327bfaf6a5a 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -24,7 +24,7 @@ public boolean automaticUpdate() { try { GitHandler git = new GitHandler(repositoryPath); git.createCommitOnCurrentBranch(automaticCommitMsg, false); - git.pushCommitsToRemoteRepository(); + git.pushCommitsToRemoteRepository(this.dialogService); } catch ( GitAPIException | IOException e) { diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index f091c3391fe..6e7c0a79bba 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -7,6 +7,8 @@ import java.nio.file.Path; import java.util.Optional; +import org.jabref.gui.DialogService; +import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; import org.eclipse.jgit.api.Git; @@ -16,6 +18,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -168,24 +171,48 @@ public void mergeBranches(String targetBranch, String sourceBranch, MergeStrateg this.checkoutBranch(currentBranch); } + public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { + pushCommitsToRemoteRepository(null); + } + /** * Pushes all commits made to the branch that is tracked by the currently checked out branch. * If pushing to remote fails, it fails silently. */ - public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { + public void pushCommitsToRemoteRepository(DialogService dialogService) throws IOException, GitAPIException { try { - TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); - System.out.println(transportConfigCallback); Git git = Git.open(this.repositoryPathAsFile); - System.out.println(git); + String remoteURL = git.getRepository().getConfig().getString("remote", "origin", "url"); + Boolean isSshRemoteRepository = remoteURL != null ? remoteURL.contains(".git") : false; + git.verifySignature(); - System.out.println("signature veridfied"); - git.push() + + if (isSshRemoteRepository) { + TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); + git.push() .setTransportConfigCallback(transportConfigCallback) .call(); - System.out.println("pushed"); + } else { + String gitUsername = ""; + String gitPassword = ""; + + if (dialogService != null) { + gitUsername = dialogService.showInputDialogAndWait(Localization.lang("Git credentials"), Localization.lang("git username")).get(); + gitPassword = dialogService.showPasswordDialogAndWait(Localization.lang("Git credentials"), Localization.lang("password"), Localization.lang("password")).get(); + + UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); + + git.push() + .setCredentialsProvider(credentialsProvider) + .call(); + } else { + git.push() + .call(); + } + } + } catch (IOException | GitAPIException e) { - if (e.getMessage().equals("origin: not found")) { + if (e.getMessage().equals("origin: not found.")) { LOGGER.info("No remote repository detected. Push skiped."); } else { LOGGER.info("Failed to push"); diff --git a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java index caaea9e93f7..ab246486196 100644 --- a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java +++ b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java @@ -17,8 +17,6 @@ public class SshTransportConfigCallback implements TransportConfigCallback { public SshTransportConfigCallback() { this.sshSessionFactory = new CustomSshSessionFactory(sshDir); - System.out.println(this.sshSessionFactory); - System.out.println(sshDir); } @Override From bf33ca0a891b8dcb06dcbe492a655603341cef5b Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:44:16 -0300 Subject: [PATCH 10/48] fix style and git logic to add one file only --- build.gradle | 2 +- .../gui/exporter/SaveDatabaseAction.java | 2 +- .../gui/exporter/SaveGitDatabaseAction.java | 15 ++-- .../java/org/jabref/logic/git/GitHandler.java | 77 +++++++++++++++---- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/build.gradle b/build.gradle index 318c70707e4..3f5f845f993 100644 --- a/build.gradle +++ b/build.gradle @@ -128,7 +128,7 @@ dependencies { // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 implementation 'org.bouncycastle:bcprov-jdk18on:1.76' - implementation ('commons-cli:commons-cli:1.5.0') + implementation 'commons-cli:commons-cli:1.5.0' implementation 'org.libreoffice:unoloader:7.6.1' implementation 'org.libreoffice:libreoffice:7.6.1' diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index cd314dd4220..dfc9be28800 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -245,7 +245,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { dialogService.notify(Localization.lang("Library saved")); if (success) { - SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath.getParent(), dialogService); + SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath, dialogService); saveGit.automaticUpdate(); } return success; diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index 327bfaf6a5a..dc71666473a 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -10,20 +10,25 @@ import org.eclipse.jgit.api.errors.GitAPIException; public class SaveGitDatabaseAction { - final Path repositoryPath; + final Path filePath; final String automaticCommitMsg = "Automatic update via JabRef"; private final DialogService dialogService; - public SaveGitDatabaseAction(Path repositoryPath, DialogService dialogService) { - this.repositoryPath = repositoryPath; + public SaveGitDatabaseAction(Path filePath, DialogService dialogService) { + this.filePath = filePath; this.dialogService = dialogService; } + /** + * Handle JabRef git integration action + * + * @return true of false whether the action was successful or not + */ public boolean automaticUpdate() { try { - GitHandler git = new GitHandler(repositoryPath); - git.createCommitOnCurrentBranch(automaticCommitMsg, false); + GitHandler git = new GitHandler(filePath.getParent()); + git.createCommitWithSingleFileOnCurrentBranch(automaticCommitMsg, filePath.getFileName().toString(), false); git.pushCommitsToRemoteRepository(this.dialogService); } catch ( GitAPIException | diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 6e7c0a79bba..c4a9f86f592 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -21,6 +21,7 @@ import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.eclipse.jgit.transport.CredentialsProvider; /** * This class handles the updating of the local and remote git repository that is located at the repository path @@ -29,7 +30,10 @@ public class GitHandler { static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); final Path repositoryPath; - final File repositoryPathAsFile; + final File repositoryPathAsFile; + String gitUsername = Optional.ofNullable(System.getenv("GIT_EMAIL")).orElse(""); + String gitPassword = Optional.ofNullable(System.getenv("GIT_PW")).orElse(""); + final CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); /** * Initialize the handler for the given repository @@ -147,6 +151,40 @@ public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) return commitCreated; } + /** + * Creates a commit on the currently checked out branch with a single file + * + * @param filename The name of the file to commit + * @param amend Whether to amend to the last commit (true), or not (false) + * @return Returns true if a new commit was created. This is the case if the repository was not clean on method invocation + */ + public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String commitMessage, boolean amend) throws IOException, GitAPIException { + boolean commitCreated = false; + try (Git git = Git.open(this.repositoryPathAsFile)) { + Status status = git.status().call(); + if (!status.isClean()) { + commitCreated = true; + // Add new and changed files to index + git.add() + .addFilepattern(filename) + .call(); + // Add all removed files to index + if (!status.getMissing().isEmpty()) { + RmCommand removeCommand = git.rm() + .setCached(true); + status.getMissing().forEach(removeCommand::addFilepattern); + removeCommand.call(); + } + git.commit() + .setAmend(amend) + .setAllowEmpty(false) + .setMessage(commitMessage) + .call(); + } + } + return commitCreated; + } + /** * Merges the source branch into the target branch * @@ -179,7 +217,7 @@ public void pushCommitsToRemoteRepository() throws IOException, GitAPIException * Pushes all commits made to the branch that is tracked by the currently checked out branch. * If pushing to remote fails, it fails silently. */ - public void pushCommitsToRemoteRepository(DialogService dialogService) throws IOException, GitAPIException { + public void pushCommitsToRemoteRepository(DialogService dialogService) { try { Git git = Git.open(this.repositoryPathAsFile); String remoteURL = git.getRepository().getConfig().getString("remote", "origin", "url"); @@ -193,20 +231,26 @@ public void pushCommitsToRemoteRepository(DialogService dialogService) throws IO .setTransportConfigCallback(transportConfigCallback) .call(); } else { - String gitUsername = ""; - String gitPassword = ""; + if (this.gitPassword.equals("") || this.gitUsername.equals("")) { + String gitUsername = ""; + String gitPassword = ""; - if (dialogService != null) { - gitUsername = dialogService.showInputDialogAndWait(Localization.lang("Git credentials"), Localization.lang("git username")).get(); - gitPassword = dialogService.showPasswordDialogAndWait(Localization.lang("Git credentials"), Localization.lang("password"), Localization.lang("password")).get(); + if (dialogService != null) { + gitUsername = dialogService.showInputDialogAndWait(Localization.lang("Git credentials"), Localization.lang("git username")).get(); + gitPassword = dialogService.showPasswordDialogAndWait(Localization.lang("Git credentials"), Localization.lang("password"), Localization.lang("password")).get(); - UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); + CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); - git.push() - .setCredentialsProvider(credentialsProvider) - .call(); + git.push() + .setCredentialsProvider(credentialsProvider) + .call(); + } else { + git.push() + .call(); + } } else { git.push() + .setCredentialsProvider(this.credentialsProvider) .call(); } } @@ -225,7 +269,7 @@ public void pushCommitsToRemoteRepository(DialogService dialogService) throws IO * Pulls all commits made to the branch that is tracked by the currently checked out branch. * If pulling to remote fails, it fails silently. */ - public void pullOnCurrentBranch() throws IOException { + public void pullOnCurrentBranch() { try (Git git = Git.open(this.repositoryPathAsFile)) { try { git.pull() @@ -238,16 +282,21 @@ public void pullOnCurrentBranch() throws IOException { throw new RuntimeException(e); } } + } catch (IOException ex) { + LOGGER.info("Failed pulling git repository"); } } /** - * Get currently checked out branch. + * Get the short name of the current branch that HEAD points to. * If checking out fails, it fails silently. */ - public String getCurrentlyCheckedOutBranch() throws IOException { + public String getCurrentlyCheckedOutBranch() { try (Git git = Git.open(this.repositoryPathAsFile)) { return git.getRepository().getBranch(); + } catch (IOException ex) { + LOGGER.info("Failed get current branch"); + return ""; } } } From 375e689b703726041b2a627fba3434499de9a4cf Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:50:33 -0300 Subject: [PATCH 11/48] add git preferences files --- .../org/jabref/logic/git/GitPreferences.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/main/java/org/jabref/logic/git/GitPreferences.java diff --git a/src/main/java/org/jabref/logic/git/GitPreferences.java b/src/main/java/org/jabref/logic/git/GitPreferences.java new file mode 100644 index 00000000000..c31f2c0004b --- /dev/null +++ b/src/main/java/org/jabref/logic/git/GitPreferences.java @@ -0,0 +1,39 @@ +package org.jabref.logic.git; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class GitPreferences { + private final StringProperty username; + private final StringProperty password; + + public GitPreferences(String username, + String password) { + this.username = new SimpleStringProperty(username); + this.password = new SimpleStringProperty(password); + } + + public final String getUsername() { + return username.getValue(); + } + + public StringProperty usernameProperty() { + return username; + } + + public void setUsername(String username) { + this.username.set(username); + } + + public final String getPassword() { + return password.getValue(); + } + + public StringProperty passwordProperty() { + return password; + } + + public void setPassword(String password) { + this.password.set(password); + } +} From d3a80145335790f38c7b2e31a401f41221ba656f Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:01:31 -0300 Subject: [PATCH 12/48] inject dialog service --- .../jabref/gui/exporter/SaveDatabaseAction.java | 2 +- .../jabref/gui/exporter/SaveGitDatabaseAction.java | 9 +++++---- src/main/java/org/jabref/logic/git/GitHandler.java | 14 ++++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index dfc9be28800..c40e7b80cf7 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -245,7 +245,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { dialogService.notify(Localization.lang("Library saved")); if (success) { - SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath, dialogService); + SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath); saveGit.automaticUpdate(); } return success; diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index dc71666473a..098a73a049a 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -4,9 +4,10 @@ import java.nio.file.Path; import org.jabref.gui.DialogService; +import org.jabref.gui.documentviewer.DocumentViewerView; import org.jabref.logic.git.GitHandler; import org.jabref.logic.l10n.Localization; - +import com.airhacks.afterburner.injection.Injector; import org.eclipse.jgit.api.errors.GitAPIException; public class SaveGitDatabaseAction { @@ -15,9 +16,9 @@ public class SaveGitDatabaseAction { private final DialogService dialogService; - public SaveGitDatabaseAction(Path filePath, DialogService dialogService) { + public SaveGitDatabaseAction(Path filePath) { this.filePath = filePath; - this.dialogService = dialogService; + this.dialogService = Injector.instantiateModelOrService(DialogService.class); } /** @@ -29,7 +30,7 @@ public boolean automaticUpdate() { try { GitHandler git = new GitHandler(filePath.getParent()); git.createCommitWithSingleFileOnCurrentBranch(automaticCommitMsg, filePath.getFileName().toString(), false); - git.pushCommitsToRemoteRepository(this.dialogService); + git.pushCommitsToRemoteRepository(); } catch ( GitAPIException | IOException e) { diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index c4a9f86f592..0fdc48c6dc2 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -8,6 +8,7 @@ import java.util.Optional; import org.jabref.gui.DialogService; +import org.jabref.gui.documentviewer.DocumentViewerView; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; @@ -21,6 +22,9 @@ import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import com.airhacks.afterburner.injection.Injector; + import org.eclipse.jgit.transport.CredentialsProvider; /** @@ -34,6 +38,7 @@ public class GitHandler { String gitUsername = Optional.ofNullable(System.getenv("GIT_EMAIL")).orElse(""); String gitPassword = Optional.ofNullable(System.getenv("GIT_PW")).orElse(""); final CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); + private DialogService dialogService; /** * Initialize the handler for the given repository @@ -43,6 +48,7 @@ public class GitHandler { public GitHandler(Path repositoryPath) { this.repositoryPath = repositoryPath; this.repositoryPathAsFile = this.repositoryPath.toFile(); + this.dialogService = Injector.instantiateModelOrService(DialogService.class); if (!isGitRepository()) { try { Git.init() @@ -209,20 +215,16 @@ public void mergeBranches(String targetBranch, String sourceBranch, MergeStrateg this.checkoutBranch(currentBranch); } - public void pushCommitsToRemoteRepository() throws IOException, GitAPIException { - pushCommitsToRemoteRepository(null); - } - /** * Pushes all commits made to the branch that is tracked by the currently checked out branch. * If pushing to remote fails, it fails silently. */ - public void pushCommitsToRemoteRepository(DialogService dialogService) { + public void pushCommitsToRemoteRepository() { try { Git git = Git.open(this.repositoryPathAsFile); String remoteURL = git.getRepository().getConfig().getString("remote", "origin", "url"); Boolean isSshRemoteRepository = remoteURL != null ? remoteURL.contains(".git") : false; - + git.verifySignature(); if (isSshRemoteRepository) { From a1e8ca012d774cd0555bb80dff93b1551167a2d4 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:15:55 -0300 Subject: [PATCH 13/48] add initial support to git credentials dialog view --- .../gui/git/GitCredentialsDialogView.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java diff --git a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java new file mode 100644 index 00000000000..605a9d8adac --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java @@ -0,0 +1,37 @@ +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.util.BaseDialog; +import org.jabref.logic.l10n.Localization; +import org.jabref.preferences.PreferencesService; + +import com.airhacks.afterburner.views.ViewLoader; +import jakarta.inject.Inject; + +public class GitCredentialsDialogView extends BaseDialog { + + @FXML private ButtonType copyVersionButton; + @FXML private TextArea textAreaVersions; + + @Inject private DialogService dialogService; + @Inject private PreferencesService preferencesService; + + + + public GitCredentialsDialogView() { + this.setTitle(Localization.lang("Git credentials")); + + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + } + + @FXML + private void initialize() { + this.setResizable(false); + } +} From e4dd331cd8d2fad1241183a41704b38c461b542c Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Tue, 3 Oct 2023 15:18:25 -0300 Subject: [PATCH 14/48] fix: git failed silently and style --- .../jabref/gui/exporter/SaveGitDatabaseAction.java | 9 +++++---- src/main/java/org/jabref/logic/git/GitHandler.java | 11 ++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index 098a73a049a..19a0f6ca9bc 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -4,13 +4,15 @@ import java.nio.file.Path; import org.jabref.gui.DialogService; -import org.jabref.gui.documentviewer.DocumentViewerView; import org.jabref.logic.git.GitHandler; -import org.jabref.logic.l10n.Localization; + import com.airhacks.afterburner.injection.Injector; import org.eclipse.jgit.api.errors.GitAPIException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SaveGitDatabaseAction { + static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); final Path filePath; final String automaticCommitMsg = "Automatic update via JabRef"; @@ -34,8 +36,7 @@ public boolean automaticUpdate() { } catch ( GitAPIException | IOException e) { - dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), e); - throw new RuntimeException(e); + LOGGER.info("Failed to automatic update"); } return true; diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 0fdc48c6dc2..bb1aaa80d90 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -8,10 +8,10 @@ import java.util.Optional; import org.jabref.gui.DialogService; -import org.jabref.gui.documentviewer.DocumentViewerView; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; +import com.airhacks.afterburner.injection.Injector; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; @@ -19,14 +19,11 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.airhacks.afterburner.injection.Injector; - -import org.eclipse.jgit.transport.CredentialsProvider; - /** * This class handles the updating of the local and remote git repository that is located at the repository path * This provides an easy-to-use interface to manage a git repository @@ -34,7 +31,7 @@ public class GitHandler { static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); final Path repositoryPath; - final File repositoryPathAsFile; + final File repositoryPathAsFile; String gitUsername = Optional.ofNullable(System.getenv("GIT_EMAIL")).orElse(""); String gitPassword = Optional.ofNullable(System.getenv("GIT_PW")).orElse(""); final CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); @@ -256,7 +253,7 @@ public void pushCommitsToRemoteRepository() { .call(); } } - + } catch (IOException | GitAPIException e) { if (e.getMessage().equals("origin: not found.")) { LOGGER.info("No remote repository detected. Push skiped."); From 4c81e5478271dc67cfc5082d8254ace68e11da2f Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:48:07 -0300 Subject: [PATCH 15/48] fix git http push --- .../gui/exporter/SaveGitDatabaseAction.java | 13 +++-- .../java/org/jabref/logic/git/GitHandler.java | 53 +++++++++++-------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index 19a0f6ca9bc..89c07234572 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -6,7 +6,6 @@ import org.jabref.gui.DialogService; import org.jabref.logic.git.GitHandler; -import com.airhacks.afterburner.injection.Injector; import org.eclipse.jgit.api.errors.GitAPIException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,11 +15,8 @@ public class SaveGitDatabaseAction { final Path filePath; final String automaticCommitMsg = "Automatic update via JabRef"; - private final DialogService dialogService; - public SaveGitDatabaseAction(Path filePath) { this.filePath = filePath; - this.dialogService = Injector.instantiateModelOrService(DialogService.class); } /** @@ -30,13 +26,16 @@ public SaveGitDatabaseAction(Path filePath) { */ public boolean automaticUpdate() { try { - GitHandler git = new GitHandler(filePath.getParent()); - git.createCommitWithSingleFileOnCurrentBranch(automaticCommitMsg, filePath.getFileName().toString(), false); + System.out.println(this.filePath.getParent()); + System.out.println(this.filePath.getFileName().toString()); + GitHandler git = new GitHandler(this.filePath.getParent()); + git.createCommitWithSingleFileOnCurrentBranch(this.filePath.getFileName().toString(), automaticCommitMsg, false); git.pushCommitsToRemoteRepository(); } catch ( GitAPIException | IOException e) { - LOGGER.info("Failed to automatic update"); + System.out.println(e.getMessage()); + LOGGER.info("Failed to automatic git update"); } return true; diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index bb1aaa80d90..79b556b9d23 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -17,6 +17,8 @@ import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.TransportConfigCallback; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.transport.CredentialsProvider; @@ -160,31 +162,38 @@ public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) * @param filename The name of the file to commit * @param amend Whether to amend to the last commit (true), or not (false) * @return Returns true if a new commit was created. This is the case if the repository was not clean on method invocation + * @throws IOException + * @throws GitAPIException + * @throws NoWorkTreeException */ - public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String commitMessage, boolean amend) throws IOException, GitAPIException { + public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String commitMessage, boolean amend) throws IOException, NoWorkTreeException, GitAPIException { boolean commitCreated = false; - try (Git git = Git.open(this.repositoryPathAsFile)) { - Status status = git.status().call(); - if (!status.isClean()) { - commitCreated = true; - // Add new and changed files to index - git.add() - .addFilepattern(filename) - .call(); - // Add all removed files to index - if (!status.getMissing().isEmpty()) { - RmCommand removeCommand = git.rm() - .setCached(true); - status.getMissing().forEach(removeCommand::addFilepattern); - removeCommand.call(); - } - git.commit() - .setAmend(amend) - .setAllowEmpty(false) - .setMessage(commitMessage) - .call(); + + Git git = Git.open(this.repositoryPathAsFile); + Status status = git.status().call(); + if (!status.isClean()) { + commitCreated = true; + + // Add new and changed files to index + git.add() + .addFilepattern(filename) + .call(); + + // Add all removed files to index + if (!status.getMissing().isEmpty()) { + RmCommand removeCommand = git.rm() + .setCached(true); + System.out.println("1----"); + status.getMissing().forEach(removeCommand::addFilepattern); + removeCommand.call(); } + git.commit() + .setAmend(amend) + .setAllowEmpty(false) + .setMessage(commitMessage) + .call(); } + return commitCreated; } @@ -220,7 +229,7 @@ public void pushCommitsToRemoteRepository() { try { Git git = Git.open(this.repositoryPathAsFile); String remoteURL = git.getRepository().getConfig().getString("remote", "origin", "url"); - Boolean isSshRemoteRepository = remoteURL != null ? remoteURL.contains(".git") : false; + Boolean isSshRemoteRepository = remoteURL != null ? remoteURL.contains("git@") : false; git.verifySignature(); From df3e4bbe7dbd41369006bd2f67811e55d83c0ff8 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Wed, 4 Oct 2023 20:23:47 -0300 Subject: [PATCH 16/48] refactor git credentials dialog to single dialog with two inputs --- .../gui/git/GitCredentialsDialogView.java | 16 ++++----- .../java/org/jabref/logic/git/GitHandler.java | 35 ++++++++++++++++--- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java index 605a9d8adac..4683c715a8f 100644 --- a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java +++ b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java @@ -1,25 +1,26 @@ 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 org.jabref.gui.DialogService; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputDialog; +import javafx.scene.layout.VBox; + import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; -import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; public class GitCredentialsDialogView extends BaseDialog { @FXML private ButtonType copyVersionButton; @FXML private TextArea textAreaVersions; - @Inject private DialogService dialogService; - @Inject private PreferencesService preferencesService; - - public GitCredentialsDialogView() { this.setTitle(Localization.lang("Git credentials")); @@ -27,7 +28,6 @@ public GitCredentialsDialogView() { ViewLoader.view(this) .load() .setAsDialogPane(this); - } @FXML diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 79b556b9d23..8d8e56b81ef 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -8,10 +8,22 @@ import java.util.Optional; import org.jabref.gui.DialogService; +import org.jabref.gui.git.GitCredentialsDialogView; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; import com.airhacks.afterburner.injection.Injector; + +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 javafx.scene.text.Text; + import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; @@ -240,12 +252,25 @@ public void pushCommitsToRemoteRepository() { .call(); } else { if (this.gitPassword.equals("") || this.gitUsername.equals("")) { - String gitUsername = ""; - String gitPassword = ""; - if (dialogService != null) { - gitUsername = dialogService.showInputDialogAndWait(Localization.lang("Git credentials"), Localization.lang("git username")).get(); - gitPassword = dialogService.showPasswordDialogAndWait(Localization.lang("Git credentials"), Localization.lang("password"), Localization.lang("password")).get(); + DialogPane pane = new DialogPane(); + VBox vBox = new VBox(); + TextField inputGitUsername = new TextField(); + PasswordField inputGitPassword = new PasswordField(); + ButtonType accept = new ButtonType(Localization.lang("Accept"), ButtonBar.ButtonData.APPLY); + ButtonType cancel = new ButtonType(Localization.lang("Cancel"), ButtonBar.ButtonData.CANCEL_CLOSE); + + vBox.getChildren().add(new Label(Localization.lang("Git username"))); + vBox.getChildren().add(inputGitUsername); + vBox.getChildren().add(new Label(Localization.lang("Git password"))); + vBox.getChildren().add(inputGitPassword); + + pane.setContent(vBox); + + dialogService.showCustomDialogAndWait(Localization.lang("Git credentials"), pane, accept, cancel); + + String gitUsername = inputGitUsername.getText(); + String gitPassword = inputGitPassword.getText(); CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); From 298d86e5feea7b8e97a4ea528e5dbd700e65537f Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Sat, 7 Oct 2023 20:34:41 -0300 Subject: [PATCH 17/48] user and password menu without Keyring --- .../PreferencesDialogViewModel.java | 4 +- .../jabref/gui/preferences/git/GitTab.fxml | 18 +++++++ .../jabref/gui/preferences/git/GitTab.java | 34 +++++++++++++ .../gui/preferences/git/GitTabViewModel.java | 49 +++++++++++++++++++ .../org/jabref/logic/git/GitCredential.java | 19 +++++++ .../org/jabref/logic/git/GitPreferences.java | 15 +++--- .../jabref/preferences/JabRefPreferences.java | 21 ++++++++ .../preferences/PreferencesService.java | 3 ++ src/main/resources/l10n/JabRef_en.properties | 4 ++ 9 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/jabref/gui/preferences/git/GitTab.fxml create mode 100644 src/main/java/org/jabref/gui/preferences/git/GitTab.java create mode 100644 src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java create mode 100644 src/main/java/org/jabref/logic/git/GitCredential.java diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java index 35f6a9db00b..82c5e89eec1 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java @@ -25,6 +25,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; @@ -84,7 +85,8 @@ public PreferencesDialogViewModel(DialogService dialogService, PreferencesServic new XmpPrivacyTab(), new CustomImporterTab(), new CustomExporterTab(), - new NetworkTab() + new NetworkTab(), + new GitTab() ); } diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTab.fxml b/src/main/java/org/jabref/gui/preferences/git/GitTab.fxml new file mode 100644 index 00000000000..f0da514166b --- /dev/null +++ b/src/main/java/org/jabref/gui/preferences/git/GitTab.fxml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTab.java b/src/main/java/org/jabref/gui/preferences/git/GitTab.java new file mode 100644 index 00000000000..e671929c8b6 --- /dev/null +++ b/src/main/java/org/jabref/gui/preferences/git/GitTab.java @@ -0,0 +1,34 @@ +package org.jabref.gui.preferences.git; + +import javafx.fxml.FXML; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; + +import org.jabref.gui.preferences.AbstractPreferenceTabView; +import org.jabref.gui.preferences.PreferencesTab; +import org.jabref.logic.l10n.Localization; + +import com.airhacks.afterburner.views.ViewLoader; + +public class GitTab extends AbstractPreferenceTabView implements PreferencesTab { + + @FXML private TextField username; + @FXML private PasswordField password; + + public GitTab() { + ViewLoader.view(this) + .root(this) + .load(); + } + + @Override + public String getTabName() { + return Localization.lang("Git"); + } + + @FXML + private void initialize() { + viewModel = new GitTabViewModel(preferencesService, dialogService); + username.textProperty().setValue(viewModel.getUsername().getValue()); + } +} diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java new file mode 100644 index 00000000000..7201742e12a --- /dev/null +++ b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java @@ -0,0 +1,49 @@ +package org.jabref.gui.preferences.git; + +import java.util.List; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import org.jabref.gui.DialogService; +import org.jabref.gui.preferences.PreferenceTabViewModel; +import org.jabref.preferences.PreferencesService; + +public class GitTabViewModel implements PreferenceTabViewModel { + + private final StringProperty username = new SimpleStringProperty("ooo"); + private final StringProperty password = new SimpleStringProperty(); + private final PreferencesService preferences; + private final DialogService dialogService; + //private final GitPreferences gitPreferences; + + public GitTabViewModel(PreferencesService preferences, DialogService dialogService) { + this.preferences = preferences; + this.dialogService = dialogService; + //this.gitPreferences = preferences.getGitPreferences(); + } + + @Override + public void setValues() { + // TEST + username.setValue("test"); + } + + @Override + public void storeSettings() { + } + + @Override + public boolean validateSettings() { + return PreferenceTabViewModel.super.validateSettings(); + } + + @Override + public List getRestartWarnings() { + return PreferenceTabViewModel.super.getRestartWarnings(); + } + + public StringProperty getUsername() { + return username; + } +} diff --git a/src/main/java/org/jabref/logic/git/GitCredential.java b/src/main/java/org/jabref/logic/git/GitCredential.java new file mode 100644 index 00000000000..9ec484b6d35 --- /dev/null +++ b/src/main/java/org/jabref/logic/git/GitCredential.java @@ -0,0 +1,19 @@ +package org.jabref.logic.git; + +public class GitCredential { + private final String username; + private final String password; + + public GitCredential(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/org/jabref/logic/git/GitPreferences.java b/src/main/java/org/jabref/logic/git/GitPreferences.java index c31f2c0004b..88aa5e13494 100644 --- a/src/main/java/org/jabref/logic/git/GitPreferences.java +++ b/src/main/java/org/jabref/logic/git/GitPreferences.java @@ -4,17 +4,16 @@ import javafx.beans.property.StringProperty; public class GitPreferences { - private final StringProperty username; - private final StringProperty password; + private StringProperty username = new SimpleStringProperty(); + private StringProperty password = new SimpleStringProperty(); - public GitPreferences(String username, - String password) { - this.username = new SimpleStringProperty(username); - this.password = new SimpleStringProperty(password); + public GitPreferences(String username, String password) { + //this.username.setValue(username); + //this.password.set(password); } - public final String getUsername() { - return username.getValue(); + public String getUsername() { + return username.toString(); } public StringProperty usernameProperty() { diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 64eb1845322..f0cf231e958 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -69,6 +69,7 @@ import org.jabref.logic.exporter.MetaDataSerializer; import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.exporter.TemplateExporter; +import org.jabref.logic.git.GitPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.fetcher.DoiFetcher; @@ -375,6 +376,9 @@ public class JabRefPreferences implements PreferencesService { private static final String PROXY_PASSWORD = "proxyPassword"; private static final String PROXY_PERSIST_PASSWORD = "persistPassword"; + // Git + private static final String GIT_USERNAME = "gitUsername"; + // Web search private static final String FETCHER_CUSTOM_KEY_NAMES = "fetcherCustomKeyNames"; private static final String FETCHER_CUSTOM_KEY_USES = "fetcherCustomKeyUses"; @@ -482,6 +486,7 @@ public class JabRefPreferences implements PreferencesService { private ColumnPreferences searchDialogColumnPreferences; private JournalAbbreviationPreferences journalAbbreviationPreferences; private FieldPreferences fieldPreferences; + private GitPreferences gitPreferences; // The constructor is made private to enforce this as a singleton class: private JabRefPreferences() { @@ -2795,6 +2800,22 @@ public ProtectedTermsPreferences getProtectedTermsPreferences() { return protectedTermsPreferences; } + @Override + public GitPreferences getGitPreferences() { + if (Objects.nonNull(gitPreferences)) { + return gitPreferences; + } + + gitPreferences = new GitPreferences( + get(GIT_USERNAME), + getGitPassword()); + + return gitPreferences; + } + + private String getGitPassword() { + return ""; + } //************************************************************************************************************* // Importer preferences //************************************************************************************************************* diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index a03123740d6..ea5104c8c2d 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -16,6 +16,7 @@ import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; import org.jabref.logic.exporter.SelfContainedSaveConfiguration; +import org.jabref.logic.git.GitPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.fetcher.GrobidPreferences; @@ -149,4 +150,6 @@ public interface PreferencesService { MrDlibPreferences getMrDlibPreferences(); ProtectedTermsPreferences getProtectedTermsPreferences(); + + GitPreferences getGitPreferences(); } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index c659babbb4c..537646b6e43 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1289,6 +1289,10 @@ SSL\ certificate\ file=SSL certificate file Duplicate\ Certificates=Duplicate Certificates You\ already\ added\ this\ certificate=You already added this certificate +Git=Git +Credential=Credential +Email=Email + Open\ folder=Open folder Export\ sort\ order=Export sort order Save\ sort\ order=Save sort order From 61097416be375d9e29c9ce945b073b81f74514ed Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Sat, 7 Oct 2023 20:35:30 -0300 Subject: [PATCH 18/48] refact credentials view to other file --- .../org/jabref/gui/git/GitCredentials.java | 32 ++++++++++++ .../gui/git/GitCredentialsDialogView.java | 50 +++++++++++++++++-- .../java/org/jabref/logic/git/GitHandler.java | 42 ++++------------ 3 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/jabref/gui/git/GitCredentials.java diff --git a/src/main/java/org/jabref/gui/git/GitCredentials.java b/src/main/java/org/jabref/gui/git/GitCredentials.java new file mode 100644 index 00000000000..84326b82030 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitCredentials.java @@ -0,0 +1,32 @@ +package org.jabref.gui.git; + +public class GitCredentials { + private String gitUsername; + private String gitPassword; + + public GitCredentials() { + this.gitUsername = null; + this.gitPassword = null; + } + + public GitCredentials(String gitUsername, String gitPassword) { + this.gitUsername = gitUsername; + this.gitPassword = gitPassword; + } + + public void setGitUsername(String gitUsername) { + this.gitUsername = gitUsername; + } + + public void setGitPassword(String gitPassword) { + this.gitPassword = gitPassword; + } + + public String getGitPassword() { + return this.gitPassword; + } + + public String getGitUsername() { + return this.gitUsername; + } +} diff --git a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java index 4683c715a8f..b73b8e7fdfc 100644 --- a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java +++ b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java @@ -11,23 +11,67 @@ import javafx.scene.control.TextInputDialog; import javafx.scene.layout.VBox; +import org.jabref.gui.DialogService; import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; +import com.airhacks.afterburner.injection.Injector; import com.airhacks.afterburner.views.ViewLoader; public class GitCredentialsDialogView extends BaseDialog { @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); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + // ViewLoader.view(this) + // .load() + // .setAsDialogPane(this); + + 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 GitCredentials getCredentials() { + dialogService.showCustomDialogAndWait(Localization.lang("Git credentials"), this.pane, this.acceptButton, this.cancelButton); + GitCredentials gitCredentials = new GitCredentials(this.inputGitUsername.getText(), this.inputGitPassword.getText()); + + return gitCredentials; + } + + public String getGitPassword() { + return this.inputGitPassword.getText(); + } + + public String getGitUsername() { + return this.inputGitUsername.getText(); } @FXML diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 8d8e56b81ef..9338d4c4f5a 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -250,45 +250,25 @@ public void pushCommitsToRemoteRepository() { git.push() .setTransportConfigCallback(transportConfigCallback) .call(); - } else { - if (this.gitPassword.equals("") || this.gitUsername.equals("")) { - if (dialogService != null) { - DialogPane pane = new DialogPane(); - VBox vBox = new VBox(); - TextField inputGitUsername = new TextField(); - PasswordField inputGitPassword = new PasswordField(); - ButtonType accept = new ButtonType(Localization.lang("Accept"), ButtonBar.ButtonData.APPLY); - ButtonType cancel = new ButtonType(Localization.lang("Cancel"), ButtonBar.ButtonData.CANCEL_CLOSE); - - vBox.getChildren().add(new Label(Localization.lang("Git username"))); - vBox.getChildren().add(inputGitUsername); - vBox.getChildren().add(new Label(Localization.lang("Git password"))); - vBox.getChildren().add(inputGitPassword); + } else if (this.gitPassword.equals("") || this.gitUsername.equals("")) { + GitCredentialsDialogView gitCredentialsDialogView = new GitCredentialsDialogView(); - pane.setContent(vBox); - - dialogService.showCustomDialogAndWait(Localization.lang("Git credentials"), pane, accept, cancel); + gitCredentialsDialogView.showGitCredentialsDialog(); - String gitUsername = inputGitUsername.getText(); - String gitPassword = inputGitPassword.getText(); + CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider( + gitCredentialsDialogView.getGitUsername(), + gitCredentialsDialogView.getGitPassword() + ); - CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); - - git.push() - .setCredentialsProvider(credentialsProvider) - .call(); - } else { - git.push() - .call(); - } + git.push() + .setCredentialsProvider(credentialsProvider) + .call(); } else { git.push() .setCredentialsProvider(this.credentialsProvider) .call(); } - } - - } catch (IOException | GitAPIException e) { + } catch (IOException | GitAPIException e) { if (e.getMessage().equals("origin: not found.")) { LOGGER.info("No remote repository detected. Push skiped."); } else { From 27b95bfaa748ff45cf666df5b5e13e2b8676ad1e Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Sat, 7 Oct 2023 22:23:39 -0300 Subject: [PATCH 19/48] fix git pref panel --- .../gui/exporter/SaveGitDatabaseAction.java | 7 +-- .../gui/git/GitCredentialsDialogView.java | 6 --- .../org/jabref/gui/git/GitPreferences.java | 51 +++++++++++++++++++ .../jabref/gui/preferences/git/GitTab.java | 8 +-- .../gui/preferences/git/GitTabViewModel.java | 35 ++++++++----- .../java/org/jabref/logic/git/GitHandler.java | 4 +- .../org/jabref/logic/git/GitPreferences.java | 38 -------------- .../jabref/preferences/JabRefPreferences.java | 15 +++--- .../preferences/PreferencesService.java | 2 +- 9 files changed, 88 insertions(+), 78 deletions(-) create mode 100644 src/main/java/org/jabref/gui/git/GitPreferences.java delete mode 100644 src/main/java/org/jabref/logic/git/GitPreferences.java diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index 89c07234572..3cf8684602a 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -26,15 +26,10 @@ public SaveGitDatabaseAction(Path filePath) { */ public boolean automaticUpdate() { try { - System.out.println(this.filePath.getParent()); - System.out.println(this.filePath.getFileName().toString()); GitHandler git = new GitHandler(this.filePath.getParent()); git.createCommitWithSingleFileOnCurrentBranch(this.filePath.getFileName().toString(), automaticCommitMsg, false); git.pushCommitsToRemoteRepository(); - } catch ( - GitAPIException | - IOException e) { - System.out.println(e.getMessage()); + } catch (GitAPIException | IOException e) { LOGGER.info("Failed to automatic git update"); } diff --git a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java index b73b8e7fdfc..84e2042130c 100644 --- a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java +++ b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java @@ -8,7 +8,6 @@ import javafx.scene.control.PasswordField; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; -import javafx.scene.control.TextInputDialog; import javafx.scene.layout.VBox; import org.jabref.gui.DialogService; @@ -16,7 +15,6 @@ import org.jabref.logic.l10n.Localization; import com.airhacks.afterburner.injection.Injector; -import com.airhacks.afterburner.views.ViewLoader; public class GitCredentialsDialogView extends BaseDialog { @@ -35,10 +33,6 @@ public GitCredentialsDialogView() { this.setTitle(Localization.lang("Git credentials")); this.dialogService = Injector.instantiateModelOrService(DialogService.class); - // ViewLoader.view(this) - // .load() - // .setAsDialogPane(this); - this.pane = new DialogPane(); VBox vBox = new VBox(); this.inputGitUsername = new TextField(); diff --git a/src/main/java/org/jabref/gui/git/GitPreferences.java b/src/main/java/org/jabref/gui/git/GitPreferences.java new file mode 100644 index 00000000000..5472e9d7d45 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitPreferences.java @@ -0,0 +1,51 @@ +package org.jabref.gui.git; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class GitPreferences { + private StringProperty username; + private StringProperty password; + + public GitPreferences(String username, String password) { + this.username = new SimpleStringProperty(username); + this.password = new SimpleStringProperty(password); + } + + public GitPreferences(StringProperty username, StringProperty password) { + this.username = username; + this.password = password; + } + + public StringProperty getUsernameProperty() { + return this.username; + } + + public String getUsername() { + return this.username.get(); + } + + public StringProperty getPasswordProperty() { + return this.password; + } + + public String getPassword() { + return this.password.get(); + } + + public void setPassword(StringProperty password) { + this.password = password; + } + + public void setPassword(String password) { + this.password = new SimpleStringProperty(password); + } + + public void setUsername(StringProperty username) { + this.username = username; + } + + public void setUsername(String username) { + this.username = new SimpleStringProperty(username); + } +} diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTab.java b/src/main/java/org/jabref/gui/preferences/git/GitTab.java index e671929c8b6..fedadb7ad85 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTab.java +++ b/src/main/java/org/jabref/gui/preferences/git/GitTab.java @@ -5,12 +5,11 @@ import javafx.scene.control.TextField; import org.jabref.gui.preferences.AbstractPreferenceTabView; -import org.jabref.gui.preferences.PreferencesTab; import org.jabref.logic.l10n.Localization; import com.airhacks.afterburner.views.ViewLoader; -public class GitTab extends AbstractPreferenceTabView implements PreferencesTab { +public class GitTab extends AbstractPreferenceTabView { @FXML private TextField username; @FXML private PasswordField password; @@ -19,6 +18,8 @@ public GitTab() { ViewLoader.view(this) .root(this) .load(); + this.username.setText(preferencesService.getGitPreferences().getUsername()); + this.password.setText(preferencesService.getGitPreferences().getPassword()); } @Override @@ -28,7 +29,6 @@ public String getTabName() { @FXML private void initialize() { - viewModel = new GitTabViewModel(preferencesService, dialogService); - username.textProperty().setValue(viewModel.getUsername().getValue()); + viewModel = new GitTabViewModel(preferencesService.getGitPreferences(), this.username, this.password); } } diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java index 7201742e12a..d189c8e91f6 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java @@ -4,33 +4,38 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; -import org.jabref.gui.DialogService; +import org.jabref.gui.git.GitPreferences; import org.jabref.gui.preferences.PreferenceTabViewModel; -import org.jabref.preferences.PreferencesService; public class GitTabViewModel implements PreferenceTabViewModel { - private final StringProperty username = new SimpleStringProperty("ooo"); - private final StringProperty password = new SimpleStringProperty(); - private final PreferencesService preferences; - private final DialogService dialogService; - //private final GitPreferences gitPreferences; + private StringProperty username = new SimpleStringProperty(); + private StringProperty password = new SimpleStringProperty(); + private GitPreferences gitPreferences; + @FXML private TextField usernameInputField; + @FXML private PasswordField passwordInputField; - public GitTabViewModel(PreferencesService preferences, DialogService dialogService) { - this.preferences = preferences; - this.dialogService = dialogService; - //this.gitPreferences = preferences.getGitPreferences(); + public GitTabViewModel(GitPreferences gitPreferences, TextField usernameInputField, PasswordField passwordInputField) { + this.gitPreferences = gitPreferences; + this.usernameInputField = usernameInputField; + this.passwordInputField = passwordInputField; } @Override public void setValues() { // TEST - username.setValue("test"); + this.username.setValue(this.usernameInputField.getText()); + this.password.setValue(this.passwordInputField.getText()); } @Override public void storeSettings() { + this.gitPreferences.setUsername(this.usernameInputField.getText()); + this.gitPreferences.setPassword(this.passwordInputField.getText()); } @Override @@ -44,6 +49,10 @@ public List getRestartWarnings() { } public StringProperty getUsername() { - return username; + return this.username; + } + + public StringProperty getPassword() { + return this.password; } } diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 9338d4c4f5a..bcecb15eec9 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -193,9 +193,7 @@ public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String // Add all removed files to index if (!status.getMissing().isEmpty()) { - RmCommand removeCommand = git.rm() - .setCached(true); - System.out.println("1----"); + RmCommand removeCommand = git.rm().setCached(true); status.getMissing().forEach(removeCommand::addFilepattern); removeCommand.call(); } diff --git a/src/main/java/org/jabref/logic/git/GitPreferences.java b/src/main/java/org/jabref/logic/git/GitPreferences.java deleted file mode 100644 index 88aa5e13494..00000000000 --- a/src/main/java/org/jabref/logic/git/GitPreferences.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.jabref.logic.git; - -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; - -public class GitPreferences { - private StringProperty username = new SimpleStringProperty(); - private StringProperty password = new SimpleStringProperty(); - - public GitPreferences(String username, String password) { - //this.username.setValue(username); - //this.password.set(password); - } - - public String getUsername() { - return username.toString(); - } - - public StringProperty usernameProperty() { - return username; - } - - public void setUsername(String username) { - this.username.set(username); - } - - public final String getPassword() { - return password.getValue(); - } - - public StringProperty passwordProperty() { - return password; - } - - public void setPassword(String password) { - this.password.set(password); - } -} diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index f0cf231e958..8d405006e39 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -69,7 +69,7 @@ import org.jabref.logic.exporter.MetaDataSerializer; import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.exporter.TemplateExporter; -import org.jabref.logic.git.GitPreferences; +import org.jabref.gui.git.GitPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.fetcher.DoiFetcher; @@ -378,6 +378,7 @@ public class JabRefPreferences implements PreferencesService { // Git private static final String GIT_USERNAME = "gitUsername"; + private static final String GIT_PASSWORD = "gitPassword"; // Web search private static final String FETCHER_CUSTOM_KEY_NAMES = "fetcherCustomKeyNames"; @@ -2807,14 +2808,14 @@ public GitPreferences getGitPreferences() { } gitPreferences = new GitPreferences( - get(GIT_USERNAME), - getGitPassword()); + get(GIT_USERNAME), + get(GIT_PASSWORD) + ); - return gitPreferences; - } + EasyBind.listen(gitPreferences.getUsernameProperty(), (obs, oldValue, newValue) -> put(GIT_USERNAME, newValue)); + EasyBind.listen(gitPreferences.getPasswordProperty(), (obs, oldValue, newValue) -> put(GIT_PASSWORD, newValue)); - private String getGitPassword() { - return ""; + return gitPreferences; } //************************************************************************************************************* // Importer preferences diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index ea5104c8c2d..33f2063543b 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -16,7 +16,7 @@ import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; import org.jabref.logic.exporter.SelfContainedSaveConfiguration; -import org.jabref.logic.git.GitPreferences; +import org.jabref.gui.git.GitPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.fetcher.GrobidPreferences; From e935882b0f24b673d8583fd25314572decdd2ec9 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Sat, 7 Oct 2023 22:39:29 -0300 Subject: [PATCH 20/48] user git preferences when pushing new commits --- .../java/org/jabref/logic/git/GitHandler.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index bcecb15eec9..38cd3cd8417 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -9,8 +9,10 @@ import org.jabref.gui.DialogService; import org.jabref.gui.git.GitCredentialsDialogView; +import org.jabref.gui.git.GitPreferences; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; +import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.injection.Injector; @@ -48,8 +50,8 @@ public class GitHandler { final File repositoryPathAsFile; String gitUsername = Optional.ofNullable(System.getenv("GIT_EMAIL")).orElse(""); String gitPassword = Optional.ofNullable(System.getenv("GIT_PW")).orElse(""); - final CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); - private DialogService dialogService; + CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); + private GitPreferences gitPreferences; /** * Initialize the handler for the given repository @@ -59,7 +61,8 @@ public class GitHandler { public GitHandler(Path repositoryPath) { this.repositoryPath = repositoryPath; this.repositoryPathAsFile = this.repositoryPath.toFile(); - this.dialogService = Injector.instantiateModelOrService(DialogService.class); + this.gitPreferences = Injector.instantiateModelOrService(PreferencesService.class).getGitPreferences(); + if (!isGitRepository()) { try { Git.init() @@ -243,6 +246,14 @@ public void pushCommitsToRemoteRepository() { git.verifySignature(); + if (this.gitPreferences.getUsername() != null && this.gitPreferences.getPassword() != null) { + this.gitUsername = this.gitPreferences.getUsername(); + this.gitPassword = this.gitPreferences.getPassword(); + this.credentialsProvider = new UsernamePasswordCredentialsProvider(this.gitUsername, this.gitPassword); + } + + + if (isSshRemoteRepository) { TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); git.push() @@ -253,20 +264,20 @@ public void pushCommitsToRemoteRepository() { gitCredentialsDialogView.showGitCredentialsDialog(); - CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider( + this.credentialsProvider = new UsernamePasswordCredentialsProvider( gitCredentialsDialogView.getGitUsername(), gitCredentialsDialogView.getGitPassword() ); - git.push() - .setCredentialsProvider(credentialsProvider) - .call(); - } else { git.push() .setCredentialsProvider(this.credentialsProvider) .call(); - } - } catch (IOException | GitAPIException e) { + } else { + git.push() + .setCredentialsProvider(this.credentialsProvider) + .call(); + } + } catch (IOException | GitAPIException e) { if (e.getMessage().equals("origin: not found.")) { LOGGER.info("No remote repository detected. Push skiped."); } else { From 344775b611e4f212825b0f4e86daa7a52a742fb3 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Sat, 7 Oct 2023 23:15:20 -0300 Subject: [PATCH 21/48] remove custom method used for validation --- .../jabref/gui/preferences/git/GitTab.java | 7 ++-- .../gui/preferences/git/GitTabViewModel.java | 31 +++++++-------- .../org/jabref/logic/git/GitPreferences.java | 38 +++++++++++++++++++ 3 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/jabref/logic/git/GitPreferences.java diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTab.java b/src/main/java/org/jabref/gui/preferences/git/GitTab.java index fedadb7ad85..7af3bc0f09b 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTab.java +++ b/src/main/java/org/jabref/gui/preferences/git/GitTab.java @@ -18,8 +18,6 @@ public GitTab() { ViewLoader.view(this) .root(this) .load(); - this.username.setText(preferencesService.getGitPreferences().getUsername()); - this.password.setText(preferencesService.getGitPreferences().getPassword()); } @Override @@ -29,6 +27,9 @@ public String getTabName() { @FXML private void initialize() { - viewModel = new GitTabViewModel(preferencesService.getGitPreferences(), this.username, this.password); + viewModel = new GitTabViewModel(preferencesService.getGitPreferences()); + + username.textProperty().bindBidirectional(viewModel.getUsernameProperty()); + password.textProperty().bindBidirectional(viewModel.getPasswordProperty()); } } diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java index d189c8e91f6..7701a334b36 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java @@ -4,9 +4,6 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.fxml.FXML; -import javafx.scene.control.PasswordField; -import javafx.scene.control.TextField; import org.jabref.gui.git.GitPreferences; import org.jabref.gui.preferences.PreferenceTabViewModel; @@ -16,26 +13,22 @@ public class GitTabViewModel implements PreferenceTabViewModel { private StringProperty username = new SimpleStringProperty(); private StringProperty password = new SimpleStringProperty(); private GitPreferences gitPreferences; - @FXML private TextField usernameInputField; - @FXML private PasswordField passwordInputField; - public GitTabViewModel(GitPreferences gitPreferences, TextField usernameInputField, PasswordField passwordInputField) { + public GitTabViewModel(GitPreferences gitPreferences) { this.gitPreferences = gitPreferences; - this.usernameInputField = usernameInputField; - this.passwordInputField = passwordInputField; + + this.username = gitPreferences.getUsernameProperty(); + this.password = gitPreferences.getPasswordProperty(); } @Override public void setValues() { - // TEST - this.username.setValue(this.usernameInputField.getText()); - this.password.setValue(this.passwordInputField.getText()); + this.username.setValue(this.gitPreferences.getUsername()); + this.password.setValue(this.gitPreferences.getPassword()); } @Override public void storeSettings() { - this.gitPreferences.setUsername(this.usernameInputField.getText()); - this.gitPreferences.setPassword(this.passwordInputField.getText()); } @Override @@ -48,11 +41,19 @@ public List getRestartWarnings() { return PreferenceTabViewModel.super.getRestartWarnings(); } - public StringProperty getUsername() { + public String getUsername() { + return this.username.get(); + } + + public StringProperty getUsernameProperty() { return this.username; } - public StringProperty getPassword() { + public String getPassword() { + return this.password.get(); + } + + public StringProperty getPasswordProperty() { return this.password; } } diff --git a/src/main/java/org/jabref/logic/git/GitPreferences.java b/src/main/java/org/jabref/logic/git/GitPreferences.java new file mode 100644 index 00000000000..88aa5e13494 --- /dev/null +++ b/src/main/java/org/jabref/logic/git/GitPreferences.java @@ -0,0 +1,38 @@ +package org.jabref.logic.git; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class GitPreferences { + private StringProperty username = new SimpleStringProperty(); + private StringProperty password = new SimpleStringProperty(); + + public GitPreferences(String username, String password) { + //this.username.setValue(username); + //this.password.set(password); + } + + public String getUsername() { + return username.toString(); + } + + public StringProperty usernameProperty() { + return username; + } + + public void setUsername(String username) { + this.username.set(username); + } + + public final String getPassword() { + return password.getValue(); + } + + public StringProperty passwordProperty() { + return password; + } + + public void setPassword(String password) { + this.password.set(password); + } +} From 7d0b6db51a21a5be4cfa3260526bf55d901b0c65 Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Mon, 16 Oct 2023 18:12:38 -0300 Subject: [PATCH 22/48] git preferences and test --- .../gui/exporter/SaveDatabaseAction.java | 2 +- .../gui/exporter/SaveGitDatabaseAction.java | 7 +++-- .../java/org/jabref/logic/git/GitHandler.java | 31 +++++-------------- .../org/jabref/logic/git/GitHandlerTest.java | 12 +++++++ 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index c40e7b80cf7..955b3f94ebe 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -245,7 +245,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { dialogService.notify(Localization.lang("Library saved")); if (success) { - SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath); + SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath, preferences); saveGit.automaticUpdate(); } return success; diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java index 3cf8684602a..7f31b5a5541 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java @@ -3,8 +3,8 @@ import java.io.IOException; import java.nio.file.Path; -import org.jabref.gui.DialogService; import org.jabref.logic.git.GitHandler; +import org.jabref.preferences.PreferencesService; import org.eclipse.jgit.api.errors.GitAPIException; import org.slf4j.Logger; @@ -14,9 +14,11 @@ public class SaveGitDatabaseAction { static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); final Path filePath; final String automaticCommitMsg = "Automatic update via JabRef"; + private final PreferencesService preferences; - public SaveGitDatabaseAction(Path filePath) { + public SaveGitDatabaseAction(Path filePath, PreferencesService preferences) { this.filePath = filePath; + this.preferences = preferences; } /** @@ -27,6 +29,7 @@ public SaveGitDatabaseAction(Path filePath) { public boolean automaticUpdate() { try { GitHandler git = new GitHandler(this.filePath.getParent()); + git.setGitPreferences(preferences.getGitPreferences()); git.createCommitWithSingleFileOnCurrentBranch(this.filePath.getFileName().toString(), automaticCommitMsg, false); git.pushCommitsToRemoteRepository(); } catch (GitAPIException | IOException e) { diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 38cd3cd8417..d6742163217 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -7,31 +7,15 @@ import java.nio.file.Path; import java.util.Optional; -import org.jabref.gui.DialogService; import org.jabref.gui.git.GitCredentialsDialogView; import org.jabref.gui.git.GitPreferences; -import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; -import org.jabref.preferences.PreferencesService; - -import com.airhacks.afterburner.injection.Injector; - -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 javafx.scene.text.Text; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.TransportConfigCallback; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.merge.MergeStrategy; @@ -61,8 +45,7 @@ public class GitHandler { public GitHandler(Path repositoryPath) { this.repositoryPath = repositoryPath; this.repositoryPathAsFile = this.repositoryPath.toFile(); - this.gitPreferences = Injector.instantiateModelOrService(PreferencesService.class).getGitPreferences(); - + if (!isGitRepository()) { try { Git.init() @@ -183,12 +166,12 @@ public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) */ public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String commitMessage, boolean amend) throws IOException, NoWorkTreeException, GitAPIException { boolean commitCreated = false; - + Git git = Git.open(this.repositoryPathAsFile); Status status = git.status().call(); if (!status.isClean()) { commitCreated = true; - + // Add new and changed files to index git.add() .addFilepattern(filename) @@ -206,7 +189,7 @@ public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String .setMessage(commitMessage) .call(); } - + return commitCreated; } @@ -251,8 +234,6 @@ public void pushCommitsToRemoteRepository() { this.gitPassword = this.gitPreferences.getPassword(); this.credentialsProvider = new UsernamePasswordCredentialsProvider(this.gitUsername, this.gitPassword); } - - if (isSshRemoteRepository) { TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); @@ -321,4 +302,8 @@ public String getCurrentlyCheckedOutBranch() { return ""; } } + + public void setGitPreferences(GitPreferences gitPreferences) { + this.gitPreferences = gitPreferences; + } } diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index d5dd4c694c3..d0c4ffc2011 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -5,6 +5,9 @@ import java.nio.file.Path; import java.util.Iterator; +import org.jabref.gui.git.GitPreferences; +import org.jabref.preferences.PreferencesService; + import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.AnyObjectId; @@ -15,15 +18,24 @@ import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class GitHandlerTest { @TempDir Path repositoryPath; private GitHandler gitHandler; + private GitPreferences gitPreferences; + @BeforeEach public void setUpGitHandler() { + gitPreferences = new GitPreferences("testUser", "testPassword"); + PreferencesService preferences = mock(PreferencesService.class); + when(preferences.getGitPreferences()).thenReturn(gitPreferences); + gitHandler = new GitHandler(repositoryPath); + gitHandler.setGitPreferences(preferences.getGitPreferences()); } @Test From f3313b1d655b6d7acc1b6131a37ea67ea00573b1 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:23:51 -0300 Subject: [PATCH 23/48] add unit test for commiting creating --- .../java/org/jabref/logic/git/GitHandler.java | 2 ++ .../org/jabref/logic/git/GitHandlerTest.java | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index d6742163217..dca3c08143c 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -65,6 +65,8 @@ public GitHandler(Path repositoryPath) { } } } catch (GitAPIException | IOException e) { + System.out.println(e); + System.out.println(e.getMessage()); LOGGER.error("Initialization failed"); } } diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index d0c4ffc2011..6162d0f3540 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -10,9 +10,11 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.util.SystemReader; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -33,7 +35,7 @@ public void setUpGitHandler() { gitPreferences = new GitPreferences("testUser", "testPassword"); PreferencesService preferences = mock(PreferencesService.class); when(preferences.getGitPreferences()).thenReturn(gitPreferences); - + gitHandler = new GitHandler(repositoryPath); gitHandler.setGitPreferences(preferences.getGitPreferences()); } @@ -63,6 +65,22 @@ void createCommitOnCurrentBranch() throws IOException, GitAPIException { } } + @Test + void createCommitWithSingleFileOnCurrentBranch() throws IOException, NoHeadException, GitAPIException { + try (Git git = Git.open(repositoryPath.toFile())) { + // Create commit + Files.createFile(Path.of(repositoryPath.toString(), "Test.txt")); + gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false); + + AnyObjectId head = git.getRepository().resolve(Constants.HEAD); + Iterator log = git.log() + .add(head) + .call().iterator(); + assertEquals("TestCommit", log.next().getFullMessage()); + assertEquals("Initial commit", log.next().getFullMessage()); + } + } + @Test void getCurrentlyCheckedOutBranch() throws IOException { assertEquals("main", gitHandler.getCurrentlyCheckedOutBranch()); From 66869054f05a732372cd497df1e0e8fa1bfa0a9d Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Tue, 17 Oct 2023 22:33:43 -0300 Subject: [PATCH 24/48] unit test git prefernces and credentials --- .../jabref/gui/git/GitCredentialsTest.java | 41 +++++++++++ .../jabref/gui/git/GitPreferencesTest.java | 68 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/test/java/org/jabref/gui/git/GitCredentialsTest.java create mode 100644 src/test/java/org/jabref/gui/git/GitPreferencesTest.java diff --git a/src/test/java/org/jabref/gui/git/GitCredentialsTest.java b/src/test/java/org/jabref/gui/git/GitCredentialsTest.java new file mode 100644 index 00000000000..2d1c3145f70 --- /dev/null +++ b/src/test/java/org/jabref/gui/git/GitCredentialsTest.java @@ -0,0 +1,41 @@ +package org.jabref.gui.git; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GitCredentialsTest { + @Test + void GitCredentials() { + GitCredentials gitCredentials = new GitCredentials(); + String gitPassword = gitCredentials.getGitPassword(); + String gitUsername = gitCredentials.getGitUsername(); + + assertEquals(null, gitUsername); + assertEquals(null, gitPassword); + } + + @Test + void GitCredentialsWithUsernameAndPassword() { + GitCredentials gitCredentials = new GitCredentials("testUsername", "testPassword"); + String gitPassword = gitCredentials.getGitPassword(); + String gitUsername = gitCredentials.getGitUsername(); + + assertEquals("testUsername", gitUsername); + assertEquals("testPassword", gitPassword); + } + + @Test + void GitCredentialsGettersAndSetters() { + GitCredentials gitCredentials = new GitCredentials(); + + gitCredentials.setGitPassword("testPassword"); + gitCredentials.setGitUsername("testUsername"); + + String gitPassword = gitCredentials.getGitPassword(); + String gitUsername = gitCredentials.getGitUsername(); + + assertEquals("testUsername", gitUsername); + assertEquals("testPassword", gitPassword); + } +} diff --git a/src/test/java/org/jabref/gui/git/GitPreferencesTest.java b/src/test/java/org/jabref/gui/git/GitPreferencesTest.java new file mode 100644 index 00000000000..fae3e8bff35 --- /dev/null +++ b/src/test/java/org/jabref/gui/git/GitPreferencesTest.java @@ -0,0 +1,68 @@ +package org.jabref.gui.git; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class GitPreferencesTest { + @Test + void GitCredentials() { + String testUsername = "testUsername"; + String testPassword = "testPassword"; + GitPreferences gitPreferences = new GitPreferences(testUsername, testPassword); + + assertEquals(testUsername, gitPreferences.getUsername()); + assertEquals(testPassword, gitPreferences.getPassword()); + assertEquals(testUsername, gitPreferences.getUsernameProperty().get()); + assertEquals(testPassword, gitPreferences.getPasswordProperty().get()); + } + + @Test + void GitCredentialsWithGitProperty() { + String testUsername = "testUsername"; + String testPassword = "testPassword"; + StringProperty gitPasswordProperty = new SimpleStringProperty(testPassword); + StringProperty gitUsernameProperty = new SimpleStringProperty(testUsername); + GitPreferences gitPreferences = new GitPreferences(gitUsernameProperty, gitPasswordProperty); + + assertEquals(testUsername, gitPreferences.getUsername()); + assertEquals(testPassword, gitPreferences.getPassword()); + assertEquals(testUsername, gitPreferences.getUsernameProperty().get()); + assertEquals(testPassword, gitPreferences.getPasswordProperty().get()); + } + + @Test + void GitCredentialsWithGitPropertyGetterAndSetters() { + String testUsername = "testUsername"; + String testPassword = "testPassword"; + String testString = "updated"; + StringProperty gitPasswordProperty = new SimpleStringProperty(testPassword); + StringProperty gitUsernameProperty = new SimpleStringProperty(testUsername); + GitPreferences gitPreferences = new GitPreferences(gitUsernameProperty, gitPasswordProperty); + gitPreferences.setUsername(testUsername + testString); + gitPreferences.setPassword(testPassword + testString); + + assertEquals(testUsername + testString, gitPreferences.getUsername()); + assertEquals(testPassword + testString, gitPreferences.getPassword()); + assertEquals(testUsername + testString, gitPreferences.getUsernameProperty().get()); + assertEquals(testPassword + testString, gitPreferences.getPasswordProperty().get()); + } + + @Test + void GitCredentialsWithGitGetterAndSetters() { + String testUsername = "testUsername"; + String testPassword = "testPassword"; + String testString = "updated"; + GitPreferences gitPreferences = new GitPreferences(testUsername, testPassword); + gitPreferences.setUsername(testUsername + testString); + gitPreferences.setPassword(testPassword + testString); + + assertEquals(testUsername + testString, gitPreferences.getUsername()); + assertEquals(testPassword + testString, gitPreferences.getPassword()); + assertEquals(testUsername + testString, gitPreferences.getUsernameProperty().get()); + assertEquals(testPassword + testString, gitPreferences.getPasswordProperty().get()); + } +} From a1022572f9eb478f787d4339176b460e2b0c5c75 Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Wed, 18 Oct 2023 21:58:58 -0300 Subject: [PATCH 25/48] push without auth --- build.gradle | 7 +- .../java/org/jabref/logic/git/GitHandler.java | 14 ++++ .../org/jabref/logic/git/GitHandlerTest.java | 80 ++++++++++++++++++- 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 3f5f845f993..a676e3aacb1 100644 --- a/build.gradle +++ b/build.gradle @@ -140,8 +140,11 @@ 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', version: '6.7.0.202309050840-r') - 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.7.0.202309050840-r' + 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.http.server', version: '6.7.0.202309050840-r' + implementation 'org.eclipse.jetty:jetty-servlet:9.4.51.v20230217' + compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' configurations.all { exclude group: "commons-logging", module: "commons-logging" } diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index dca3c08143c..3c8abaafff0 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -18,6 +18,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; @@ -308,4 +309,17 @@ public String getCurrentlyCheckedOutBranch() { public void setGitPreferences(GitPreferences gitPreferences) { this.gitPreferences = gitPreferences; } + + public void setRemoteUrl(String url) { + try { + Git git = Git.open(this.repositoryPathAsFile); + StoredConfig config = git.getRepository().getConfig(); + config.setString("remote", "origin", "url", url); + config.save(); + } catch ( + IOException e) { + LOGGER.info("Failed to set git remote url"); + throw new RuntimeException(e); + } + } } diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index 6162d0f3540..aab2f0f4109 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -1,5 +1,6 @@ package org.jabref.logic.git; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -8,13 +9,18 @@ import org.jabref.gui.git.GitPreferences; import org.jabref.preferences.PreferencesService; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.NoHeadException; +import org.eclipse.jgit.http.server.GitServlet; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.util.SystemReader; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -27,15 +33,39 @@ class GitHandlerTest { @TempDir Path repositoryPath; private GitHandler gitHandler; - private GitPreferences gitPreferences; + private final String port = "8080"; + private final String localUrl = "http://localhost:" + port + '/'; + + + private static Repository createRepository() throws IOException { + File localPath = File.createTempFile("TestGitRepository", ""); + if(!localPath.delete()) { + throw new IOException("Could not delete temporary file " + localPath); + } + if(!localPath.mkdirs()) { + throw new IOException("Could not create directory " + localPath); + } + Repository repository = FileRepositoryBuilder.create(new File(localPath, ".git")); + repository.create(); + return repository; + } + private static Path createFolder() throws IOException { + File localPath = File.createTempFile("TestGitRepository", ""); + if(!localPath.delete()) { + throw new IOException("Could not delete temporary file " + localPath); + } + if(!localPath.mkdirs()) { + throw new IOException("Could not create directory " + localPath); + } + return localPath.toPath(); + } @BeforeEach - public void setUpGitHandler() { + public void setUpGitHandler() throws IOException { gitPreferences = new GitPreferences("testUser", "testPassword"); PreferencesService preferences = mock(PreferencesService.class); when(preferences.getGitPreferences()).thenReturn(gitPreferences); - gitHandler = new GitHandler(repositoryPath); gitHandler.setGitPreferences(preferences.getGitPreferences()); } @@ -85,4 +115,46 @@ void createCommitWithSingleFileOnCurrentBranch() throws IOException, NoHeadExcep void getCurrentlyCheckedOutBranch() throws IOException { assertEquals("main", gitHandler.getCurrentlyCheckedOutBranch()); } + + @Test + void pushSingleFile () throws Exception { + // Server + Repository repository = createRepository(); + repository.getConfig().setString("http", null, "jabrefGitTest", "true"); + GitServlet gs = new GitServlet(); + gs.setRepositoryResolver((req, name) -> { + repository.incrementOpen(); + return repository; + }); + Server server = new Server(8080); + ServletHandler handler = new ServletHandler(); + server.setHandler(handler); + ServletHolder holder = new ServletHolder(gs); + handler.addServletWithMapping(holder, "/*"); + server.start(); + + //Clone + Path clonedRepPath = createFolder(); + Git.cloneRepository() + .setURI( "http://localhost:8080/jabrefGitTest") + .setDirectory(clonedRepPath.toFile()) + .call(); + + //Add files + Files.createFile(Path.of(clonedRepPath.toString(), "bib_1.bib")); + Files.createFile(Path.of(clonedRepPath.toString(), "bib_2.bib")); + + //Commit + GitHandler git = new GitHandler(clonedRepPath); + git.createCommitWithSingleFileOnCurrentBranch(clonedRepPath.toString() + "/bib_1.bib", "PushSingleFile", false); + + //Push + gitPreferences = new GitPreferences("", ""); + PreferencesService preferences = mock(PreferencesService.class); + when(preferences.getGitPreferences()).thenReturn(gitPreferences); + git.setGitPreferences(preferences.getGitPreferences()); + git.pushCommitsToRemoteRepository(); + + server.stop(); + } } From a2a5a71b8de823a0b3bef31a4185cf6496b5bdfe Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Thu, 19 Oct 2023 00:41:51 -0300 Subject: [PATCH 26/48] add test git push with credentials --- build.gradle | 1 + .../org/jabref/logic/git/GitHandlerTest.java | 58 ++++++++++++++++--- .../org/jabref/logic/git/HelloServlet.java | 33 +++++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/jabref/logic/git/HelloServlet.java diff --git a/build.gradle b/build.gradle index a676e3aacb1..ecb94f5684c 100644 --- a/build.gradle +++ b/build.gradle @@ -144,6 +144,7 @@ dependencies { 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.http.server', version: '6.7.0.202309050840-r' implementation 'org.eclipse.jetty:jetty-servlet:9.4.51.v20230217' + implementation 'org.eclipse.jetty:jetty-security:9.4.51.v20230217' compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' configurations.all { exclude group: "commons-logging", module: "commons-logging" diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index aab2f0f4109..774a2215f98 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -6,12 +6,19 @@ import java.nio.file.Path; import java.util.Iterator; +import org.eclipse.jetty.security.*; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.jabref.gui.git.GitPreferences; import org.jabref.preferences.PreferencesService; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Credential; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.NoHeadException; @@ -118,25 +125,32 @@ void getCurrentlyCheckedOutBranch() throws IOException { @Test void pushSingleFile () throws Exception { + String username = "test"; + String password = "test"; + CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password); // Server Repository repository = createRepository(); repository.getConfig().setString("http", null, "jabrefGitTest", "true"); + repository.getConfig().setString("remote", "origin", "url", "http://localhost:8080/"); + repository.getConfig().setBoolean("http", null, "receivepack", true); GitServlet gs = new GitServlet(); gs.setRepositoryResolver((req, name) -> { repository.incrementOpen(); return repository; }); Server server = new Server(8080); - ServletHandler handler = new ServletHandler(); - server.setHandler(handler); - ServletHolder holder = new ServletHolder(gs); - handler.addServletWithMapping(holder, "/*"); - server.start(); + ServletContextHandler context = new ServletContextHandler(server, "/*", ServletContextHandler.SESSIONS); + context.setSecurityHandler(basicAuth(username, password, "Private!")); + context.addServlet(new ServletHolder(gs), "/"); + server.setHandler(context); + server.start(); + server.join(); //Clone Path clonedRepPath = createFolder(); Git.cloneRepository() - .setURI( "http://localhost:8080/jabrefGitTest") + .setCredentialsProvider(credentialsProvider) + .setURI( "http://localhost:8080/") .setDirectory(clonedRepPath.toFile()) .call(); @@ -149,7 +163,7 @@ void pushSingleFile () throws Exception { git.createCommitWithSingleFileOnCurrentBranch(clonedRepPath.toString() + "/bib_1.bib", "PushSingleFile", false); //Push - gitPreferences = new GitPreferences("", ""); + gitPreferences = new GitPreferences(username, password); PreferencesService preferences = mock(PreferencesService.class); when(preferences.getGitPreferences()).thenReturn(gitPreferences); git.setGitPreferences(preferences.getGitPreferences()); @@ -157,4 +171,34 @@ void pushSingleFile () throws Exception { server.stop(); } + + private static final SecurityHandler basicAuth(String username, String password, String realm) { + + HashLoginService l = new HashLoginService(); + UserStore userStore = new UserStore(); + String[] roles = new String[] {"user"}; + Credential credential = Credential.getCredential(password); + userStore.addUser(username, credential, roles); + l.setUserStore(userStore); + l.setName(realm); + CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password); + + Constraint constraint = new Constraint(); + constraint.setName(Constraint.__BASIC_AUTH); + constraint.setRoles(new String[]{"user"}); + constraint.setAuthenticate(true); + + ConstraintMapping cm = new ConstraintMapping(); + cm.setConstraint(constraint); + cm.setPathSpec("/*"); + + ConstraintSecurityHandler csh = new ConstraintSecurityHandler(); + csh.setAuthenticator(new BasicAuthenticator()); + csh.setRealmName("myrealm"); + csh.addConstraintMapping(cm); + csh.setLoginService(l); + + return csh; + + } } diff --git a/src/test/java/org/jabref/logic/git/HelloServlet.java b/src/test/java/org/jabref/logic/git/HelloServlet.java new file mode 100644 index 00000000000..fcaacc7d658 --- /dev/null +++ b/src/test/java/org/jabref/logic/git/HelloServlet.java @@ -0,0 +1,33 @@ +package org.jabref.logic.git; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class HelloServlet extends HttpServlet +{ + final String greeting; + + public HelloServlet() + { + this("Hello"); + } + + public HelloServlet( String greeting ) + { + this.greeting = greeting; + } + + @Override + protected void doGet( HttpServletRequest request, + HttpServletResponse response ) throws ServletException, + IOException + { + response.setContentType("text/html"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().println( + "

" + greeting + " from HelloServlet

"); + } +} From fdebffa3cc6cea21d0400b08384d3bb53b8eb121 Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Thu, 19 Oct 2023 19:28:27 -0300 Subject: [PATCH 27/48] git push without write on disk --- .../org/jabref/logic/git/GitHandlerTest.java | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index 774a2215f98..69d9f5582d9 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -6,19 +6,20 @@ import java.nio.file.Path; import java.util.Iterator; -import org.eclipse.jetty.security.*; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jgit.transport.CredentialsProvider; -import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.jabref.gui.git.GitPreferences; import org.jabref.preferences.PreferencesService; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.HashLoginService; +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.security.UserStore; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Credential; -import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.NoHeadException; @@ -28,6 +29,9 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -45,7 +49,7 @@ class GitHandlerTest { private final String localUrl = "http://localhost:" + port + '/'; - private static Repository createRepository() throws IOException { + private static Repository createRepository() throws IOException, GitAPIException { File localPath = File.createTempFile("TestGitRepository", ""); if(!localPath.delete()) { throw new IOException("Could not delete temporary file " + localPath); @@ -55,6 +59,7 @@ private static Repository createRepository() throws IOException { } Repository repository = FileRepositoryBuilder.create(new File(localPath, ".git")); repository.create(); + repository.getConfig().setBoolean("http", null, "receivepack", true); return repository; } private static Path createFolder() throws IOException { @@ -107,7 +112,7 @@ void createCommitWithSingleFileOnCurrentBranch() throws IOException, NoHeadExcep try (Git git = Git.open(repositoryPath.toFile())) { // Create commit Files.createFile(Path.of(repositoryPath.toString(), "Test.txt")); - gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false); + Assertions.assertTrue(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false)); AnyObjectId head = git.getRepository().resolve(Constants.HEAD); Iterator log = git.log() @@ -115,6 +120,7 @@ void createCommitWithSingleFileOnCurrentBranch() throws IOException, NoHeadExcep .call().iterator(); assertEquals("TestCommit", log.next().getFullMessage()); assertEquals("Initial commit", log.next().getFullMessage()); + Assertions.assertFalse(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false)); } } @@ -127,30 +133,17 @@ void getCurrentlyCheckedOutBranch() throws IOException { void pushSingleFile () throws Exception { String username = "test"; String password = "test"; - CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password); + // Server Repository repository = createRepository(); - repository.getConfig().setString("http", null, "jabrefGitTest", "true"); - repository.getConfig().setString("remote", "origin", "url", "http://localhost:8080/"); - repository.getConfig().setBoolean("http", null, "receivepack", true); - GitServlet gs = new GitServlet(); - gs.setRepositoryResolver((req, name) -> { - repository.incrementOpen(); - return repository; - }); - Server server = new Server(8080); - ServletContextHandler context = new ServletContextHandler(server, "/*", ServletContextHandler.SESSIONS); - context.setSecurityHandler(basicAuth(username, password, "Private!")); - context.addServlet(new ServletHolder(gs), "/"); - server.setHandler(context); + Server server = createServer(username, password, repository); - server.start(); - server.join(); //Clone + CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password); Path clonedRepPath = createFolder(); Git.cloneRepository() .setCredentialsProvider(credentialsProvider) - .setURI( "http://localhost:8080/") + .setURI( "http://localhost:8080/repoTest") .setDirectory(clonedRepPath.toFile()) .call(); @@ -160,7 +153,7 @@ void pushSingleFile () throws Exception { //Commit GitHandler git = new GitHandler(clonedRepPath); - git.createCommitWithSingleFileOnCurrentBranch(clonedRepPath.toString() + "/bib_1.bib", "PushSingleFile", false); + Assertions.assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_1.bib", "PushSingleFile", false)); //Push gitPreferences = new GitPreferences(username, password); @@ -169,6 +162,7 @@ void pushSingleFile () throws Exception { git.setGitPreferences(preferences.getGitPreferences()); git.pushCommitsToRemoteRepository(); + Assertions.assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_2.bib", "PushSingleFile", false)); server.stop(); } @@ -201,4 +195,18 @@ private static final SecurityHandler basicAuth(String username, String password, return csh; } + + private Server createServer(String username, String password, Repository repository) { + GitServlet gs = new GitServlet(); + gs.setRepositoryResolver((req, name) -> { + repository.incrementOpen(); + return repository; + }); + Server server = new Server(8080); + ServletContextHandler context = new ServletContextHandler(server, "/*", ServletContextHandler.SESSIONS); + context.setSecurityHandler(basicAuth(username, password, "Private!")); + context.addServlet(new ServletHolder(gs), "/*"); + server.setHandler(context); + return server; + } } From 45cd4332dadc970f12d2f18e1df181b09f94366c Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Mon, 23 Oct 2023 08:37:04 -0300 Subject: [PATCH 28/48] fix: start server --- src/test/java/org/jabref/logic/git/GitHandlerTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index 69d9f5582d9..2d0eb39a348 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -137,6 +137,7 @@ void pushSingleFile () throws Exception { // Server Repository repository = createRepository(); Server server = createServer(username, password, repository); + server.start(); //Clone CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password); From 35949a950d22693383b04efd28ca617cf3dc5632 Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Mon, 23 Oct 2023 12:41:58 -0300 Subject: [PATCH 29/48] fix: contextPath and req name --- .../org/jabref/logic/git/GitHandlerTest.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index 2d0eb39a348..fd256918f18 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; +import java.util.Objects; import org.jabref.gui.git.GitPreferences; import org.jabref.preferences.PreferencesService; @@ -45,8 +46,6 @@ class GitHandlerTest { Path repositoryPath; private GitHandler gitHandler; private GitPreferences gitPreferences; - private final String port = "8080"; - private final String localUrl = "http://localhost:" + port + '/'; private static Repository createRepository() throws IOException, GitAPIException { @@ -144,7 +143,7 @@ void pushSingleFile () throws Exception { Path clonedRepPath = createFolder(); Git.cloneRepository() .setCredentialsProvider(credentialsProvider) - .setURI( "http://localhost:8080/repoTest") + .setURI( "http://localhost:8080/repoTest/.git") .setDirectory(clonedRepPath.toFile()) .call(); @@ -164,6 +163,9 @@ void pushSingleFile () throws Exception { git.pushCommitsToRemoteRepository(); Assertions.assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_2.bib", "PushSingleFile", false)); + + Git.lsRemoteRepository(); + server.stop(); } @@ -200,11 +202,14 @@ private static final SecurityHandler basicAuth(String username, String password, private Server createServer(String username, String password, Repository repository) { GitServlet gs = new GitServlet(); gs.setRepositoryResolver((req, name) -> { - repository.incrementOpen(); - return repository; + if (Objects.equals(name, ".git")) { + repository.incrementOpen(); + return repository; + } + return null; }); Server server = new Server(8080); - ServletContextHandler context = new ServletContextHandler(server, "/*", ServletContextHandler.SESSIONS); + ServletContextHandler context = new ServletContextHandler(server, "/repoTest", ServletContextHandler.SESSIONS); context.setSecurityHandler(basicAuth(username, password, "Private!")); context.addServlet(new ServletHolder(gs), "/*"); server.setHandler(context); From 72c753248d02f6e9fd10f28a6f95d04a6c3bd828 Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Mon, 23 Oct 2023 12:56:21 -0300 Subject: [PATCH 30/48] fix: pushSingleFile --- .../java/org/jabref/logic/git/GitHandlerTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index fd256918f18..36669005525 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -59,6 +59,8 @@ private static Repository createRepository() throws IOException, GitAPIException Repository repository = FileRepositoryBuilder.create(new File(localPath, ".git")); repository.create(); repository.getConfig().setBoolean("http", null, "receivepack", true); + Git git = new Git(repository); + git.commit().setMessage("Initial commit").call(); return repository; } private static Path createFolder() throws IOException { @@ -149,7 +151,6 @@ void pushSingleFile () throws Exception { //Add files Files.createFile(Path.of(clonedRepPath.toString(), "bib_1.bib")); - Files.createFile(Path.of(clonedRepPath.toString(), "bib_2.bib")); //Commit GitHandler git = new GitHandler(clonedRepPath); @@ -162,10 +163,14 @@ void pushSingleFile () throws Exception { git.setGitPreferences(preferences.getGitPreferences()); git.pushCommitsToRemoteRepository(); - Assertions.assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_2.bib", "PushSingleFile", false)); - - Git.lsRemoteRepository(); - + //Check + Git gitRemoteRep = new Git(repository); + AnyObjectId head = gitRemoteRep.getRepository().resolve(Constants.HEAD); + Iterator log = gitRemoteRep.log() + .add(head) + .call().iterator(); + assertEquals("PushSingleFile", log.next().getFullMessage()); + assertEquals("Initial commit", log.next().getFullMessage()); server.stop(); } From c1848461c0d73d6448114318ea2144e9fb4d2f93 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Wed, 25 Oct 2023 18:47:00 -0300 Subject: [PATCH 31/48] code style --- build.gradle | 6 ++++-- .../org/jabref/logic/git/GitHandlerTest.java | 18 +++++------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index ecb94f5684c..30f92afba1f 100644 --- a/build.gradle +++ b/build.gradle @@ -143,8 +143,10 @@ dependencies { implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.7.0.202309050840-r' 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.http.server', version: '6.7.0.202309050840-r' - implementation 'org.eclipse.jetty:jetty-servlet:9.4.51.v20230217' - implementation 'org.eclipse.jetty:jetty-security:9.4.51.v20230217' + + implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.51.v20230217' + implementation group: 'org.eclipse.jetty', name: 'jetty-security', version: '9.4.51.v20230217' + compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' configurations.all { exclude group: "commons-logging", module: "commons-logging" diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index 36669005525..2c04bf74bb3 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -32,12 +32,11 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -113,7 +112,7 @@ void createCommitWithSingleFileOnCurrentBranch() throws IOException, NoHeadExcep try (Git git = Git.open(repositoryPath.toFile())) { // Create commit Files.createFile(Path.of(repositoryPath.toString(), "Test.txt")); - Assertions.assertTrue(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false)); + assertTrue(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false)); AnyObjectId head = git.getRepository().resolve(Constants.HEAD); Iterator log = git.log() @@ -121,7 +120,7 @@ void createCommitWithSingleFileOnCurrentBranch() throws IOException, NoHeadExcep .call().iterator(); assertEquals("TestCommit", log.next().getFullMessage()); assertEquals("Initial commit", log.next().getFullMessage()); - Assertions.assertFalse(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false)); + assertFalse(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false)); } } @@ -154,7 +153,7 @@ void pushSingleFile () throws Exception { //Commit GitHandler git = new GitHandler(clonedRepPath); - Assertions.assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_1.bib", "PushSingleFile", false)); + assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_1.bib", "PushSingleFile", false)); //Push gitPreferences = new GitPreferences(username, password); @@ -163,14 +162,7 @@ void pushSingleFile () throws Exception { git.setGitPreferences(preferences.getGitPreferences()); git.pushCommitsToRemoteRepository(); - //Check - Git gitRemoteRep = new Git(repository); - AnyObjectId head = gitRemoteRep.getRepository().resolve(Constants.HEAD); - Iterator log = gitRemoteRep.log() - .add(head) - .call().iterator(); - assertEquals("PushSingleFile", log.next().getFullMessage()); - assertEquals("Initial commit", log.next().getFullMessage()); + assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_2.bib", "PushSingleFile", false)); server.stop(); } From 6dfac3fe56f02ac056c9bcb57d8c4efb68c9184b Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Wed, 25 Oct 2023 18:56:26 -0300 Subject: [PATCH 32/48] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c28ea8966b..9ff1d635a0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added protected terms described as "Computer science". [#10222](https://github.com/JabRef/jabref/pull/10222) - We added a fetcher for [LOBID](https://lobid.org/resources/api) resources. [koppor#386](https://github.com/koppor/jabref/issues/386) - When in `biblatex` mode, the [integrity check](https://docs.jabref.org/finding-sorting-and-cleaning-entries/checkintegrity) for journal titles now also checks the field `journal`. +- 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 From c2e5d7481e5dc56e81ad6d4056dc9ae9f106b379 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:41:54 -0300 Subject: [PATCH 33/48] cleanup code --- build.gradle | 8 ++-- .../gui/exporter/SaveDatabaseAction.java | 19 +++++++-- .../gui/exporter/SaveGitDatabaseAction.java | 41 ------------------- .../java/org/jabref/logic/git/GitHandler.java | 36 ++++------------ .../org/jabref/logic/git/GitPreferences.java | 8 ++-- .../logic/git/SshTransportConfigCallback.java | 11 +++-- .../jabref/gui/git/GitCredentialsTest.java | 7 ++-- .../org/jabref/logic/git/GitHandlerTest.java | 22 +++++----- .../org/jabref/logic/git/HelloServlet.java | 33 --------------- 9 files changed, 52 insertions(+), 133 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java delete mode 100644 src/test/java/org/jabref/logic/git/HelloServlet.java diff --git a/build.gradle b/build.gradle index dcbf7089473..0ae75a70eeb 100644 --- a/build.gradle +++ b/build.gradle @@ -144,12 +144,12 @@ dependencies { implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.7.0.202309050840-r' 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.http.server', version: '6.7.0.202309050840-r' + testImplementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit.http.server', version: '6.7.0.202309050840-r' - implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.51.v20230217' - implementation group: 'org.eclipse.jetty', name: 'jetty-security', version: '9.4.51.v20230217' + 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' - compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' + testCompileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' configurations.all { exclude group: "commons-logging", module: "commons-logging" } diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 955b3f94ebe..b901bcb06db 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -18,6 +18,8 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Text; +import org.eclipse.jgit.api.errors.GitAPIException; + import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.autosaveandbackup.AutosaveManager; @@ -32,6 +34,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.shared.DatabaseLocation; @@ -245,11 +248,10 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { dialogService.notify(Localization.lang("Library saved")); if (success) { - SaveGitDatabaseAction saveGit = new SaveGitDatabaseAction(targetPath, preferences); - saveGit.automaticUpdate(); + this.automaticGitUpdate(targetPath); } return success; - } catch (SaveException ex) { + } catch (SaveException | IOException | GitAPIException ex) { LOGGER.error(String.format("A problem occurred when trying to save the file %s", targetPath), ex); dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); return false; @@ -320,4 +322,15 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset } } } + + /** + * Handle JabRef git integration action. This method is called when the user save the database. + */ + public void automaticGitUpdate(Path filePath) throws IOException, GitAPIException { + GitHandler git = new GitHandler(filePath.getParent()); + git.setGitPreferences(preferences.getGitPreferences()); + String automaticCommitMsg = "Automatic update via JabRef"; + git.createCommitWithSingleFileOnCurrentBranch(filePath.getFileName().toString(), automaticCommitMsg); + git.pushCommitsToRemoteRepository(); + } } diff --git a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java deleted file mode 100644 index 7f31b5a5541..00000000000 --- a/src/main/java/org/jabref/gui/exporter/SaveGitDatabaseAction.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.jabref.gui.exporter; - -import java.io.IOException; -import java.nio.file.Path; - -import org.jabref.logic.git.GitHandler; -import org.jabref.preferences.PreferencesService; - -import org.eclipse.jgit.api.errors.GitAPIException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SaveGitDatabaseAction { - static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); - final Path filePath; - final String automaticCommitMsg = "Automatic update via JabRef"; - private final PreferencesService preferences; - - public SaveGitDatabaseAction(Path filePath, PreferencesService preferences) { - this.filePath = filePath; - this.preferences = preferences; - } - - /** - * Handle JabRef git integration action - * - * @return true of false whether the action was successful or not - */ - public boolean automaticUpdate() { - try { - GitHandler git = new GitHandler(this.filePath.getParent()); - git.setGitPreferences(preferences.getGitPreferences()); - git.createCommitWithSingleFileOnCurrentBranch(this.filePath.getFileName().toString(), automaticCommitMsg, false); - git.pushCommitsToRemoteRepository(); - } catch (GitAPIException | IOException e) { - LOGGER.info("Failed to automatic git update"); - } - - return true; - } -} diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 3c8abaafff0..7fd38eeb296 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -18,7 +18,6 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; @@ -66,9 +65,7 @@ public GitHandler(Path repositoryPath) { } } } catch (GitAPIException | IOException e) { - System.out.println(e); - System.out.println(e.getMessage()); - LOGGER.error("Initialization failed"); + LOGGER.error("Initialization failed", e); } } } @@ -161,13 +158,12 @@ public boolean createCommitOnCurrentBranch(String commitMessage, boolean amend) * Creates a commit on the currently checked out branch with a single file * * @param filename The name of the file to commit - * @param amend Whether to amend to the last commit (true), or not (false) * @return Returns true if a new commit was created. This is the case if the repository was not clean on method invocation * @throws IOException * @throws GitAPIException * @throws NoWorkTreeException */ - public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String commitMessage, boolean amend) throws IOException, NoWorkTreeException, GitAPIException { + public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String commitMessage) throws IOException, NoWorkTreeException, GitAPIException { boolean commitCreated = false; Git git = Git.open(this.repositoryPathAsFile); @@ -187,7 +183,6 @@ public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String removeCommand.call(); } git.commit() - .setAmend(amend) .setAllowEmpty(false) .setMessage(commitMessage) .call(); @@ -224,11 +219,11 @@ public void mergeBranches(String targetBranch, String sourceBranch, MergeStrateg * Pushes all commits made to the branch that is tracked by the currently checked out branch. * If pushing to remote fails, it fails silently. */ - public void pushCommitsToRemoteRepository() { + public void pushCommitsToRemoteRepository() throws IOException { try { Git git = Git.open(this.repositoryPathAsFile); String remoteURL = git.getRepository().getConfig().getString("remote", "origin", "url"); - Boolean isSshRemoteRepository = remoteURL != null ? remoteURL.contains("git@") : false; + boolean isSshRemoteRepository = remoteURL != null && remoteURL.contains("git@"); git.verifySignature(); @@ -243,7 +238,7 @@ public void pushCommitsToRemoteRepository() { git.push() .setTransportConfigCallback(transportConfigCallback) .call(); - } else if (this.gitPassword.equals("") || this.gitUsername.equals("")) { + } else if (this.gitPassword.isEmpty() || this.gitUsername.isEmpty()) { GitCredentialsDialogView gitCredentialsDialogView = new GitCredentialsDialogView(); gitCredentialsDialogView.showGitCredentialsDialog(); @@ -261,9 +256,9 @@ public void pushCommitsToRemoteRepository() { .setCredentialsProvider(this.credentialsProvider) .call(); } - } catch (IOException | GitAPIException e) { + } catch (GitAPIException e) { if (e.getMessage().equals("origin: not found.")) { - LOGGER.info("No remote repository detected. Push skiped."); + LOGGER.info("No remote repository detected. Push skipped.", e); } else { LOGGER.info("Failed to push"); throw new RuntimeException(e); @@ -282,7 +277,7 @@ public void pullOnCurrentBranch() { .call(); } catch (GitAPIException e) { if (e.getMessage().equals("origin: not found")) { - LOGGER.info("No remote repository detected. Push skiped."); + LOGGER.info("No remote repository detected. Push skipped."); } else { LOGGER.info("Failed to pull"); throw new RuntimeException(e); @@ -302,24 +297,11 @@ public String getCurrentlyCheckedOutBranch() { return git.getRepository().getBranch(); } catch (IOException ex) { LOGGER.info("Failed get current branch"); - return ""; + return String.valueOf(Optional.empty()); } } public void setGitPreferences(GitPreferences gitPreferences) { this.gitPreferences = gitPreferences; } - - public void setRemoteUrl(String url) { - try { - Git git = Git.open(this.repositoryPathAsFile); - StoredConfig config = git.getRepository().getConfig(); - config.setString("remote", "origin", "url", url); - config.save(); - } catch ( - IOException e) { - LOGGER.info("Failed to set git remote url"); - throw new RuntimeException(e); - } - } } diff --git a/src/main/java/org/jabref/logic/git/GitPreferences.java b/src/main/java/org/jabref/logic/git/GitPreferences.java index 88aa5e13494..2939575a779 100644 --- a/src/main/java/org/jabref/logic/git/GitPreferences.java +++ b/src/main/java/org/jabref/logic/git/GitPreferences.java @@ -4,12 +4,12 @@ import javafx.beans.property.StringProperty; public class GitPreferences { - private StringProperty username = new SimpleStringProperty(); - private StringProperty password = new SimpleStringProperty(); + private final StringProperty username = new SimpleStringProperty(); + private final StringProperty password = new SimpleStringProperty(); public GitPreferences(String username, String password) { - //this.username.setValue(username); - //this.password.set(password); + this.username.setValue(username); + this.password.set(password); } public String getUsername() { diff --git a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java index ab246486196..1bad485bea0 100644 --- a/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java +++ b/src/main/java/org/jabref/logic/git/SshTransportConfigCallback.java @@ -12,10 +12,10 @@ import org.eclipse.jgit.util.FS; public class SshTransportConfigCallback implements TransportConfigCallback { - private Path sshDir = new File(FS.DETECTED.userHome(), "/.ssh").toPath(); - private SshdSessionFactory sshSessionFactory; + private final SshdSessionFactory sshSessionFactory; public SshTransportConfigCallback() { + Path sshDir = new File(FS.DETECTED.userHome(), "/.ssh").toPath(); this.sshSessionFactory = new CustomSshSessionFactory(sshDir); } @@ -25,8 +25,8 @@ public void configure(Transport transport) { sshTransport.setSshSessionFactory(this.sshSessionFactory); } - public final class CustomSshSessionFactory extends SshdSessionFactory { - private Path sshDir; + public static final class CustomSshSessionFactory extends SshdSessionFactory { + private final Path sshDir; public CustomSshSessionFactory(Path sshDir) { this.sshDir = sshDir; @@ -37,9 +37,8 @@ public File getSshDirectory() { try { return Files.createDirectories(sshDir).toFile(); } catch (IOException e) { - e.printStackTrace(); + throw new RuntimeException(e); } - return null; } } } diff --git a/src/test/java/org/jabref/gui/git/GitCredentialsTest.java b/src/test/java/org/jabref/gui/git/GitCredentialsTest.java index 2d1c3145f70..b3a6a8cbf09 100644 --- a/src/test/java/org/jabref/gui/git/GitCredentialsTest.java +++ b/src/test/java/org/jabref/gui/git/GitCredentialsTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; public class GitCredentialsTest { @Test @@ -11,8 +12,8 @@ void GitCredentials() { String gitPassword = gitCredentials.getGitPassword(); String gitUsername = gitCredentials.getGitUsername(); - assertEquals(null, gitUsername); - assertEquals(null, gitPassword); + assertNull(gitUsername); + assertNull(gitPassword); } @Test @@ -31,7 +32,7 @@ void GitCredentialsGettersAndSetters() { gitCredentials.setGitPassword("testPassword"); gitCredentials.setGitUsername("testUsername"); - + String gitPassword = gitCredentials.getGitPassword(); String gitUsername = gitCredentials.getGitUsername(); diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index 2c04bf74bb3..7de3033fa9b 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -23,7 +23,6 @@ import org.eclipse.jetty.util.security.Credential; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.http.server.GitServlet; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; @@ -74,7 +73,7 @@ private static Path createFolder() throws IOException { } @BeforeEach - public void setUpGitHandler() throws IOException { + public void setUpGitHandler() { gitPreferences = new GitPreferences("testUser", "testPassword"); PreferencesService preferences = mock(PreferencesService.class); when(preferences.getGitPreferences()).thenReturn(gitPreferences); @@ -108,11 +107,11 @@ void createCommitOnCurrentBranch() throws IOException, GitAPIException { } @Test - void createCommitWithSingleFileOnCurrentBranch() throws IOException, NoHeadException, GitAPIException { + void createCommitWithSingleFileOnCurrentBranch() throws IOException, GitAPIException { try (Git git = Git.open(repositoryPath.toFile())) { // Create commit Files.createFile(Path.of(repositoryPath.toString(), "Test.txt")); - assertTrue(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false)); + assertTrue(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit")); AnyObjectId head = git.getRepository().resolve(Constants.HEAD); Iterator log = git.log() @@ -120,12 +119,12 @@ void createCommitWithSingleFileOnCurrentBranch() throws IOException, NoHeadExcep .call().iterator(); assertEquals("TestCommit", log.next().getFullMessage()); assertEquals("Initial commit", log.next().getFullMessage()); - assertFalse(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit", false)); + assertFalse(gitHandler.createCommitWithSingleFileOnCurrentBranch("Test.txt", "TestCommit")); } } @Test - void getCurrentlyCheckedOutBranch() throws IOException { + void getCurrentlyCheckedOutBranch() { assertEquals("main", gitHandler.getCurrentlyCheckedOutBranch()); } @@ -153,7 +152,7 @@ void pushSingleFile () throws Exception { //Commit GitHandler git = new GitHandler(clonedRepPath); - assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_1.bib", "PushSingleFile", false)); + assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_1.bib", "PushSingleFile")); //Push gitPreferences = new GitPreferences(username, password); @@ -162,11 +161,11 @@ void pushSingleFile () throws Exception { git.setGitPreferences(preferences.getGitPreferences()); git.pushCommitsToRemoteRepository(); - assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_2.bib", "PushSingleFile", false)); + assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_2.bib", "PushSingleFile")); server.stop(); } - private static final SecurityHandler basicAuth(String username, String password, String realm) { + private static SecurityHandler basicAuth(String username, String password) { HashLoginService l = new HashLoginService(); UserStore userStore = new UserStore(); @@ -174,8 +173,7 @@ private static final SecurityHandler basicAuth(String username, String password, Credential credential = Credential.getCredential(password); userStore.addUser(username, credential, roles); l.setUserStore(userStore); - l.setName(realm); - CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password); + l.setName("Private!"); Constraint constraint = new Constraint(); constraint.setName(Constraint.__BASIC_AUTH); @@ -207,7 +205,7 @@ private Server createServer(String username, String password, Repository reposit }); Server server = new Server(8080); ServletContextHandler context = new ServletContextHandler(server, "/repoTest", ServletContextHandler.SESSIONS); - context.setSecurityHandler(basicAuth(username, password, "Private!")); + context.setSecurityHandler(basicAuth(username, password)); context.addServlet(new ServletHolder(gs), "/*"); server.setHandler(context); return server; diff --git a/src/test/java/org/jabref/logic/git/HelloServlet.java b/src/test/java/org/jabref/logic/git/HelloServlet.java deleted file mode 100644 index fcaacc7d658..00000000000 --- a/src/test/java/org/jabref/logic/git/HelloServlet.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.jabref.logic.git; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public class HelloServlet extends HttpServlet -{ - final String greeting; - - public HelloServlet() - { - this("Hello"); - } - - public HelloServlet( String greeting ) - { - this.greeting = greeting; - } - - @Override - protected void doGet( HttpServletRequest request, - HttpServletResponse response ) throws ServletException, - IOException - { - response.setContentType("text/html"); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().println( - "

" + greeting + " from HelloServlet

"); - } -} From a6682a0d9018fb0a492667620db7a5caae9b4fb2 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Mon, 30 Oct 2023 22:09:09 -0300 Subject: [PATCH 34/48] organize imports --- src/test/java/org/jabref/logic/git/GitHandlerTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index 7de3033fa9b..bfb84230230 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -7,9 +7,6 @@ import java.util.Iterator; import java.util.Objects; -import org.jabref.gui.git.GitPreferences; -import org.jabref.preferences.PreferencesService; - import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; @@ -31,11 +28,14 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.jabref.gui.git.GitPreferences; +import org.jabref.preferences.PreferencesService; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; - -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; From b0351abbe361e0b668f086eb0cf70c4e14153b43 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Sat, 4 Nov 2023 14:23:33 -0300 Subject: [PATCH 35/48] fix code style, add git preferences to control workflow, add files to support conflict resolver, remove fail silently --- .../gui/exporter/SaveDatabaseAction.java | 34 +- .../java/org/jabref/gui/git/GitChange.java | 71 ++ .../org/jabref/gui/git/GitChangeResolver.java | 16 + .../gui/git/GitChangeResolverFactory.java | 29 + .../jabref/gui/git/GitChangesDetailView.java | 5 + .../jabref/gui/git/GitChangesResolver.java | 16 + .../gui/git/GitChangesResolverDialog.java | 137 ++++ .../org/jabref/gui/git/GitCredentials.java | 32 - .../gui/git/GitCredentialsDialogView.java | 9 +- .../org/jabref/gui/git/GitPreferences.java | 51 -- .../org/jabref/gui/git/entryadd/EntryAdd.java | 31 + .../gui/git/entrychange/EntryChange.java | 49 ++ .../entrychange/EntryChangeDetailsView.java | 71 ++ .../git/entrychange/EntryChangeResolver.java | 48 ++ .../EntryWithPreviewAndSourceDetailsView.java | 25 + .../git/entrychange/PreviewWithSourceTab.java | 73 ++ .../gui/git/entrydelete/EntryDelete.java | 31 + .../gui/git/groupchange/GroupChange.java | 56 ++ .../groupchange/GroupChangeDetailsView.java | 25 + .../git/metedatachange/MetadataChange.java | 32 + .../MetadataChangeDetailsView.java | 65 ++ .../git/preamblechange/PreambleChange.java | 35 + .../PreambleChangeDetailsView.java | 37 + .../gui/git/stringadd/BibTexStringAdd.java | 39 ++ .../stringadd/BibTexStringAddDetailsView.java | 27 + .../git/stringchange/BibTexStringChange.java | 43 ++ .../BibTexStringChangeDetailsView.java | 29 + .../git/stringdelete/BibTexStringDelete.java | 38 ++ .../BibTexStringDeleteDetailsView.java | 27 + .../git/stringrename/BibTexStringRename.java | 48 ++ .../BibTexStringRenameDetailsView.java | 18 + .../jabref/gui/preferences/git/GitTab.fxml | 7 + .../jabref/gui/preferences/git/GitTab.java | 16 + .../gui/preferences/git/GitTabViewModel.java | 28 +- .../java/org/jabref/logic/git/GitHandler.java | 115 +++- .../org/jabref/logic/git/GitPreferences.java | 86 ++- src/main/java/org/jabref/model/git/Git.java | 641 ++++++++++++++++++ .../java/org/jabref/model/git/GitContext.java | 265 ++++++++ .../jabref/preferences/JabRefPreferences.java | 14 +- .../preferences/PreferencesService.java | 2 +- .../jabref/gui/git/GitCredentialsTest.java | 42 -- .../jabref/gui/git/GitPreferencesTest.java | 68 -- .../org/jabref/logic/git/GitHandlerTest.java | 9 +- 43 files changed, 2278 insertions(+), 262 deletions(-) create mode 100644 src/main/java/org/jabref/gui/git/GitChange.java create mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolver.java create mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java create mode 100644 src/main/java/org/jabref/gui/git/GitChangesDetailView.java create mode 100644 src/main/java/org/jabref/gui/git/GitChangesResolver.java create mode 100644 src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java delete mode 100644 src/main/java/org/jabref/gui/git/GitCredentials.java delete mode 100644 src/main/java/org/jabref/gui/git/GitPreferences.java create mode 100644 src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChange.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java create mode 100644 src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java create mode 100644 src/main/java/org/jabref/gui/git/groupchange/GroupChange.java create mode 100644 src/main/java/org/jabref/gui/git/groupchange/GroupChangeDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/metedatachange/MetadataChange.java create mode 100644 src/main/java/org/jabref/gui/git/metedatachange/MetadataChangeDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/preamblechange/PreambleChange.java create mode 100644 src/main/java/org/jabref/gui/git/preamblechange/PreambleChangeDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/stringadd/BibTexStringAdd.java create mode 100644 src/main/java/org/jabref/gui/git/stringadd/BibTexStringAddDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/stringchange/BibTexStringChange.java create mode 100644 src/main/java/org/jabref/gui/git/stringchange/BibTexStringChangeDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDelete.java create mode 100644 src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDeleteDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/stringrename/BibTexStringRename.java create mode 100644 src/main/java/org/jabref/gui/git/stringrename/BibTexStringRenameDetailsView.java create mode 100644 src/main/java/org/jabref/model/git/Git.java create mode 100644 src/main/java/org/jabref/model/git/GitContext.java delete mode 100644 src/test/java/org/jabref/gui/git/GitCredentialsTest.java delete mode 100644 src/test/java/org/jabref/gui/git/GitPreferencesTest.java diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index b901bcb06db..076ddce8d54 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -20,10 +20,12 @@ import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.autosaveandbackup.AutosaveManager; import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.git.GitCredentialsDialogView; import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.gui.util.BackgroundTask; @@ -327,10 +329,32 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset * Handle JabRef git integration action. This method is called when the user save the database. */ public void automaticGitUpdate(Path filePath) throws IOException, GitAPIException { - GitHandler git = new GitHandler(filePath.getParent()); - git.setGitPreferences(preferences.getGitPreferences()); - String automaticCommitMsg = "Automatic update via JabRef"; - git.createCommitWithSingleFileOnCurrentBranch(filePath.getFileName().toString(), automaticCommitMsg); - git.pushCommitsToRemoteRepository(); + GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences()); + if (preferences.getGitPreferences().getAutoCommit()) { + String automaticCommitMsg = "Automatic update via JabRef"; + git.createCommitWithSingleFileOnCurrentBranch(filePath.getFileName().toString(), automaticCommitMsg); + } + if (preferences.getGitPreferences().getAutoSync()) { + try { + git.pullOnCurrentBranch(); + } catch (IOException ex) { + if (ex.getMessage().equals("No git credentials")) { + GitCredentialsDialogView gitCredentialsDialogView = new GitCredentialsDialogView(); + + gitCredentialsDialogView.showGitCredentialsDialog(); + + UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider( + gitCredentialsDialogView.getGitUsername(), + gitCredentialsDialogView.getGitPassword() + ); + + git.setCredentialsProvider(credentialsProvider); + git.pullOnCurrentBranch(); + } else { + throw new IOException(ex.getMessage()); + } + } + git.pushCommitsToRemoteRepository(); + } } } diff --git a/src/main/java/org/jabref/gui/git/GitChange.java b/src/main/java/org/jabref/gui/git/GitChange.java new file mode 100644 index 00000000000..3923b5ab173 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChange.java @@ -0,0 +1,71 @@ +package org.jabref.gui.git; + +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.git.entryadd.EntryAdd; +import org.jabref.gui.git.entrychange.EntryChange; +import org.jabref.gui.git.entrydelete.EntryDelete; +import org.jabref.gui.git.groupchange.GroupChange; +import org.jabref.gui.git.metedatachange.MetadataChange; +import org.jabref.gui.git.preamblechange.PreambleChange; +import org.jabref.gui.git.stringadd.BibTexStringAdd; +import org.jabref.gui.git.stringchange.BibTexStringChange; +import org.jabref.gui.git.stringdelete.BibTexStringDelete; +import org.jabref.gui.git.stringrename.BibTexStringRename; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.util.OptionalObjectProperty; +import org.jabref.model.git.GitContext; + +public sealed abstract class GitChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename { + protected final GitContext gitContext; + protected final OptionalObjectProperty externalChangeResolver = OptionalObjectProperty.empty(); + private final BooleanProperty accepted = new SimpleBooleanProperty(); + private final StringProperty name = new SimpleStringProperty(); + + protected GitChange(GitContext gitContext, GitChangeResolverFactory gitChangeResolverFactory) { + this.gitContext = gitContext; + setChangeName("Unnamed Change!"); + + if (gitChangeResolverFactory != null) { + externalChangeResolver.set(gitChangeResolverFactory.create(this)); + } + } + + public boolean isAccepted() { + return accepted.get(); + } + + public BooleanProperty acceptedProperty() { + return accepted; + } + + public void setAccepted(boolean accepted) { + this.accepted.set(accepted); + } + + /** + * Convenience method for accepting changes + * */ + public void accept() { + setAccepted(true); + } + + public String getName() { + return name.get(); + } + + protected void setChangeName(String changeName) { + name.set(changeName); + } + + public Optional getExternalChangeResolver() { + return externalChangeResolver.get(); + } + + public abstract void applyChange(NamedCompound undoEdit); +} diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolver.java b/src/main/java/org/jabref/gui/git/GitChangeResolver.java new file mode 100644 index 00000000000..260922ad689 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChangeResolver.java @@ -0,0 +1,16 @@ +package org.jabref.gui.git; + +import java.util.Optional; + +import org.jabref.gui.DialogService; +import org.jabref.gui.collab.entrychange.EntryChangeResolver; + +public sealed abstract class GitChangeResolver permits EntryChangeResolver { + protected final DialogService dialogService; + + protected GitChangeResolver(DialogService dialogService) { + this.dialogService = dialogService; + } + + public abstract Optional askUserToResolveChange(); +} diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java b/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java new file mode 100644 index 00000000000..9fc4462c68e --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java @@ -0,0 +1,29 @@ +package org.jabref.gui.git; + +import java.util.Optional; + +import org.jabref.gui.DialogService; +import org.jabref.gui.collab.entrychange.EntryChange; +import org.jabref.gui.collab.entrychange.EntryChangeResolver; +import org.jabref.model.git.GitContext; +import org.jabref.preferences.PreferencesService; + +public class GitChangeResolverFactory { + private final DialogService dialogService; + private final GitContext gitContext; + private final PreferencesService preferencesService; + + public GitChangeResolverFactory(DialogService dialogService, GitContext gitContext, PreferencesService preferencesService) { + this.dialogService = dialogService; + this.gitContext = gitContext; + this.preferencesService = preferencesService; + } + + public Optional create(GitChange change) { + if (change instanceof EntryChange entryChange) { + return Optional.of(new EntryChangeResolver(entryChange, dialogService, gitContext, preferencesService)); + } + + return Optional.empty(); + } +} diff --git a/src/main/java/org/jabref/gui/git/GitChangesDetailView.java b/src/main/java/org/jabref/gui/git/GitChangesDetailView.java new file mode 100644 index 00000000000..5127b12b19e --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChangesDetailView.java @@ -0,0 +1,5 @@ +package org.jabref.gui.git; + +public class GitChangesDetailView { + +} diff --git a/src/main/java/org/jabref/gui/git/GitChangesResolver.java b/src/main/java/org/jabref/gui/git/GitChangesResolver.java new file mode 100644 index 00000000000..4ea568ee310 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChangesResolver.java @@ -0,0 +1,16 @@ +package org.jabref.gui.git; + +import java.util.Optional; + +import org.jabref.gui.DialogService; +import org.jabref.gui.collab.entrychange.EntryChangeResolver; + +public sealed abstract class GitChangesResolver permits EntryChangeResolver { + protected final DialogService dialogService; + + protected GItChangeResolver(DialogService dialogService) { + this.dialogService = dialogService; + } + + public abstract Optional askUserToResolveChange(); +} diff --git a/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java b/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java new file mode 100644 index 00000000000..a9c1a9e53a2 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java @@ -0,0 +1,137 @@ +package org.jabref.gui.git; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.undo.UndoManager; + +import javafx.beans.property.SimpleStringProperty; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.BorderPane; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.collab.ExternalChangesResolverViewModel; +import org.jabref.gui.preview.PreviewViewer; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.util.BaseDialog; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.preferences.PreferencesService; + +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; +import jakarta.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GitChangesResolverDialog extends BaseDialog { + private final static Logger LOGGER = LoggerFactory.getLogger(GitChangesResolverDialog.class); + /** + * Reconstructing the details view to preview an {@link GitChange} every time it's selected is a heavy operation. + * It is also useless because changes are static and if the change data is static then the view doesn't have to change + * either. This cache is used to ensure that we only create the detail view instance once for each {@link GitChange}. + */ + private final Map DETAILS_VIEW_CACHE = new HashMap<>(); + + @FXML + private TableView changesTableView; + @FXML + private TableColumn changeName; + @FXML + private Button askUserToResolveChangeButton; + @FXML + private BorderPane changeInfoPane; + + private final List changes; + private final BibGitContext git; + + private ExternalChangesResolverViewModel viewModel; + + @Inject private UndoManager undoManager; + @Inject private StateManager stateManager; + @Inject private DialogService dialogService; + @Inject private PreferencesService preferencesService; + @Inject private ThemeManager themeManager; + @Inject private BibEntryTypesManager entryTypesManager; + @Inject private TaskExecutor taskExecutor; + + /** + * A dialog going through given changes, which are diffs to the provided git. + * Each accepted change is written to the provided git. + * + * @param changes The list of changes + * @param git The git to apply the changes to + */ + public GitChangesResolverDialog(List changes, BibGitContext git, String dialogTitle) { + this.changes = changes; + this.git = git; + + this.setTitle(dialogTitle); + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + this.setResultConverter(button -> { + if (viewModel.areAllChangesResolved()) { + LOGGER.info("External changes are resolved successfully"); + return true; + } else { + LOGGER.info("External changes aren't resolved"); + return false; + } + }); + } + + @FXML + private void initialize() { + PreviewViewer previewViewer = new PreviewViewer(git, dialogService, preferencesService, stateManager, themeManager, taskExecutor); + GitChangeDetailsViewFactory gitChangeDetailsViewFactory = new GitChangeDetailsViewFactory(git, dialogService, stateManager, themeManager, preferencesService, entryTypesManager, previewViewer, taskExecutor); + + viewModel = new ExternalChangesResolverViewModel(changes, undoManager); + + changeName.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getName())); + askUserToResolveChangeButton.disableProperty().bind(viewModel.canAskUserToResolveChangeProperty().not()); + + changesTableView.setItems(viewModel.getVisibleChanges()); + // Think twice before setting this to MULTIPLE... + changesTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + changesTableView.getSelectionModel().selectFirst(); + + viewModel.selectedChangeProperty().bind(changesTableView.getSelectionModel().selectedItemProperty()); + EasyBind.subscribe(viewModel.selectedChangeProperty(), selectedChange -> { + if (selectedChange != null) { + GitChangeDetailsView detailsView = DETAILS_VIEW_CACHE.computeIfAbsent(selectedChange, gitChangeDetailsViewFactory::create); + changeInfoPane.setCenter(detailsView); + } + }); + + EasyBind.subscribe(viewModel.areAllChangesResolvedProperty(), isResolved -> { + if (isResolved) { + viewModel.applyChanges(); + close(); + } + }); + } + + @FXML + public void denyChanges() { + viewModel.denyChange(); + } + + @FXML + public void acceptChanges() { + viewModel.acceptChange(); + } + + @FXML + public void askUserToResolveChange() { + viewModel.getSelectedChange().flatMap(GitChange::getExternalChangeResolver) + .flatMap(GitChangesResolver::askUserToResolveChange).ifPresent(viewModel::acceptMergedChange); + } +} diff --git a/src/main/java/org/jabref/gui/git/GitCredentials.java b/src/main/java/org/jabref/gui/git/GitCredentials.java deleted file mode 100644 index 84326b82030..00000000000 --- a/src/main/java/org/jabref/gui/git/GitCredentials.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.jabref.gui.git; - -public class GitCredentials { - private String gitUsername; - private String gitPassword; - - public GitCredentials() { - this.gitUsername = null; - this.gitPassword = null; - } - - public GitCredentials(String gitUsername, String gitPassword) { - this.gitUsername = gitUsername; - this.gitPassword = gitPassword; - } - - public void setGitUsername(String gitUsername) { - this.gitUsername = gitUsername; - } - - public void setGitPassword(String gitPassword) { - this.gitPassword = gitPassword; - } - - public String getGitPassword() { - return this.gitPassword; - } - - public String getGitUsername() { - return this.gitUsername; - } -} diff --git a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java index 84e2042130c..ac7a915cecf 100644 --- a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java +++ b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java @@ -12,6 +12,7 @@ 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; @@ -22,7 +23,7 @@ public class GitCredentialsDialogView extends BaseDialog { @FXML private TextArea textAreaVersions; private DialogService dialogService; private DialogPane pane; - + private ButtonType acceptButton; private ButtonType cancelButton; private TextField inputGitUsername; @@ -46,16 +47,16 @@ public GitCredentialsDialogView() { 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 GitCredentials getCredentials() { + public GitCredential getCredentials() { dialogService.showCustomDialogAndWait(Localization.lang("Git credentials"), this.pane, this.acceptButton, this.cancelButton); - GitCredentials gitCredentials = new GitCredentials(this.inputGitUsername.getText(), this.inputGitPassword.getText()); + GitCredential gitCredentials = new GitCredential(this.inputGitUsername.getText(), this.inputGitPassword.getText()); return gitCredentials; } diff --git a/src/main/java/org/jabref/gui/git/GitPreferences.java b/src/main/java/org/jabref/gui/git/GitPreferences.java deleted file mode 100644 index 5472e9d7d45..00000000000 --- a/src/main/java/org/jabref/gui/git/GitPreferences.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.jabref.gui.git; - -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; - -public class GitPreferences { - private StringProperty username; - private StringProperty password; - - public GitPreferences(String username, String password) { - this.username = new SimpleStringProperty(username); - this.password = new SimpleStringProperty(password); - } - - public GitPreferences(StringProperty username, StringProperty password) { - this.username = username; - this.password = password; - } - - public StringProperty getUsernameProperty() { - return this.username; - } - - public String getUsername() { - return this.username.get(); - } - - public StringProperty getPasswordProperty() { - return this.password; - } - - public String getPassword() { - return this.password.get(); - } - - public void setPassword(StringProperty password) { - this.password = password; - } - - public void setPassword(String password) { - this.password = new SimpleStringProperty(password); - } - - public void setUsername(StringProperty username) { - this.username = username; - } - - public void setUsername(String username) { - this.username = new SimpleStringProperty(username); - } -} diff --git a/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java b/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java new file mode 100644 index 00000000000..0c4e01969fc --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java @@ -0,0 +1,31 @@ +package org.jabref.gui.git.entryadd; + +import org.jabref.gui.git.GitChange; +import org.jabref.gui.git.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableInsertEntries; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.entry.BibEntry; + +public final class EntryAdd extends GitChange { + private final BibEntry addedEntry; + + public EntryAdd(BibEntry addedEntry, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.addedEntry = addedEntry; + setChangeName(addedEntry.getCitationKey() + .map(key -> Localization.lang("Added entry '%0'", key)) + .orElse(Localization.lang("Added entry"))); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + databaseContext.getGit().insertEntry(addedEntry); + undoEdit.addEdit(new UndoableInsertEntries(databaseContext.getGit(), addedEntry)); + } + + public BibEntry getAddedEntry() { + return addedEntry; + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java new file mode 100644 index 00000000000..7753bb422cc --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java @@ -0,0 +1,49 @@ +package org.jabref.gui.collab.entrychange; + +import javax.swing.undo.CompoundEdit; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableInsertEntries; +import org.jabref.gui.undo.UndoableRemoveEntries; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.entry.BibEntry; + +public final class EntryChange extends GitChange { + private final BibEntry oldEntry; + private final BibEntry newEntry; + + public EntryChange(BibEntry oldEntry, BibEntry newEntry, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.oldEntry = oldEntry; + this.newEntry = newEntry; + setChangeName(oldEntry.getCitationKey().map(key -> Localization.lang("Modified entry '%0'", key)) + .orElse(Localization.lang("Modified entry"))); + } + + public EntryChange(BibEntry oldEntry, BibEntry newEntry, GitContext databaseContext) { + this(oldEntry, newEntry, databaseContext, null); + } + + public BibEntry getOldEntry() { + return oldEntry; + } + + public BibEntry getNewEntry() { + return newEntry; + } + + @Override + public void applyChange(NamedCompound undoEdit) { + databaseContext.getGit().removeEntry(oldEntry); + databaseContext.getGit().insertEntry(newEntry); + CompoundEdit changeEntryEdit = new CompoundEdit(); + changeEntryEdit.addEdit(new UndoableRemoveEntries(databaseContext.getGit(), oldEntry)); + changeEntryEdit.addEdit(new UndoableInsertEntries(databaseContext.getGit(), newEntry)); + changeEntryEdit.end(); + + undoEdit.addEdit(changeEntryEdit); + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java new file mode 100644 index 00000000000..53a4ce25c87 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java @@ -0,0 +1,71 @@ +package org.jabref.gui.collab.entrychange; + +import javafx.geometry.Orientation; +import javafx.scene.control.Label; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TabPane; +import javafx.scene.layout.VBox; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.collab.GitChangeDetailsView; +import org.jabref.gui.preview.PreviewViewer; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.preferences.PreferencesService; + +import com.tobiasdiez.easybind.EasyBind; + +public final class EntryChangeDetailsView extends GitChangeDetailsView { + private final PreviewWithSourceTab oldPreviewWithSourcesTab = new PreviewWithSourceTab(); + private final PreviewWithSourceTab newPreviewWithSourcesTab = new PreviewWithSourceTab(); + + public EntryChangeDetailsView(BibEntry oldEntry, + BibEntry newEntry, + GitContext databaseContext, + DialogService dialogService, + StateManager stateManager, + ThemeManager themeManager, + PreferencesService preferencesService, + BibEntryTypesManager entryTypesManager, + PreviewViewer previewViewer, + TaskExecutor taskExecutor) { + Label inJabRef = new Label(Localization.lang("In JabRef")); + inJabRef.getStyleClass().add("lib-change-header"); + Label onDisk = new Label(Localization.lang("On disk")); + onDisk.getStyleClass().add("lib-change-header"); + + // we need a copy here as we otherwise would set the same entry twice + PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, preferencesService, stateManager, themeManager, taskExecutor); + + TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, preferencesService, entryTypesManager, previewClone, Localization.lang("Entry Preview")); + TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, preferencesService, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); + + EasyBind.subscribe(oldEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { + newEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); + }); + + EasyBind.subscribe(newEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { + if (oldEntryTabPane.getSelectionModel().getSelectedIndex() != selectedIndex.intValue()) { + oldEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); + } + }); + + VBox containerOld = new VBox(inJabRef, oldEntryTabPane); + VBox containerNew = new VBox(onDisk, newEntryTabPane); + + SplitPane split = new SplitPane(containerOld, containerNew); + split.setOrientation(Orientation.HORIZONTAL); + + setLeftAnchor(split, 8d); + setTopAnchor(split, 8d); + setRightAnchor(split, 8d); + setBottomAnchor(split, 8d); + + this.getChildren().add(split); + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java new file mode 100644 index 00000000000..049138bb5c9 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java @@ -0,0 +1,48 @@ +package org.jabref.gui.collab.entrychange; + +import java.util.Optional; + +import org.jabref.gui.DialogService; +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolver; +import org.jabref.gui.mergeentries.EntriesMergeResult; +import org.jabref.gui.mergeentries.MergeEntriesDialog; +import org.jabref.gui.mergeentries.newmergedialog.ShowDiffConfig; +import org.jabref.gui.mergeentries.newmergedialog.diffhighlighter.DiffHighlighter.BasicDiffMethod; +import org.jabref.gui.mergeentries.newmergedialog.toolbar.ThreeWayMergeToolbar; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.preferences.PreferencesService; + +public final class EntryChangeResolver extends GitChangeResolver { + private final EntryChange entryChange; + private final GitContext databaseContext; + + private final PreferencesService preferencesService; + + public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, GitContext databaseContext, PreferencesService preferencesService) { + super(dialogService); + this.entryChange = entryChange; + this.databaseContext = databaseContext; + this.preferencesService = preferencesService; + } + + @Override + public Optional askUserToResolveChange() { + MergeEntriesDialog mergeEntriesDialog = new MergeEntriesDialog(entryChange.getOldEntry(), entryChange.getNewEntry(), preferencesService); + mergeEntriesDialog.setLeftHeaderText(Localization.lang("In JabRef")); + mergeEntriesDialog.setRightHeaderText(Localization.lang("On disk")); + mergeEntriesDialog.configureDiff(new ShowDiffConfig(ThreeWayMergeToolbar.DiffView.SPLIT, BasicDiffMethod.WORDS)); + + return dialogService.showCustomDialogAndWait(mergeEntriesDialog) + .map(this::mapMergeResultToExternalChange); + } + + private EntryChange mapMergeResultToExternalChange(EntriesMergeResult entriesMergeResult) { + return new EntryChange( + entryChange.getOldEntry(), + entriesMergeResult.mergedEntry(), + databaseContext + ); + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java new file mode 100644 index 00000000000..2a505871b7e --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java @@ -0,0 +1,25 @@ +package org.jabref.gui.collab.entrychange; + +import javafx.scene.control.TabPane; + +import org.jabref.gui.collab.GitChangeDetailsView; +import org.jabref.gui.preview.PreviewViewer; +import org.jabref.model.database.GitContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.preferences.PreferencesService; + +public final class EntryWithPreviewAndSourceDetailsView extends GitChangeDetailsView { + + private final PreviewWithSourceTab previewWithSourceTab = new PreviewWithSourceTab(); + + public EntryWithPreviewAndSourceDetailsView(BibEntry entry, GitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { + TabPane tabPanePreviewCode = previewWithSourceTab.getPreviewWithSourceTab(entry, bibGitContext, preferencesService, entryTypesManager, previewViewer); + setLeftAnchor(tabPanePreviewCode, 8d); + setTopAnchor(tabPanePreviewCode, 8d); + setRightAnchor(tabPanePreviewCode, 8d); + setBottomAnchor(tabPanePreviewCode, 8d); + + getChildren().setAll(tabPanePreviewCode); + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java b/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java new file mode 100644 index 00000000000..c4bda2c4456 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java @@ -0,0 +1,73 @@ +package org.jabref.gui.collab.entrychange; + +import java.io.IOException; +import java.io.StringWriter; + +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; + +import org.jabref.gui.preview.PreviewViewer; +import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldPreferences; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.OS; +import org.jabref.model.database.GitContext; +import org.jabref.model.database.GitMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.strings.StringUtil; +import org.jabref.preferences.PreferencesService; + +import org.fxmisc.richtext.CodeArea; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PreviewWithSourceTab { + + private static final Logger LOGGER = LoggerFactory.getLogger(PreviewWithSourceTab.class); + + public TabPane getPreviewWithSourceTab(BibEntry entry, GitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { + return getPreviewWithSourceTab(entry, bibGitContext, preferencesService, entryTypesManager, previewViewer, ""); + } + + public TabPane getPreviewWithSourceTab(BibEntry entry, GitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, String label) { + previewViewer.setLayout(preferencesService.getPreviewPreferences().getSelectedPreviewLayout()); + previewViewer.setEntry(entry); + + CodeArea codeArea = new CodeArea(); + codeArea.setId("bibtexcodearea"); + codeArea.setWrapText(true); + codeArea.setDisable(true); + + TabPane tabPanePreviewCode = new TabPane(); + Tab previewTab = new Tab(); + previewTab.setContent(previewViewer); + + if (StringUtil.isNullOrEmpty(label)) { + previewTab.setText(Localization.lang("Entry preview")); + } else { + previewTab.setText(label); + } + + try { + codeArea.appendText(getSourceString(entry, bibGitContext.getMode(), preferencesService.getFieldPreferences(), entryTypesManager)); + } catch (IOException e) { + LOGGER.error("Error getting Bibtex: {}", entry); + } + codeArea.setEditable(false); + Tab codeTab = new Tab(Localization.lang("%0 source", bibGitContext.getMode().getFormattedName()), codeArea); + + tabPanePreviewCode.getTabs().addAll(previewTab, codeTab); + return tabPanePreviewCode; + } + + private String getSourceString(BibEntry entry, GitMode type, FieldPreferences fieldPreferences, BibEntryTypesManager entryTypesManager) throws IOException { + StringWriter writer = new StringWriter(); + BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); + FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); + new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); + return writer.toString(); + } +} diff --git a/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java b/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java new file mode 100644 index 00000000000..60ef97d87bc --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java @@ -0,0 +1,31 @@ +package org.jabref.gui.collab.entrydelete; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableRemoveEntries; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.entry.BibEntry; + +public final class EntryDelete extends GitChange { + private final BibEntry deletedEntry; + + public EntryDelete(BibEntry deletedEntry, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.deletedEntry = deletedEntry; + setChangeName(deletedEntry.getCitationKey() + .map(key -> Localization.lang("Deleted entry '%0'", key)) + .orElse(Localization.lang("Deleted entry"))); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + databaseContext.getGit().removeEntry(deletedEntry); + undoEdit.addEdit(new UndoableRemoveEntries(databaseContext.getGit(), deletedEntry)); + } + + public BibEntry getDeletedEntry() { + return deletedEntry; + } +} diff --git a/src/main/java/org/jabref/gui/git/groupchange/GroupChange.java b/src/main/java/org/jabref/gui/git/groupchange/GroupChange.java new file mode 100644 index 00000000000..8e57954486a --- /dev/null +++ b/src/main/java/org/jabref/gui/git/groupchange/GroupChange.java @@ -0,0 +1,56 @@ +package org.jabref.gui.collab.groupchange; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.groups.GroupTreeNodeViewModel; +import org.jabref.gui.groups.UndoableModifySubtree; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.logic.bibtex.comparator.GroupDiff; +import org.jabref.logic.groups.DefaultGroupsFactory; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.groups.GroupTreeNode; + +public final class GroupChange extends GitChange { + private final GroupDiff groupDiff; + + public GroupChange(GroupDiff groupDiff, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.groupDiff = groupDiff; + setChangeName(groupDiff.getOriginalGroupRoot() == null ? Localization.lang("Removed all groups") : Localization + .lang("Modified groups tree")); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + GroupTreeNode oldRoot = groupDiff.getOriginalGroupRoot(); + GroupTreeNode newRoot = groupDiff.getNewGroupRoot(); + + GroupTreeNode root = databaseContext.getMetaData().getGroups().orElseGet(() -> { + GroupTreeNode groupTreeNode = new GroupTreeNode(DefaultGroupsFactory.getAllEntriesGroup()); + databaseContext.getMetaData().setGroups(groupTreeNode); + return groupTreeNode; + }); + + final UndoableModifySubtree undo = new UndoableModifySubtree( + new GroupTreeNodeViewModel(databaseContext.getMetaData().getGroups().orElse(null)), + new GroupTreeNodeViewModel(root), Localization.lang("Modified groups")); + root.removeAllChildren(); + if (newRoot == null) { + // I think setting root to null is not possible + root.setGroup(DefaultGroupsFactory.getAllEntriesGroup(), false, false, null); + } else { + // change root group, even though it'll be AllEntries anyway + root.setGroup(newRoot.getGroup(), false, false, null); + for (GroupTreeNode child : newRoot.getChildren()) { + child.copySubtree().moveTo(root); + } + } + + undoEdit.addEdit(undo); + } + + public GroupDiff getGroupDiff() { + return groupDiff; + } +} diff --git a/src/main/java/org/jabref/gui/git/groupchange/GroupChangeDetailsView.java b/src/main/java/org/jabref/gui/git/groupchange/GroupChangeDetailsView.java new file mode 100644 index 00000000000..ae8c1923635 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/groupchange/GroupChangeDetailsView.java @@ -0,0 +1,25 @@ +package org.jabref.gui.collab.groupchange; + +import javafx.scene.control.Label; + +import org.jabref.gui.collab.GitChangeDetailsView; +import org.jabref.logic.l10n.Localization; + +public final class GroupChangeDetailsView extends GitChangeDetailsView { + + public GroupChangeDetailsView(GroupChange groupChange) { + String labelValue = ""; + if (groupChange.getGroupDiff().getNewGroupRoot() == null) { + labelValue = groupChange.getName() + '.'; + } else { + labelValue = Localization.lang("%0. Accepting the change replaces the complete groups tree with the externally modified groups tree.", groupChange.getName()); + } + Label label = new Label(labelValue); + setLeftAnchor(label, 8d); + setTopAnchor(label, 8d); + setRightAnchor(label, 8d); + setBottomAnchor(label, 8d); + + getChildren().setAll(label); + } +} diff --git a/src/main/java/org/jabref/gui/git/metedatachange/MetadataChange.java b/src/main/java/org/jabref/gui/git/metedatachange/MetadataChange.java new file mode 100644 index 00000000000..eff69451d42 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/metedatachange/MetadataChange.java @@ -0,0 +1,32 @@ +package org.jabref.gui.collab.metedatachange; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.logic.bibtex.comparator.MetaDataDiff; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; + +public final class MetadataChange extends GitChange { + private final MetaDataDiff metaDataDiff; + + public MetadataChange(MetaDataDiff metaDataDiff, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.metaDataDiff = metaDataDiff; + setChangeName(Localization.lang("Metadata change")); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + // TODO: Metadata edit should be undoable + databaseContext.setMetaData(metaDataDiff.getNewMetaData()); + // group change is handled by GroupChange, so we set the groups root to the original value + // to prevent any inconsistency + metaDataDiff.getGroupDifferences() + .ifPresent(groupDiff -> databaseContext.getMetaData().setGroups(groupDiff.getOriginalGroupRoot())); + } + + public MetaDataDiff getMetaDataDiff() { + return metaDataDiff; + } +} diff --git a/src/main/java/org/jabref/gui/git/metedatachange/MetadataChangeDetailsView.java b/src/main/java/org/jabref/gui/git/metedatachange/MetadataChangeDetailsView.java new file mode 100644 index 00000000000..71acbe65033 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/metedatachange/MetadataChangeDetailsView.java @@ -0,0 +1,65 @@ +package org.jabref.gui.collab.metedatachange; + +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.VBox; + +import org.jabref.gui.collab.GitChangeDetailsView; +import org.jabref.logic.bibtex.comparator.MetaDataDiff; +import org.jabref.logic.l10n.Localization; +import org.jabref.preferences.PreferencesService; + +public final class MetadataChangeDetailsView extends GitChangeDetailsView { + + public MetadataChangeDetailsView(MetadataChange metadataChange, PreferencesService preferencesService) { + VBox container = new VBox(15); + + Label header = new Label(Localization.lang("The following metadata changed:")); + header.getStyleClass().add("sectionHeader"); + container.getChildren().add(header); + + for (MetaDataDiff.Difference diff : metadataChange.getMetaDataDiff().getDifferences(preferencesService)) { + container.getChildren().add(new Label(getDifferenceString(diff.differenceType()))); + container.getChildren().add(new Label(diff.originalObject().toString())); + container.getChildren().add(new Label(diff.newObject().toString())); + } + + ScrollPane scrollPane = new ScrollPane(container); + scrollPane.setFitToWidth(true); + getChildren().setAll(scrollPane); + + setLeftAnchor(scrollPane, 8d); + setTopAnchor(scrollPane, 8d); + setRightAnchor(scrollPane, 8d); + setBottomAnchor(scrollPane, 8d); + } + + private String getDifferenceString(MetaDataDiff.DifferenceType changeType) { + return switch (changeType) { + case PROTECTED -> + Localization.lang("Library protection"); + case GROUPS_ALTERED -> + Localization.lang("Modified groups tree"); + case ENCODING -> + Localization.lang("Library encoding"); + case SAVE_SORT_ORDER -> + Localization.lang("Save sort order"); + case KEY_PATTERNS -> + Localization.lang("Key patterns"); + case USER_FILE_DIRECTORY -> + Localization.lang("User-specific file directory"); + case LATEX_FILE_DIRECTORY -> + Localization.lang("LaTeX file directory"); + case DEFAULT_KEY_PATTERN -> + Localization.lang("Default pattern"); + case SAVE_ACTIONS -> + Localization.lang("Save actions"); + case MODE -> + Localization.lang("Library mode"); + case GENERAL_FILE_DIRECTORY -> + Localization.lang("General file directory"); + case CONTENT_SELECTOR -> + Localization.lang("Content selectors"); + }; + } +} diff --git a/src/main/java/org/jabref/gui/git/preamblechange/PreambleChange.java b/src/main/java/org/jabref/gui/git/preamblechange/PreambleChange.java new file mode 100644 index 00000000000..94d6928c443 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/preamblechange/PreambleChange.java @@ -0,0 +1,35 @@ +package org.jabref.gui.collab.preamblechange; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoablePreambleChange; +import org.jabref.logic.bibtex.comparator.PreambleDiff; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class PreambleChange extends GitChange { + private static final Logger LOGGER = LoggerFactory.getLogger(PreambleChange.class); + + private final PreambleDiff preambleDiff; + + public PreambleChange(PreambleDiff preambleDiff, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.preambleDiff = preambleDiff; + + setChangeName(Localization.lang("Changed preamble")); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + databaseContext.getGit().setPreamble(preambleDiff.getNewPreamble()); + undoEdit.addEdit(new UndoablePreambleChange(databaseContext.getGit(), preambleDiff.getOriginalPreamble(), preambleDiff.getNewPreamble())); + } + + public PreambleDiff getPreambleDiff() { + return preambleDiff; + } +} diff --git a/src/main/java/org/jabref/gui/git/preamblechange/PreambleChangeDetailsView.java b/src/main/java/org/jabref/gui/git/preamblechange/PreambleChangeDetailsView.java new file mode 100644 index 00000000000..8630f62c240 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/preamblechange/PreambleChangeDetailsView.java @@ -0,0 +1,37 @@ +package org.jabref.gui.collab.preamblechange; + +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; + +import org.jabref.gui.collab.DatabaseChangeDetailsView; +import org.jabref.logic.bibtex.comparator.PreambleDiff; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.strings.StringUtil; + +public final class PreambleChangeDetailsView extends DatabaseChangeDetailsView { + + public PreambleChangeDetailsView(PreambleChange preambleChange) { + PreambleDiff preambleDiff = preambleChange.getPreambleDiff(); + + VBox container = new VBox(); + Label header = new Label(Localization.lang("Changed preamble")); + header.getStyleClass().add("sectionHeader"); + container.getChildren().add(header); + + if (StringUtil.isNotBlank(preambleDiff.getOriginalPreamble())) { + container.getChildren().add(new Label(Localization.lang("Current value: %0", preambleDiff.getOriginalPreamble()))); + } + + if (StringUtil.isNotBlank(preambleDiff.getNewPreamble())) { + container.getChildren().add(new Label(Localization.lang("Value set externally: %0", preambleDiff.getNewPreamble()))); + } else { + container.getChildren().add(new Label(Localization.lang("Value cleared externally"))); + } + setLeftAnchor(container, 8d); + setTopAnchor(container, 8d); + setRightAnchor(container, 8d); + setBottomAnchor(container, 8d); + + getChildren().setAll(container); + } +} diff --git a/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAdd.java b/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAdd.java new file mode 100644 index 00000000000..3aea67fdaf2 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAdd.java @@ -0,0 +1,39 @@ +package org.jabref.gui.collab.stringadd; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableInsertString; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.database.KeyCollisionException; +import org.jabref.model.entry.BibtexString; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class BibTexStringAdd extends GitChange { + private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringAdd.class); + + private final BibtexString addedString; + + public BibTexStringAdd(BibtexString addedString, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.addedString = addedString; + setChangeName(Localization.lang("Added string: '%0'", addedString.getName())); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + try { + databaseContext.getGit().addString(addedString); + undoEdit.addEdit(new UndoableInsertString(databaseContext.getGit(), addedString)); + } catch (KeyCollisionException ex) { + LOGGER.warn("Error: could not add string '{}': {}", addedString.getName(), ex.getMessage(), ex); + } + } + + public BibtexString getAddedString() { + return addedString; + } +} diff --git a/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAddDetailsView.java b/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAddDetailsView.java new file mode 100644 index 00000000000..fffa0d9c81b --- /dev/null +++ b/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAddDetailsView.java @@ -0,0 +1,27 @@ +package org.jabref.gui.collab.stringadd; + +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; + +import org.jabref.gui.collab.GitChangeDetailsView; +import org.jabref.logic.l10n.Localization; + +public final class BibTexStringAddDetailsView extends GitChangeDetailsView { + + public BibTexStringAddDetailsView(BibTexStringAdd stringAdd) { + VBox container = new VBox(); + Label header = new Label(Localization.lang("Added string")); + header.getStyleClass().add("sectionHeader"); + container.getChildren().addAll( + header, + new Label(Localization.lang("Label: %0", stringAdd.getAddedString().getName())), + new Label(Localization.lang("Content: %0", stringAdd.getAddedString().getContent())) + ); + setLeftAnchor(container, 8d); + setTopAnchor(container, 8d); + setRightAnchor(container, 8d); + setBottomAnchor(container, 8d); + + getChildren().setAll(container); + } +} diff --git a/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChange.java b/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChange.java new file mode 100644 index 00000000000..3901f496e92 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChange.java @@ -0,0 +1,43 @@ +package org.jabref.gui.collab.stringchange; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableStringChange; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.entry.BibtexString; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class BibTexStringChange extends GitChange { + private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringChange.class); + + private final BibtexString oldString; + private final BibtexString newString; + + public BibTexStringChange(BibtexString oldString, BibtexString newString, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.oldString = oldString; + this.newString = newString; + + setChangeName(Localization.lang("Modified string: '%0'", oldString.getName())); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + String oldContent = oldString.getContent(); + String newContent = newString.getContent(); + oldString.setContent(newContent); + undoEdit.addEdit(new UndoableStringChange(oldString, false, oldContent, newContent)); + } + + public BibtexString getOldString() { + return oldString; + } + + public BibtexString getNewString() { + return newString; + } +} diff --git a/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChangeDetailsView.java b/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChangeDetailsView.java new file mode 100644 index 00000000000..f5638b4da3f --- /dev/null +++ b/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChangeDetailsView.java @@ -0,0 +1,29 @@ +package org.jabref.gui.collab.stringchange; + +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; + +import org.jabref.gui.collab.GitChangeDetailsView; +import org.jabref.logic.l10n.Localization; + +public final class BibTexStringChangeDetailsView extends GitChangeDetailsView { + + public BibTexStringChangeDetailsView(BibTexStringChange stringChange) { + VBox container = new VBox(); + Label header = new Label(Localization.lang("Modified string")); + header.getStyleClass().add("sectionHeader"); + container.getChildren().addAll( + header, + new Label(Localization.lang("Label: %0", stringChange.getOldString().getName())), + new Label(Localization.lang("Content: %0", stringChange.getNewString().getContent())) + ); + + container.getChildren().add(new Label(Localization.lang("Current content: %0", stringChange.getOldString().getContent()))); + setLeftAnchor(container, 8d); + setTopAnchor(container, 8d); + setRightAnchor(container, 8d); + setBottomAnchor(container, 8d); + + getChildren().setAll(container); + } +} diff --git a/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDelete.java b/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDelete.java new file mode 100644 index 00000000000..fb8ddbdadb2 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDelete.java @@ -0,0 +1,38 @@ +package org.jabref.gui.collab.stringdelete; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableRemoveString; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.entry.BibtexString; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class BibTexStringDelete extends GitChange { + private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringDelete.class); + + private final BibtexString deletedString; + + public BibTexStringDelete(BibtexString deletedString, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.deletedString = deletedString; + setChangeName(Localization.lang("Deleted string: '%0'", deletedString.getName())); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + try { + databaseContext.getGit().removeString(deletedString.getId()); + undoEdit.addEdit(new UndoableRemoveString(databaseContext.getGit(), deletedString)); + } catch (Exception ex) { + LOGGER.warn("Error: could not remove string '{}': {}", deletedString.getName(), ex.getMessage(), ex); + } + } + + public BibtexString getDeletedString() { + return deletedString; + } +} diff --git a/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDeleteDetailsView.java b/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDeleteDetailsView.java new file mode 100644 index 00000000000..163d92df2cc --- /dev/null +++ b/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDeleteDetailsView.java @@ -0,0 +1,27 @@ +package org.jabref.gui.collab.stringdelete; + +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; + +import org.jabref.gui.collab.GitChangeDetailsView; +import org.jabref.logic.l10n.Localization; + +public final class BibTexStringDeleteDetailsView extends GitChangeDetailsView { + + public BibTexStringDeleteDetailsView(BibTexStringDelete stringDelete) { + VBox container = new VBox(); + Label header = new Label(Localization.lang("Deleted string")); + header.getStyleClass().add("sectionHeader"); + container.getChildren().addAll( + header, + new Label(Localization.lang("Label: %0", stringDelete.getDeletedString().getName())), + new Label(Localization.lang("Content: %0", stringDelete.getDeletedString().getContent())) + ); + setLeftAnchor(container, 8d); + setTopAnchor(container, 8d); + setRightAnchor(container, 8d); + setBottomAnchor(container, 8d); + + getChildren().setAll(container); + } +} diff --git a/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRename.java b/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRename.java new file mode 100644 index 00000000000..9bfb89401dd --- /dev/null +++ b/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRename.java @@ -0,0 +1,48 @@ +package org.jabref.gui.collab.stringrename; + +import org.jabref.gui.collab.GitChange; +import org.jabref.gui.collab.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableStringChange; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.GitContext; +import org.jabref.model.entry.BibtexString; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class BibTexStringRename extends GitChange { + private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringRename.class); + + private final BibtexString oldString; + private final BibtexString newString; + + public BibTexStringRename(BibtexString oldString, BibtexString newString, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.oldString = oldString; + this.newString = newString; + + setChangeName(Localization.lang("Renamed string: '%0'", oldString.getName())); + } + + @Override + public void applyChange(NamedCompound undoEdit) { + if (databaseContext.getGit().hasStringByName(newString.getName())) { + // The name to change to is already in the database, so we can't comply. + LOGGER.info("Cannot rename string '{}' to '{}' because the name is already in use", oldString.getName(), newString.getName()); + } + + String currentName = oldString.getName(); + String newName = newString.getName(); + oldString.setName(newName); + undoEdit.addEdit(new UndoableStringChange(oldString, true, currentName, newName)); + } + + public BibtexString getOldString() { + return oldString; + } + + public BibtexString getNewString() { + return newString; + } +} diff --git a/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRenameDetailsView.java b/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRenameDetailsView.java new file mode 100644 index 00000000000..c7bd70f79ea --- /dev/null +++ b/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRenameDetailsView.java @@ -0,0 +1,18 @@ +package org.jabref.gui.collab.stringrename; + +import javafx.scene.control.Label; + +import org.jabref.gui.collab.GitChangeDetailsView; + +public final class BibTexStringRenameDetailsView extends GitChangeDetailsView { + + public BibTexStringRenameDetailsView(BibTexStringRename stringRename) { + Label label = new Label(stringRename.getNewString().getName() + " : " + stringRename.getOldString().getContent()); + setLeftAnchor(label, 8d); + setTopAnchor(label, 8d); + setRightAnchor(label, 8d); + setBottomAnchor(label, 8d); + + getChildren().setAll(label); + } +} diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTab.fxml b/src/main/java/org/jabref/gui/preferences/git/GitTab.fxml index f0da514166b..bb251f2da14 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/git/GitTab.fxml @@ -4,6 +4,7 @@ + @@ -15,4 +16,10 @@ diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTab.java b/src/main/java/org/jabref/gui/preferences/git/GitTab.java index 7af3bc0f09b..15c97dd674e 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTab.java +++ b/src/main/java/org/jabref/gui/preferences/git/GitTab.java @@ -1,6 +1,7 @@ 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; @@ -13,11 +14,14 @@ public class GitTab extends AbstractPreferenceTabView { @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 @@ -31,5 +35,17 @@ private void initialize() { username.textProperty().bindBidirectional(viewModel.getUsernameProperty()); password.textProperty().bindBidirectional(viewModel.getPasswordProperty()); + autoCommit.selectedProperty().addListener( + (observable, oldValue, newValue) -> { + if (newValue) { + autoCommit.selectedProperty().setValue(false); + } + }); + autoSync.selectedProperty().addListener( + (observable, oldValue, newValue) -> { + if (newValue) { + autoSync.selectedProperty().setValue(false); + } + }); } } diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java index 7701a334b36..4de13e5918d 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java @@ -2,21 +2,27 @@ import java.util.List; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.beans.property.BooleanProperty; -import org.jabref.gui.git.GitPreferences; +import org.jabref.logic.git.GitPreferences; import org.jabref.gui.preferences.PreferenceTabViewModel; public class GitTabViewModel implements PreferenceTabViewModel { private StringProperty username = new SimpleStringProperty(); private StringProperty password = new SimpleStringProperty(); + private BooleanProperty autoCommit = new SimpleBooleanProperty(); + + private BooleanProperty autoSync = new SimpleBooleanProperty(); private 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(); } @@ -25,6 +31,8 @@ public GitTabViewModel(GitPreferences gitPreferences) { public void setValues() { this.username.setValue(this.gitPreferences.getUsername()); this.password.setValue(this.gitPreferences.getPassword()); + this.autoCommit.setValue(this.gitPreferences.getAutoCommit()); + this.autoCommit.setValue(this.gitPreferences.getAutoSync()); } @Override @@ -56,4 +64,20 @@ public String getPassword() { public StringProperty getPasswordProperty() { return this.password; } + + public Boolean getAutoCommit() { + return this.autoCommit.get(); + } + + public BooleanProperty getAutoCommitProperty() { + return this.autoCommit; + } + + public Boolean getAutoSync() { + return this.autoSync.get(); + } + + public BooleanProperty getAutoSyncProperty() { + return this.autoSync; + } } diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 7fd38eeb296..539037bc4f8 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -7,10 +7,6 @@ import java.nio.file.Path; import java.util.Optional; -import org.jabref.gui.git.GitCredentialsDialogView; -import org.jabref.gui.git.GitPreferences; -import org.jabref.logic.util.io.FileUtil; - import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; @@ -21,6 +17,7 @@ import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.jabref.logic.util.io.FileUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,9 +29,9 @@ public class GitHandler { static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); final Path repositoryPath; final File repositoryPathAsFile; - String gitUsername = Optional.ofNullable(System.getenv("GIT_EMAIL")).orElse(""); - String gitPassword = Optional.ofNullable(System.getenv("GIT_PW")).orElse(""); - CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsername, gitPassword); + String gitUsername; + String gitPassword; + CredentialsProvider credentialsProvider; private GitPreferences gitPreferences; /** @@ -45,6 +42,9 @@ public class GitHandler { public GitHandler(Path repositoryPath) { this.repositoryPath = repositoryPath; this.repositoryPathAsFile = this.repositoryPath.toFile(); + this.gitUsername = Optional.ofNullable(System.getenv("GIT_EMAIL")).orElse(""); + this.gitPassword = Optional.ofNullable(System.getenv("GIT_PW")).orElse(""); + this.credentialsProvider = new UsernamePasswordCredentialsProvider(this.gitUsername, this.gitPassword); if (!isGitRepository()) { try { @@ -70,6 +70,45 @@ public GitHandler(Path repositoryPath) { } } + public GitHandler(Path repositoryPath, GitPreferences gitPreferences) { + this.gitPreferences = gitPreferences; + if (gitPreferences.getUsername() != null && gitPreferences.getPassword() != null) { + this.gitUsername = gitPreferences.getUsername(); + this.gitPassword = gitPreferences.getPassword(); + } else { + this.gitUsername = Optional.ofNullable(System.getenv("GIT_EMAIL")).orElse(""); + this.gitPassword = Optional.ofNullable(System.getenv("GIT_PW")).orElse(""); + } + this.credentialsProvider = new UsernamePasswordCredentialsProvider(this.gitUsername, this.gitPassword); + + this.repositoryPath = repositoryPath; + this.repositoryPathAsFile = this.repositoryPath.toFile(); + + + if (!isGitRepository()) { + try { + Git.init() + .setDirectory(repositoryPathAsFile) + .setInitialBranch("main") + .call(); + setupGitIgnore(); + String initialCommit = "Initial commit"; + if (!createCommitOnCurrentBranch(initialCommit, false)) { + // Maybe, setupGitIgnore failed and did not add something + // Then, we create an empty commit + try (Git git = Git.open(repositoryPathAsFile)) { + git.commit() + .setAllowEmpty(true) + .setMessage(initialCommit) + .call(); + } + } + } catch (GitAPIException | IOException e) { + LOGGER.error("Initialization failed", e); + } + } + } + void setupGitIgnore() { try { Path gitignore = Path.of(repositoryPath.toString(), ".gitignore"); @@ -227,31 +266,14 @@ public void pushCommitsToRemoteRepository() throws IOException { git.verifySignature(); - if (this.gitPreferences.getUsername() != null && this.gitPreferences.getPassword() != null) { - this.gitUsername = this.gitPreferences.getUsername(); - this.gitPassword = this.gitPreferences.getPassword(); - this.credentialsProvider = new UsernamePasswordCredentialsProvider(this.gitUsername, this.gitPassword); - } - if (isSshRemoteRepository) { TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); git.push() .setTransportConfigCallback(transportConfigCallback) .call(); - } else if (this.gitPassword.isEmpty() || this.gitUsername.isEmpty()) { - GitCredentialsDialogView gitCredentialsDialogView = new GitCredentialsDialogView(); - - gitCredentialsDialogView.showGitCredentialsDialog(); - - this.credentialsProvider = new UsernamePasswordCredentialsProvider( - gitCredentialsDialogView.getGitUsername(), - gitCredentialsDialogView.getGitPassword() - ); - - git.push() - .setCredentialsProvider(this.credentialsProvider) - .call(); - } else { + }else if (this.gitPassword.isEmpty() || this.gitUsername.isEmpty()) { + throw new IOException("No git credentials"); + } else { git.push() .setCredentialsProvider(this.credentialsProvider) .call(); @@ -270,11 +292,34 @@ public void pushCommitsToRemoteRepository() throws IOException { * Pulls all commits made to the branch that is tracked by the currently checked out branch. * If pulling to remote fails, it fails silently. */ - public void pullOnCurrentBranch() { - try (Git git = Git.open(this.repositoryPathAsFile)) { + public void pullOnCurrentBranch() throws IOException { + Git git = Git.open(this.repositoryPathAsFile); + + String remoteURL = git.getRepository().getConfig().getString("remote", "origin", "url"); + boolean isSshRemoteRepository = remoteURL != null && remoteURL.contains("git@"); + + git.verifySignature(); + + if (isSshRemoteRepository) { try { + TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); git.pull() - .call(); + .setTransportConfigCallback(transportConfigCallback) + .call(); + } catch (GitAPIException e) { + if (e.getMessage().equals("origin: not found")) { + LOGGER.info("No remote repository detected. Push skipped."); + } else { + LOGGER.info("Failed to pull"); + throw new RuntimeException(e); + } + } + } else if (this.gitPassword.isEmpty() || this.gitUsername.isEmpty()) { + throw new IOException("No git credentials"); + } else { + try { + git.pull() + .setCredentialsProvider(this.credentialsProvider).call(); } catch (GitAPIException e) { if (e.getMessage().equals("origin: not found")) { LOGGER.info("No remote repository detected. Push skipped."); @@ -283,9 +328,9 @@ public void pullOnCurrentBranch() { throw new RuntimeException(e); } } - } catch (IOException ex) { - LOGGER.info("Failed pulling git repository"); } + + } /** @@ -301,7 +346,9 @@ public String getCurrentlyCheckedOutBranch() { } } - public void setGitPreferences(GitPreferences gitPreferences) { - this.gitPreferences = gitPreferences; + public void setCredentialsProvider(CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + this.gitUsername = "credentialsProvider"; + this.gitPassword = "credentialsProvider"; } } diff --git a/src/main/java/org/jabref/logic/git/GitPreferences.java b/src/main/java/org/jabref/logic/git/GitPreferences.java index 2939575a779..3599c2d6846 100644 --- a/src/main/java/org/jabref/logic/git/GitPreferences.java +++ b/src/main/java/org/jabref/logic/git/GitPreferences.java @@ -1,38 +1,92 @@ package org.jabref.logic.git; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; public class GitPreferences { - private final StringProperty username = new SimpleStringProperty(); - private final StringProperty password = new SimpleStringProperty(); + private StringProperty username; + private StringProperty password; + private BooleanProperty autoCommit; - public GitPreferences(String username, String password) { - this.username.setValue(username); - this.password.set(password); + private BooleanProperty autoSync; + + public GitPreferences(String username, String password, Boolean autoCommit, Boolean autoSync) { + this.username = new SimpleStringProperty(username); + this.password = new SimpleStringProperty(password); + this.autoCommit = new SimpleBooleanProperty(autoCommit); + this.autoSync = new SimpleBooleanProperty(autoSync); } - public String getUsername() { - return username.toString(); + public GitPreferences(StringProperty username, StringProperty password, BooleanProperty autoCommit, BooleanProperty autoSync) { + this.username = username; + this.password = password; + this.autoCommit = autoCommit; + this.autoSync = autoSync; } - public StringProperty usernameProperty() { - return username; + public StringProperty getUsernameProperty() { + return this.username; } - public void setUsername(String username) { - this.username.set(username); + public Boolean getAutoCommit() { + return this.autoCommit.get(); + } + + public BooleanProperty getAutoCommitProperty() { + return this.autoCommit; + } + + public Boolean getAutoSync() { + return this.autoSync.get(); } - public final String getPassword() { - return password.getValue(); + public BooleanProperty getAutoSyncProperty() { + return this.autoSync; } - public StringProperty passwordProperty() { - return password; + public void setAutoCommit(Boolean autoCommit) { + this.autoCommit = new SimpleBooleanProperty(autoCommit); + } + + public void setAutoCommitProperty(BooleanProperty autoCommit) { + this.autoCommit = autoCommit; + } + + public void setAutoSync(Boolean autoSync) { + this.autoSync = new SimpleBooleanProperty(autoSync); + } + + public void setAutoSyncProperty(BooleanProperty autoSync) { + this.autoSync = autoSync; + } + + public String getUsername() { + return this.username.get(); + } + + public StringProperty getPasswordProperty() { + return this.password; + } + + public String getPassword() { + return this.password.get(); + } + + public void setPassword(StringProperty password) { + this.password = password; } public void setPassword(String password) { - this.password.set(password); + this.password = new SimpleStringProperty(password); + } + + public void setUsername(StringProperty username) { + this.username = username; + } + + public void setUsername(String username) { + this.username = new SimpleStringProperty(username); } } diff --git a/src/main/java/org/jabref/model/git/Git.java b/src/main/java/org/jabref/model/git/Git.java new file mode 100644 index 00000000000..8c545c0126b --- /dev/null +++ b/src/main/java/org/jabref/model/git/Git.java @@ -0,0 +1,641 @@ +package org.jabref.model.git; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.model.database.event.EntriesAddedEvent; +import org.jabref.model.database.event.EntriesRemovedEvent; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibtexString; +import org.jabref.model.entry.Month; +import org.jabref.model.entry.event.EntriesEventSource; +import org.jabref.model.entry.event.EntryChangedEvent; +import org.jabref.model.entry.event.FieldChangedEvent; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.strings.StringUtil; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A bibliography database. This is the "bib" file (or the library stored in a shared SQL database) + */ +public class Git { + + private static final Logger LOGGER = LoggerFactory.getLogger(Git.class); + private static final Pattern RESOLVE_CONTENT_PATTERN = Pattern.compile(".*#[^#]+#.*"); + + /** + * State attributes + */ + private final ObservableList entries = FXCollections.synchronizedObservableList(FXCollections.observableArrayList(BibEntry::getObservables)); + private Map bibtexStrings = new ConcurrentHashMap<>(); + + private final EventBus eventBus = new EventBus(); + + private String preamble; + + // All file contents below the last entry in the file + private String epilog = ""; + + private String sharedDatabaseID; + + private String newLineSeparator = System.lineSeparator(); + + public Git(List entries, String newLineSeparator) { + this(entries); + this.newLineSeparator = newLineSeparator; + } + + public Git(List entries) { + this(); + insertEntries(entries); + } + + public Git() { + this.registerListener(new KeyChangeListener(this)); + } + + /** + * Returns a text with references resolved according to an optionally given database. + * + * @param toResolve maybenull The text to resolve. + * @param database maybenull The database to use for resolving the text. + * @return The resolved text or the original text if either the text or the database are null + * @deprecated use {@link Git#resolveForStrings(String)} + */ + @Deprecated + public static String getText(String toResolve, Git database) { + if ((toResolve != null) && (database != null)) { + return database.resolveForStrings(toResolve); + } + return toResolve; + } + + /** + * Returns the number of entries. + */ + public int getEntryCount() { + return entries.size(); + } + + /** + * Checks if the database contains entries. + */ + public boolean hasEntries() { + return !entries.isEmpty(); + } + + /** + * Returns the list of entries sorted by the given comparator. + */ + public List getEntriesSorted(Comparator comparator) { + List entriesSorted = new ArrayList<>(entries); + entriesSorted.sort(comparator); + return entriesSorted; + } + + /** + * Returns whether an entry with the given ID exists (-> entry_type + hashcode). + */ + public boolean containsEntryWithId(String id) { + return entries.stream().anyMatch(entry -> entry.getId().equals(id)); + } + + public ObservableList getEntries() { + return FXCollections.unmodifiableObservableList(entries); + } + + /** + * Returns a set of Strings, that contains all field names that are visible. This means that the fields + * are not internal fields. Internal fields are fields, that are starting with "_". + * + * @return set of fieldnames, that are visible + */ + public Set getAllVisibleFields() { + Set allFields = new TreeSet<>(Comparator.comparing(Field::getName)); + for (BibEntry e : getEntries()) { + allFields.addAll(e.getFields()); + } + return allFields.stream().filter(field -> !FieldFactory.isInternalField(field)) + .collect(Collectors.toSet()); + } + + /** + * Returns the entry with the given citation key. + */ + public synchronized Optional getEntryByCitationKey(String key) { + for (BibEntry entry : entries) { + if (key.equals(entry.getCitationKey().orElse(null))) { + return Optional.of(entry); + } + } + return Optional.empty(); + } + + /** + * Collects entries having the specified citation key and returns these entries as list. + * The order of the entries is the order they appear in the database. + * + * @return list of entries that contains the given key + */ + public synchronized List getEntriesByCitationKey(String key) { + List result = new ArrayList<>(); + + for (BibEntry entry : entries) { + entry.getCitationKey().ifPresent(entryKey -> { + if (key.equals(entryKey)) { + result.add(entry); + } + }); + } + return result; + } + + public synchronized void insertEntry(BibEntry entry) { + insertEntry(entry, EntriesEventSource.LOCAL); + } + + /** + * Inserts the entry. + * + * @param entry entry to insert + * @param eventSource source the event is sent from + */ + public synchronized void insertEntry(BibEntry entry, EntriesEventSource eventSource) { + insertEntries(Collections.singletonList(entry), eventSource); + } + + public synchronized void insertEntries(BibEntry... entries) { + insertEntries(Arrays.asList(entries), EntriesEventSource.LOCAL); + } + + public synchronized void insertEntries(List entries) { + insertEntries(entries, EntriesEventSource.LOCAL); + } + + public synchronized void insertEntries(List newEntries, EntriesEventSource eventSource) { + Objects.requireNonNull(newEntries); + for (BibEntry entry : newEntries) { + entry.registerListener(this); + } + if (newEntries.isEmpty()) { + eventBus.post(new EntriesAddedEvent(newEntries, eventSource)); + } else { + eventBus.post(new EntriesAddedEvent(newEntries, newEntries.get(0), eventSource)); + } + entries.addAll(newEntries); + } + + public synchronized void removeEntry(BibEntry bibEntry) { + removeEntries(Collections.singletonList(bibEntry)); + } + + public synchronized void removeEntry(BibEntry bibEntry, EntriesEventSource eventSource) { + removeEntries(Collections.singletonList(bibEntry), eventSource); + } + + /** + * Removes the given entries. + * The entries removed based on the id {@link BibEntry#getId()} + * + * @param toBeDeleted Entries to delete + */ + public synchronized void removeEntries(List toBeDeleted) { + removeEntries(toBeDeleted, EntriesEventSource.LOCAL); + } + + /** + * Removes the given entries. + * The entries are removed based on the id {@link BibEntry#getId()} + * + * @param toBeDeleted Entry to delete + * @param eventSource Source the event is sent from + */ + public synchronized void removeEntries(List toBeDeleted, EntriesEventSource eventSource) { + Objects.requireNonNull(toBeDeleted); + + List ids = new ArrayList<>(); + for (BibEntry entry : toBeDeleted) { + ids.add(entry.getId()); + } + boolean anyRemoved = entries.removeIf(entry -> ids.contains(entry.getId())); + if (anyRemoved) { + eventBus.post(new EntriesRemovedEvent(toBeDeleted, eventSource)); + } + } + + /** + * Returns the database's preamble. + * If the preamble text consists only of whitespace, then also an empty optional is returned. + */ + public synchronized Optional getPreamble() { + if (StringUtil.isBlank(preamble)) { + return Optional.empty(); + } else { + return Optional.of(preamble); + } + } + + /** + * Sets the database's preamble. + */ + public synchronized void setPreamble(String preamble) { + this.preamble = preamble; + } + + /** + * Inserts a Bibtex String. + */ + public synchronized void addString(BibtexString string) throws KeyCollisionException { + String id = string.getId(); + + if (hasStringByName(string.getName())) { + throw new KeyCollisionException("A string with that label already exists", id); + } + + if (bibtexStrings.containsKey(id)) { + throw new KeyCollisionException("Duplicate BibTeX string id.", id); + } + + bibtexStrings.put(id, string); + } + + /** + * Replaces the existing lists of BibTexString with the given one + * Duplicates throw KeyCollisionException + * + * @param stringsToAdd The collection of strings to set + */ + public void setStrings(List stringsToAdd) { + bibtexStrings = new ConcurrentHashMap<>(); + stringsToAdd.forEach(this::addString); + } + + /** + * Removes the string with the given id. + */ + public void removeString(String id) { + bibtexStrings.remove(id); + } + + /** + * Returns a Set of keys to all BibtexString objects in the database. + * These are in no sorted order. + */ + public Set getStringKeySet() { + return bibtexStrings.keySet(); + } + + /** + * Returns a Collection of all BibtexString objects in the database. + * These are in no particular order. + */ + public Collection getStringValues() { + return bibtexStrings.values(); + } + + /** + * Returns the string with the given id. + */ + public BibtexString getString(String id) { + return bibtexStrings.get(id); + } + + /** + * Returns the string with the given name/label + */ + public Optional getStringByName(String name) { + return getStringValues().stream().filter(string -> string.getName().equals(name)).findFirst(); + } + + /** + * Returns the number of strings. + */ + public int getStringCount() { + return bibtexStrings.size(); + } + + /** + * Check if there are strings. + */ + public boolean hasNoStrings() { + return bibtexStrings.isEmpty(); + } + + /** + * Copies the preamble of another Git. + * + * @param database another Git + */ + public void copyPreamble(Git database) { + setPreamble(database.getPreamble().orElse("")); + } + + /** + * Returns true if a string with the given label already exists. + */ + public synchronized boolean hasStringByName(String label) { + return bibtexStrings.values().stream().anyMatch(value -> value.getName().equals(label)); + } + + /** + * Resolves any references to strings contained in this field content, + * if possible. + */ + public String resolveForStrings(String content) { + Objects.requireNonNull(content, "Content for resolveForStrings must not be null."); + return resolveContent(content, new HashSet<>(), new HashSet<>()); + } + + /** + * Get all strings used in the entries. + */ + public Collection getUsedStrings(Collection entries) { + List result = new ArrayList<>(); + Set allUsedIds = new HashSet<>(); + + // All entries + for (BibEntry entry : entries) { + for (String fieldContent : entry.getFieldValues()) { + resolveContent(fieldContent, new HashSet<>(), allUsedIds); + } + } + + // Preamble + if (preamble != null) { + resolveContent(preamble, new HashSet<>(), allUsedIds); + } + + for (String stringId : allUsedIds) { + result.add((BibtexString) bibtexStrings.get(stringId).clone()); + } + + return result; + } + + /** + * Take the given collection of BibEntry and resolve any string + * references. + * + * @param entriesToResolve A collection of BibtexEntries in which all strings of the form + * #xxx# will be resolved against the hash map of string + * references stored in the database. + * @param inPlace If inPlace is true then the given BibtexEntries will be modified, if false then copies of the BibtexEntries are made before resolving the strings. + * @return a list of bibtexentries, with all strings resolved. It is dependent on the value of inPlace whether copies are made or the given BibtexEntries are modified. + */ + public List resolveForStrings(Collection entriesToResolve, boolean inPlace) { + Objects.requireNonNull(entriesToResolve, "entries must not be null."); + + List results = new ArrayList<>(entriesToResolve.size()); + + for (BibEntry entry : entriesToResolve) { + results.add(this.resolveForStrings(entry, inPlace)); + } + return results; + } + + /** + * Take the given BibEntry and resolve any string references. + * + * @param entry A BibEntry in which all strings of the form #xxx# will be + * resolved against the hash map of string references stored in + * the database. + * @param inPlace If inPlace is true then the given BibEntry will be + * modified, if false then a copy is made using close made before + * resolving the strings. + * @return a BibEntry with all string references resolved. It is + * dependent on the value of inPlace whether a copy is made or the + * given BibtexEntries is modified. + */ + public BibEntry resolveForStrings(BibEntry entry, boolean inPlace) { + BibEntry resultingEntry; + if (inPlace) { + resultingEntry = entry; + } else { + resultingEntry = (BibEntry) entry.clone(); + } + + for (Map.Entry field : resultingEntry.getFieldMap().entrySet()) { + resultingEntry.setField(field.getKey(), this.resolveForStrings(field.getValue())); + } + return resultingEntry; + } + + /** + * If the label represents a string contained in this database, returns + * that string's content. Resolves references to other strings, taking + * care not to follow a circular reference pattern. + * If the string is undefined, returns null. + */ + private String resolveString(String label, Set usedIds, Set allUsedIds) { + Objects.requireNonNull(label); + Objects.requireNonNull(usedIds); + Objects.requireNonNull(allUsedIds); + + for (BibtexString string : bibtexStrings.values()) { + if (string.getName().equalsIgnoreCase(label)) { + // First check if this string label has been resolved + // earlier in this recursion. If so, we have a + // circular reference, and have to stop to avoid + // infinite recursion. + if (usedIds.contains(string.getId())) { + LOGGER.info("Stopped due to circular reference in strings: " + label); + return label; + } + // If not, log this string's ID now. + usedIds.add(string.getId()); + if (allUsedIds != null) { + allUsedIds.add(string.getId()); + } + + // Ok, we found the string. Now we must make sure we + // resolve any references to other strings in this one. + String result = string.getContent(); + result = resolveContent(result, usedIds, allUsedIds); + + // Finished with recursing this branch, so we remove our + // ID again: + usedIds.remove(string.getId()); + + return result; + } + } + + // If we get to this point, the string has obviously not been defined locally. + // Check if one of the standard BibTeX month strings has been used: + Optional month = Month.getMonthByShortName(label); + return month.map(Month::getFullName).orElse(null); + } + + private String resolveContent(String result, Set usedIds, Set allUsedIds) { + String res = result; + if (RESOLVE_CONTENT_PATTERN.matcher(res).matches()) { + StringBuilder newRes = new StringBuilder(); + int piv = 0; + int next; + while ((next = res.indexOf(FieldWriter.BIBTEX_STRING_START_END_SYMBOL, piv)) >= 0) { + // We found the next string ref. Append the text + // up to it. + if (next > 0) { + newRes.append(res, piv, next); + } + int stringEnd = res.indexOf(FieldWriter.BIBTEX_STRING_START_END_SYMBOL, next + 1); + if (stringEnd >= 0) { + // We found the boundaries of the string ref, + // now resolve that one. + String refLabel = res.substring(next + 1, stringEnd); + String resolved = resolveString(refLabel, usedIds, allUsedIds); + + if (resolved == null) { + // Could not resolve string. Display the # + // characters rather than removing them: + newRes.append(res, next, stringEnd + 1); + } else { + // The string was resolved, so we display its meaning only, + // stripping the # characters signifying the string label: + newRes.append(resolved); + } + piv = stringEnd + 1; + } else { + // We did not find the boundaries of the string ref. This + // makes it impossible to interpret it as a string label. + // So we should just append the rest of the text and finish. + newRes.append(res.substring(next)); + piv = res.length(); + break; + } + } + if (piv < (res.length() - 1)) { + newRes.append(res.substring(piv)); + } + res = newRes.toString(); + } + return res; + } + + public String getEpilog() { + return epilog; + } + + public void setEpilog(String epilog) { + this.epilog = epilog; + } + + /** + * Registers a listener object (subscriber) to the internal event bus. + * The following events are posted: + * + * - {@link EntriesAddedEvent} + * - {@link EntryChangedEvent} + * - {@link EntriesRemovedEvent} + * + * @param listener listener (subscriber) to add + */ + public void registerListener(Object listener) { + this.eventBus.register(listener); + } + + /** + * Unregisters an listener object. + * + * @param listener listener (subscriber) to remove + */ + public void unregisterListener(Object listener) { + try { + this.eventBus.unregister(listener); + } catch (IllegalArgumentException e) { + // occurs if the event source has not been registered, should not prevent shutdown + LOGGER.debug("Problem unregistering", e); + } + } + + @Subscribe + private void relayEntryChangeEvent(FieldChangedEvent event) { + eventBus.post(event); + } + + public Optional getReferencedEntry(BibEntry entry) { + return entry.getField(StandardField.CROSSREF).flatMap(this::getEntryByCitationKey); + } + + public Optional getSharedDatabaseID() { + return Optional.ofNullable(this.sharedDatabaseID); + } + + public void setSharedDatabaseID(String sharedDatabaseID) { + this.sharedDatabaseID = sharedDatabaseID; + } + + public boolean isShared() { + return getSharedDatabaseID().isPresent(); + } + + public void clearSharedDatabaseID() { + this.sharedDatabaseID = null; + } + + /** + * Generates and sets a random ID which is globally unique. + * + * @return The generated sharedDatabaseID + */ + public String generateSharedDatabaseID() { + this.sharedDatabaseID = new BigInteger(128, new SecureRandom()).toString(32); + return this.sharedDatabaseID; + } + + /** + * Returns the number of occurrences of the given citation key in this database. + */ + public long getNumberOfCitationKeyOccurrences(String key) { + return entries.stream() + .flatMap(entry -> entry.getCitationKey().stream()) + .filter(key::equals) + .count(); + } + + /** + * Checks if there is more than one occurrence of the citation key. + */ + public boolean isDuplicateCitationKeyExisting(String key) { + return getNumberOfCitationKeyOccurrences(key) > 1; + } + + /** + * Set the newline separator. + */ + public void setNewLineSeparator(String newLineSeparator) { + this.newLineSeparator = newLineSeparator; + } + + /** + * Returns the string used to indicate a linebreak + */ + public String getNewLineSeparator() { + return newLineSeparator; + } +} diff --git a/src/main/java/org/jabref/model/git/GitContext.java b/src/main/java/org/jabref/model/git/GitContext.java new file mode 100644 index 00000000000..3163f15a2b2 --- /dev/null +++ b/src/main/java/org/jabref/model/git/GitContext.java @@ -0,0 +1,265 @@ +package org.jabref.model.git; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.architecture.AllowedToUseLogic; +import org.jabref.gui.LibraryTab; +import org.jabref.logic.crawler.Crawler; +import org.jabref.logic.crawler.StudyRepository; +import org.jabref.logic.shared.DatabaseLocation; +import org.jabref.logic.shared.DatabaseSynchronizer; +import org.jabref.logic.util.CoarseChangeFilter; +import org.jabref.logic.util.OS; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.metadata.MetaData; +import org.jabref.model.study.Study; +import org.jabref.preferences.FilePreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents everything related to a BIB file. + * + *

The entries are stored in Git, the other data in MetaData + * and the options relevant for this file in Defaults. + *

+ *

+ * To get an instance for a .bib file, use {@link org.jabref.logic.importer.fileformat.BibtexParser}. + *

+ */ +@AllowedToUseLogic("because it needs access to shared database features") +public class GitContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); + + private final Git database; + private MetaData metaData; + + /** + * The path where this database was last saved to. + */ + private Path path; + + private DatabaseSynchronizer dbmsSynchronizer; + private CoarseChangeFilter dbmsListener; + private DatabaseLocation location; + + public GitContext() { + this(new Git()); + } + + public GitContext(Git database) { + this(database, new MetaData()); + } + + public GitContext(Git database, MetaData metaData) { + this.database = Objects.requireNonNull(database); + this.metaData = Objects.requireNonNull(metaData); + this.location = DatabaseLocation.LOCAL; + } + + public GitContext(Git database, MetaData metaData, Path path) { + this(database, metaData, path, DatabaseLocation.LOCAL); + } + + public GitContext(Git database, MetaData metaData, Path path, DatabaseLocation location) { + this(database, metaData); + Objects.requireNonNull(location); + this.path = path; + + if (location == DatabaseLocation.LOCAL) { + convertToLocalDatabase(); + } + } + + public GitMode getMode() { + return metaData.getMode().orElse(GitMode.BIBLATEX); + } + + public void setMode(GitMode bibDatabaseMode) { + metaData.setMode(bibDatabaseMode); + } + + public void setDatabasePath(Path file) { + this.path = file; + } + + /** + * Get the path where this database was last saved to or loaded from, if any. + * + * @return Optional of the relevant Path, or Optional.empty() if none is defined. + */ + public Optional getDatabasePath() { + return Optional.ofNullable(path); + } + + public void clearDatabasePath() { + this.path = null; + } + + public Git getDatabase() { + return database; + } + + public MetaData getMetaData() { + return metaData; + } + + public void setMetaData(MetaData metaData) { + this.metaData = Objects.requireNonNull(metaData); + } + + public boolean isBiblatexMode() { + return getMode() == GitMode.BIBLATEX; + } + + /** + * Returns whether this .bib file belongs to a {@link Study} + */ + public boolean isStudy() { + return this.getDatabasePath() + .map(path -> path.getFileName().toString().equals(Crawler.FILENAME_STUDY_RESULT_BIB) && + Files.exists(path.resolveSibling(StudyRepository.STUDY_DEFINITION_FILE_NAME))) + .orElse(false); + } + + /** + * Look up the directories set up for this database. + * There can be up to four directories definitions for these files: + *
    + *
  1. next to the .bib file.
  2. + *
  3. the preferences can specify a default one.
  4. + *
  5. the database's metadata can specify a general directory.
  6. + *
  7. the database's metadata can specify a user-specific directory.
  8. + *
+ *

+ * The settings are prioritized in the following order, and the first defined setting is used: + *

    + *
  1. user-specific metadata directory
  2. + *
  3. general metadata directory
  4. + *
  5. BIB file directory (if configured in the preferences AND none of the two above directories are configured)
  6. + *
  7. preferences directory (if .bib file directory should not be used according to the preferences)
  8. + *
+ * + * @param preferences The fileDirectory preferences + */ + public List getFileDirectories(FilePreferences preferences) { + List fileDirs = new ArrayList<>(); + + // 1. Metadata user-specific directory + metaData.getUserFileDirectory(preferences.getUserAndHost()) + .ifPresent(userFileDirectory -> fileDirs.add(getFileDirectoryPath(userFileDirectory))); + + // 2. Metadata general directory + metaData.getDefaultFileDirectory() + .ifPresent(metaDataDirectory -> fileDirs.add(getFileDirectoryPath(metaDataDirectory))); + + // 3. BIB file directory or Main file directory + // fileDirs.isEmpty in the case, 1) no user-specific file directory and 2) no general file directory is set + // (in the metadata of the bib file) + if (fileDirs.isEmpty() && preferences.shouldStoreFilesRelativeToBibFile()) { + getDatabasePath().ifPresent(dbPath -> { + Path parentPath = dbPath.getParent(); + if (parentPath == null) { + parentPath = Path.of(System.getProperty("user.dir")); + } + Objects.requireNonNull(parentPath, "BibTeX database parent path is null"); + fileDirs.add(parentPath); + }); + } else { + // Main file directory + preferences.getMainFileDirectory().ifPresent(fileDirs::add); + } + + return fileDirs.stream().map(Path::toAbsolutePath).collect(Collectors.toList()); + } + + /** + * Returns the first existing file directory from {@link #getFileDirectories(FilePreferences)} + * + * @return the path - or an empty optional, if none of the directories exists + */ + public Optional getFirstExistingFileDir(FilePreferences preferences) { + return getFileDirectories(preferences).stream() + .filter(Files::exists) + .findFirst(); + } + + private Path getFileDirectoryPath(String directoryName) { + Path directory = Path.of(directoryName); + // If this directory is relative, we try to interpret it as relative to + // the file path of this BIB file: + Optional databaseFile = getDatabasePath(); + if (!directory.isAbsolute() && databaseFile.isPresent()) { + return databaseFile.get().getParent().resolve(directory).normalize(); + } + return directory; + } + + public DatabaseSynchronizer getDBMSSynchronizer() { + return this.dbmsSynchronizer; + } + + public void clearDBMSSynchronizer() { + this.dbmsSynchronizer = null; + } + + public DatabaseLocation getLocation() { + return this.location; + } + + public void convertToSharedDatabase(DatabaseSynchronizer dmbsSynchronizer) { + this.dbmsSynchronizer = dmbsSynchronizer; + + this.dbmsListener = new CoarseChangeFilter(this); + dbmsListener.registerListener(dbmsSynchronizer); + + this.location = DatabaseLocation.SHARED; + } + + public void convertToLocalDatabase() { + if (Objects.nonNull(dbmsListener) && (location == DatabaseLocation.SHARED)) { + dbmsListener.unregisterListener(dbmsSynchronizer); + dbmsListener.shutdown(); + } + + this.location = DatabaseLocation.LOCAL; + } + + public List getEntries() { + return database.getEntries(); + } + + public Path getFulltextIndexPath() { + Path appData = OS.getNativeDesktop().getFulltextIndexBaseDirectory(); + Path indexPath; + + if (getDatabasePath().isPresent()) { + indexPath = appData.resolve(String.valueOf(this.getDatabasePath().get().hashCode())); + LOGGER.debug("Index path for {} is {}", getDatabasePath().get(), indexPath); + return indexPath; + } + + indexPath = appData.resolve("unsaved"); + LOGGER.debug("Using index for unsaved database: {}", indexPath); + return indexPath; + } + + @Override + public String toString() { + return "GitContext{" + + "metaData=" + metaData + + ", mode=" + getMode() + + ", databasePath=" + getDatabasePath() + + ", biblatexMode=" + isBiblatexMode() + + ", fulltextIndexPath=" + getFulltextIndexPath() + + '}'; + } +} diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 8d405006e39..84772936af2 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -69,7 +69,7 @@ import org.jabref.logic.exporter.MetaDataSerializer; import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.exporter.TemplateExporter; -import org.jabref.gui.git.GitPreferences; +import org.jabref.logic.git.GitPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.fetcher.DoiFetcher; @@ -380,6 +380,9 @@ public class JabRefPreferences implements PreferencesService { private static final String GIT_USERNAME = "gitUsername"; private static final String GIT_PASSWORD = "gitPassword"; + private static final String GIT_AUTOCOMMIT = "autoCommit"; + private static final String GIT_AUTOSYNC = "autoSync"; + // Web search private static final String FETCHER_CUSTOM_KEY_NAMES = "fetcherCustomKeyNames"; private static final String FETCHER_CUSTOM_KEY_USES = "fetcherCustomKeyUses"; @@ -663,6 +666,9 @@ private JabRefPreferences() { defaults.put(PROTECTED_TERMS_ENABLED_EXTERNAL, ""); defaults.put(PROTECTED_TERMS_DISABLED_EXTERNAL, ""); + defaults.put(GIT_AUTOCOMMIT, Boolean.TRUE); + defaults.put(GIT_AUTOSYNC, Boolean.TRUE); + // OpenOffice/LibreOffice if (OS.WINDOWS) { defaults.put(OO_EXECUTABLE_PATH, OpenOfficePreferences.DEFAULT_WIN_EXEC_PATH); @@ -2809,11 +2815,15 @@ public GitPreferences getGitPreferences() { gitPreferences = new GitPreferences( get(GIT_USERNAME), - get(GIT_PASSWORD) + get(GIT_PASSWORD), + getBoolean(GIT_AUTOCOMMIT), + getBoolean(GIT_AUTOSYNC) ); EasyBind.listen(gitPreferences.getUsernameProperty(), (obs, oldValue, newValue) -> put(GIT_USERNAME, newValue)); EasyBind.listen(gitPreferences.getPasswordProperty(), (obs, oldValue, newValue) -> put(GIT_PASSWORD, newValue)); + EasyBind.listen(gitPreferences.getAutoCommitProperty(), (obs, oldValue, newValue) -> putBoolean(GIT_AUTOCOMMIT, newValue)); + EasyBind.listen(gitPreferences.getAutoSyncProperty(), (obs, oldValue, newValue) -> putBoolean(GIT_AUTOSYNC, newValue)); return gitPreferences; } diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index 33f2063543b..ea5104c8c2d 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -16,7 +16,7 @@ import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; import org.jabref.logic.exporter.SelfContainedSaveConfiguration; -import org.jabref.gui.git.GitPreferences; +import org.jabref.logic.git.GitPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.fetcher.GrobidPreferences; diff --git a/src/test/java/org/jabref/gui/git/GitCredentialsTest.java b/src/test/java/org/jabref/gui/git/GitCredentialsTest.java deleted file mode 100644 index b3a6a8cbf09..00000000000 --- a/src/test/java/org/jabref/gui/git/GitCredentialsTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.jabref.gui.git; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -public class GitCredentialsTest { - @Test - void GitCredentials() { - GitCredentials gitCredentials = new GitCredentials(); - String gitPassword = gitCredentials.getGitPassword(); - String gitUsername = gitCredentials.getGitUsername(); - - assertNull(gitUsername); - assertNull(gitPassword); - } - - @Test - void GitCredentialsWithUsernameAndPassword() { - GitCredentials gitCredentials = new GitCredentials("testUsername", "testPassword"); - String gitPassword = gitCredentials.getGitPassword(); - String gitUsername = gitCredentials.getGitUsername(); - - assertEquals("testUsername", gitUsername); - assertEquals("testPassword", gitPassword); - } - - @Test - void GitCredentialsGettersAndSetters() { - GitCredentials gitCredentials = new GitCredentials(); - - gitCredentials.setGitPassword("testPassword"); - gitCredentials.setGitUsername("testUsername"); - - String gitPassword = gitCredentials.getGitPassword(); - String gitUsername = gitCredentials.getGitUsername(); - - assertEquals("testUsername", gitUsername); - assertEquals("testPassword", gitPassword); - } -} diff --git a/src/test/java/org/jabref/gui/git/GitPreferencesTest.java b/src/test/java/org/jabref/gui/git/GitPreferencesTest.java deleted file mode 100644 index fae3e8bff35..00000000000 --- a/src/test/java/org/jabref/gui/git/GitPreferencesTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.jabref.gui.git; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; - -public class GitPreferencesTest { - @Test - void GitCredentials() { - String testUsername = "testUsername"; - String testPassword = "testPassword"; - GitPreferences gitPreferences = new GitPreferences(testUsername, testPassword); - - assertEquals(testUsername, gitPreferences.getUsername()); - assertEquals(testPassword, gitPreferences.getPassword()); - assertEquals(testUsername, gitPreferences.getUsernameProperty().get()); - assertEquals(testPassword, gitPreferences.getPasswordProperty().get()); - } - - @Test - void GitCredentialsWithGitProperty() { - String testUsername = "testUsername"; - String testPassword = "testPassword"; - StringProperty gitPasswordProperty = new SimpleStringProperty(testPassword); - StringProperty gitUsernameProperty = new SimpleStringProperty(testUsername); - GitPreferences gitPreferences = new GitPreferences(gitUsernameProperty, gitPasswordProperty); - - assertEquals(testUsername, gitPreferences.getUsername()); - assertEquals(testPassword, gitPreferences.getPassword()); - assertEquals(testUsername, gitPreferences.getUsernameProperty().get()); - assertEquals(testPassword, gitPreferences.getPasswordProperty().get()); - } - - @Test - void GitCredentialsWithGitPropertyGetterAndSetters() { - String testUsername = "testUsername"; - String testPassword = "testPassword"; - String testString = "updated"; - StringProperty gitPasswordProperty = new SimpleStringProperty(testPassword); - StringProperty gitUsernameProperty = new SimpleStringProperty(testUsername); - GitPreferences gitPreferences = new GitPreferences(gitUsernameProperty, gitPasswordProperty); - gitPreferences.setUsername(testUsername + testString); - gitPreferences.setPassword(testPassword + testString); - - assertEquals(testUsername + testString, gitPreferences.getUsername()); - assertEquals(testPassword + testString, gitPreferences.getPassword()); - assertEquals(testUsername + testString, gitPreferences.getUsernameProperty().get()); - assertEquals(testPassword + testString, gitPreferences.getPasswordProperty().get()); - } - - @Test - void GitCredentialsWithGitGetterAndSetters() { - String testUsername = "testUsername"; - String testPassword = "testPassword"; - String testString = "updated"; - GitPreferences gitPreferences = new GitPreferences(testUsername, testPassword); - gitPreferences.setUsername(testUsername + testString); - gitPreferences.setPassword(testPassword + testString); - - assertEquals(testUsername + testString, gitPreferences.getUsername()); - assertEquals(testPassword + testString, gitPreferences.getPassword()); - assertEquals(testUsername + testString, gitPreferences.getUsernameProperty().get()); - assertEquals(testPassword + testString, gitPreferences.getPasswordProperty().get()); - } -} diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index bfb84230230..c050edd2379 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -28,7 +28,7 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; -import org.jabref.gui.git.GitPreferences; +import org.jabref.logic.git.GitPreferences; import org.jabref.preferences.PreferencesService; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -74,11 +74,10 @@ private static Path createFolder() throws IOException { @BeforeEach public void setUpGitHandler() { - gitPreferences = new GitPreferences("testUser", "testPassword"); + gitPreferences = new GitPreferences("testUser", "testPassword", true, true); PreferencesService preferences = mock(PreferencesService.class); when(preferences.getGitPreferences()).thenReturn(gitPreferences); - gitHandler = new GitHandler(repositoryPath); - gitHandler.setGitPreferences(preferences.getGitPreferences()); + gitHandler = new GitHandler(repositoryPath, preferences.getGitPreferences()); } @Test @@ -155,7 +154,7 @@ void pushSingleFile () throws Exception { assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_1.bib", "PushSingleFile")); //Push - gitPreferences = new GitPreferences(username, password); + gitPreferences = new GitPreferences(username, password, true, true); PreferencesService preferences = mock(PreferencesService.class); when(preferences.getGitPreferences()).thenReturn(gitPreferences); git.setGitPreferences(preferences.getGitPreferences()); From dff72dd7e7f4882af87e99f7836ea70f78ea864b Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Sat, 18 Nov 2023 17:26:45 -0300 Subject: [PATCH 36/48] init developing resolve conflicts --- .../gui/exporter/SaveDatabaseAction.java | 23 +- .../git/BibGitContext.java} | 31 +- .../java/org/jabref/gui/git/GitChange.java | 112 +-- ...ailView.java => GitChangeDetailsView.java} | 2 +- .../org/jabref/gui/git/GitChangeResolver.java | 16 - .../gui/git/GitChangeResolverFactory.java | 29 - .../jabref/gui/git/GitChangesResolver.java | 16 - .../gui/git/GitChangesResolverDialog.java | 270 ++++---- .../org/jabref/gui/git/entryadd/EntryAdd.java | 31 - .../gui/git/entrychange/EntryChange.java | 49 -- .../entrychange/EntryChangeDetailsView.java | 71 -- .../git/entrychange/EntryChangeResolver.java | 48 -- .../EntryWithPreviewAndSourceDetailsView.java | 25 - .../git/entrychange/PreviewWithSourceTab.java | 73 -- .../gui/git/entrydelete/EntryDelete.java | 31 - .../gui/git/groupchange/GroupChange.java | 56 -- .../groupchange/GroupChangeDetailsView.java | 25 - .../git/metedatachange/MetadataChange.java | 32 - .../MetadataChangeDetailsView.java | 65 -- .../git/preamblechange/PreambleChange.java | 35 - .../PreambleChangeDetailsView.java | 37 - .../gui/git/stringadd/BibTexStringAdd.java | 39 -- .../stringadd/BibTexStringAddDetailsView.java | 27 - .../git/stringchange/BibTexStringChange.java | 43 -- .../BibTexStringChangeDetailsView.java | 29 - .../git/stringdelete/BibTexStringDelete.java | 38 -- .../BibTexStringDeleteDetailsView.java | 27 - .../git/stringrename/BibTexStringRename.java | 48 -- .../BibTexStringRenameDetailsView.java | 18 - .../java/org/jabref/logic/git/GitHandler.java | 3 + src/main/java/org/jabref/model/git/Git.java | 641 ------------------ 31 files changed, 228 insertions(+), 1762 deletions(-) rename src/main/java/org/jabref/{model/git/GitContext.java => gui/git/BibGitContext.java} (91%) rename src/main/java/org/jabref/gui/git/{GitChangesDetailView.java => GitChangeDetailsView.java} (50%) delete mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolver.java delete mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java delete mode 100644 src/main/java/org/jabref/gui/git/GitChangesResolver.java delete mode 100644 src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChange.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java delete mode 100644 src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java delete mode 100644 src/main/java/org/jabref/gui/git/groupchange/GroupChange.java delete mode 100644 src/main/java/org/jabref/gui/git/groupchange/GroupChangeDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/metedatachange/MetadataChange.java delete mode 100644 src/main/java/org/jabref/gui/git/metedatachange/MetadataChangeDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/preamblechange/PreambleChange.java delete mode 100644 src/main/java/org/jabref/gui/git/preamblechange/PreambleChangeDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/stringadd/BibTexStringAdd.java delete mode 100644 src/main/java/org/jabref/gui/git/stringadd/BibTexStringAddDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/stringchange/BibTexStringChange.java delete mode 100644 src/main/java/org/jabref/gui/git/stringchange/BibTexStringChangeDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDelete.java delete mode 100644 src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDeleteDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/stringrename/BibTexStringRename.java delete mode 100644 src/main/java/org/jabref/gui/git/stringrename/BibTexStringRenameDetailsView.java delete mode 100644 src/main/java/org/jabref/model/git/Git.java diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 076ddce8d54..d6910622750 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -26,6 +26,7 @@ import org.jabref.gui.autosaveandbackup.AutosaveManager; import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.git.GitCredentialsDialogView; +import org.jabref.gui.git.GitChangesResolverDialog; import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.gui.util.BackgroundTask; @@ -337,8 +338,8 @@ public void automaticGitUpdate(Path filePath) throws IOException, GitAPIExceptio if (preferences.getGitPreferences().getAutoSync()) { try { git.pullOnCurrentBranch(); - } catch (IOException ex) { - if (ex.getMessage().equals("No git credentials")) { + } catch (IOException ex1) { + if (ex1.getMessage().equals("No git credentials")) { GitCredentialsDialogView gitCredentialsDialogView = new GitCredentialsDialogView(); gitCredentialsDialogView.showGitCredentialsDialog(); @@ -349,9 +350,21 @@ public void automaticGitUpdate(Path filePath) throws IOException, GitAPIExceptio ); git.setCredentialsProvider(credentialsProvider); - git.pullOnCurrentBranch(); - } else { - throw new IOException(ex.getMessage()); + try { + git.pullOnCurrentBranch(); + } catch (IOException ex2) { + if (ex2.getMessage().equals("HEAD is detached")) { + GitChangesResolverDialog GitChangesResolverDialog = new GitChangesResolverDialog(Path filePath); + } else { + throw new RuntimeException(ex2.getMessage()); + } + } + + } else if (ex1.getMessage().equals("HEAD is detached")) { + GitChangesResolverDialog GitChangesResolverDialog = new GitChangesResolverDialog(); + } + else { + throw new IOException(ex1.getMessage()); } } git.pushCommitsToRemoteRepository(); diff --git a/src/main/java/org/jabref/model/git/GitContext.java b/src/main/java/org/jabref/gui/git/BibGitContext.java similarity index 91% rename from src/main/java/org/jabref/model/git/GitContext.java rename to src/main/java/org/jabref/gui/git/BibGitContext.java index 3163f15a2b2..ad64a04b05b 100644 --- a/src/main/java/org/jabref/model/git/GitContext.java +++ b/src/main/java/org/jabref/gui/git/BibGitContext.java @@ -1,4 +1,4 @@ -package org.jabref.model.git; +package org.jabref.model.database; import java.nio.file.Files; import java.nio.file.Path; @@ -27,7 +27,7 @@ /** * Represents everything related to a BIB file. * - *

The entries are stored in Git, the other data in MetaData + *

The entries are stored in BibDatabase, the other data in MetaData * and the options relevant for this file in Defaults. *

*

@@ -35,11 +35,10 @@ *

*/ @AllowedToUseLogic("because it needs access to shared database features") -public class GitContext { +public class BibGitContext { private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); - private final Git database; private MetaData metaData; /** @@ -51,25 +50,25 @@ public class GitContext { private CoarseChangeFilter dbmsListener; private DatabaseLocation location; - public GitContext() { - this(new Git()); + public BibGitContext() { + this(new BibDatabase()); } - public GitContext(Git database) { + public BibGitContext(BibDatabase database) { this(database, new MetaData()); } - public GitContext(Git database, MetaData metaData) { + public BibGitContext(BibDatabase database, MetaData metaData) { this.database = Objects.requireNonNull(database); this.metaData = Objects.requireNonNull(metaData); this.location = DatabaseLocation.LOCAL; } - public GitContext(Git database, MetaData metaData, Path path) { + public BibGitContext(BibDatabase database, MetaData metaData, Path path) { this(database, metaData, path, DatabaseLocation.LOCAL); } - public GitContext(Git database, MetaData metaData, Path path, DatabaseLocation location) { + public BibGitContext(BibDatabase database, MetaData metaData, Path path, DatabaseLocation location) { this(database, metaData); Objects.requireNonNull(location); this.path = path; @@ -79,11 +78,11 @@ public GitContext(Git database, MetaData metaData, Path path, DatabaseLocation l } } - public GitMode getMode() { - return metaData.getMode().orElse(GitMode.BIBLATEX); + public BibDatabaseMode getMode() { + return metaData.getMode().orElse(BibDatabaseMode.BIBLATEX); } - public void setMode(GitMode bibDatabaseMode) { + public void setMode(BibDatabaseMode bibDatabaseMode) { metaData.setMode(bibDatabaseMode); } @@ -104,7 +103,7 @@ public void clearDatabasePath() { this.path = null; } - public Git getDatabase() { + public BibDatabase getDatabase() { return database; } @@ -117,7 +116,7 @@ public void setMetaData(MetaData metaData) { } public boolean isBiblatexMode() { - return getMode() == GitMode.BIBLATEX; + return getMode() == BibDatabaseMode.BIBLATEX; } /** @@ -254,7 +253,7 @@ public Path getFulltextIndexPath() { @Override public String toString() { - return "GitContext{" + + return "BibGitContext{" + "metaData=" + metaData + ", mode=" + getMode() + ", databasePath=" + getDatabasePath() + diff --git a/src/main/java/org/jabref/gui/git/GitChange.java b/src/main/java/org/jabref/gui/git/GitChange.java index 3923b5ab173..17d3d27e59b 100644 --- a/src/main/java/org/jabref/gui/git/GitChange.java +++ b/src/main/java/org/jabref/gui/git/GitChange.java @@ -1,71 +1,71 @@ package org.jabref.gui.git; -import java.util.Optional; + 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 javafx.beans.property.BooleanProperty; + import javafx.beans.property.SimpleBooleanProperty; + import javafx.beans.property.SimpleStringProperty; + import javafx.beans.property.StringProperty; -import org.jabref.gui.git.entryadd.EntryAdd; -import org.jabref.gui.git.entrychange.EntryChange; -import org.jabref.gui.git.entrydelete.EntryDelete; -import org.jabref.gui.git.groupchange.GroupChange; -import org.jabref.gui.git.metedatachange.MetadataChange; -import org.jabref.gui.git.preamblechange.PreambleChange; -import org.jabref.gui.git.stringadd.BibTexStringAdd; -import org.jabref.gui.git.stringchange.BibTexStringChange; -import org.jabref.gui.git.stringdelete.BibTexStringDelete; -import org.jabref.gui.git.stringrename.BibTexStringRename; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.util.OptionalObjectProperty; -import org.jabref.model.git.GitContext; + import org.jabref.gui.git.entryadd.EntryAdd; + import org.jabref.gui.git.entrychange.EntryChange; + import org.jabref.gui.git.entrydelete.EntryDelete; + import org.jabref.gui.git.groupchange.GroupChange; + import org.jabref.gui.git.metedatachange.MetadataChange; + import org.jabref.gui.git.preamblechange.PreambleChange; + import org.jabref.gui.git.stringadd.BibTexStringAdd; + import org.jabref.gui.git.stringchange.BibTexStringChange; + import org.jabref.gui.git.stringdelete.BibTexStringDelete; + import org.jabref.gui.git.stringrename.BibTexStringRename; + import org.jabref.gui.undo.NamedCompound; + import org.jabref.gui.util.OptionalObjectProperty; + import org.jabref.model.git.GitContext; -public sealed abstract class GitChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename { - protected final GitContext gitContext; - protected final OptionalObjectProperty externalChangeResolver = OptionalObjectProperty.empty(); - private final BooleanProperty accepted = new SimpleBooleanProperty(); - private final StringProperty name = new SimpleStringProperty(); + public sealed abstract class GitChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename { + protected final GitContext gitContext; + protected final OptionalObjectProperty externalChangeResolver = OptionalObjectProperty.empty(); + private final BooleanProperty accepted = new SimpleBooleanProperty(); + private final StringProperty name = new SimpleStringProperty(); - protected GitChange(GitContext gitContext, GitChangeResolverFactory gitChangeResolverFactory) { - this.gitContext = gitContext; - setChangeName("Unnamed Change!"); + protected GitChange(GitContext gitContext, GitChangeResolverFactory gitChangeResolverFactory) { + this.gitContext = gitContext; + setChangeName("Unnamed Change!"); - if (gitChangeResolverFactory != null) { - externalChangeResolver.set(gitChangeResolverFactory.create(this)); - } - } + if (gitChangeResolverFactory != null) { + externalChangeResolver.set(gitChangeResolverFactory.create(this)); + } + } - public boolean isAccepted() { - return accepted.get(); - } + public boolean isAccepted() { + return accepted.get(); + } - public BooleanProperty acceptedProperty() { - return accepted; - } + public BooleanProperty acceptedProperty() { + return accepted; + } - public void setAccepted(boolean accepted) { - this.accepted.set(accepted); - } + public void setAccepted(boolean accepted) { + this.accepted.set(accepted); + } - /** - * Convenience method for accepting changes - * */ - public void accept() { - setAccepted(true); - } + /** + * Convenience method for accepting changes + * */ + public void accept() { + setAccepted(true); + } - public String getName() { - return name.get(); - } + public String getName() { + return name.get(); + } - protected void setChangeName(String changeName) { - name.set(changeName); - } + protected void setChangeName(String changeName) { + name.set(changeName); + } - public Optional getExternalChangeResolver() { - return externalChangeResolver.get(); - } + public Optional getExternalChangeResolver() { + return externalChangeResolver.get(); + } - public abstract void applyChange(NamedCompound undoEdit); -} + public abstract void applyChange(NamedCompound undoEdit); + } \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/git/GitChangesDetailView.java b/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java similarity index 50% rename from src/main/java/org/jabref/gui/git/GitChangesDetailView.java rename to src/main/java/org/jabref/gui/git/GitChangeDetailsView.java index 5127b12b19e..67763c4c971 100644 --- a/src/main/java/org/jabref/gui/git/GitChangesDetailView.java +++ b/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java @@ -1,5 +1,5 @@ package org.jabref.gui.git; -public class GitChangesDetailView { +public class GitChangeDetailsView { } diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolver.java b/src/main/java/org/jabref/gui/git/GitChangeResolver.java deleted file mode 100644 index 260922ad689..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChangeResolver.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jabref.gui.git; - -import java.util.Optional; - -import org.jabref.gui.DialogService; -import org.jabref.gui.collab.entrychange.EntryChangeResolver; - -public sealed abstract class GitChangeResolver permits EntryChangeResolver { - protected final DialogService dialogService; - - protected GitChangeResolver(DialogService dialogService) { - this.dialogService = dialogService; - } - - public abstract Optional askUserToResolveChange(); -} diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java b/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java deleted file mode 100644 index 9fc4462c68e..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.jabref.gui.git; - -import java.util.Optional; - -import org.jabref.gui.DialogService; -import org.jabref.gui.collab.entrychange.EntryChange; -import org.jabref.gui.collab.entrychange.EntryChangeResolver; -import org.jabref.model.git.GitContext; -import org.jabref.preferences.PreferencesService; - -public class GitChangeResolverFactory { - private final DialogService dialogService; - private final GitContext gitContext; - private final PreferencesService preferencesService; - - public GitChangeResolverFactory(DialogService dialogService, GitContext gitContext, PreferencesService preferencesService) { - this.dialogService = dialogService; - this.gitContext = gitContext; - this.preferencesService = preferencesService; - } - - public Optional create(GitChange change) { - if (change instanceof EntryChange entryChange) { - return Optional.of(new EntryChangeResolver(entryChange, dialogService, gitContext, preferencesService)); - } - - return Optional.empty(); - } -} diff --git a/src/main/java/org/jabref/gui/git/GitChangesResolver.java b/src/main/java/org/jabref/gui/git/GitChangesResolver.java deleted file mode 100644 index 4ea568ee310..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChangesResolver.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jabref.gui.git; - -import java.util.Optional; - -import org.jabref.gui.DialogService; -import org.jabref.gui.collab.entrychange.EntryChangeResolver; - -public sealed abstract class GitChangesResolver permits EntryChangeResolver { - protected final DialogService dialogService; - - protected GItChangeResolver(DialogService dialogService) { - this.dialogService = dialogService; - } - - public abstract Optional askUserToResolveChange(); -} diff --git a/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java b/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java index a9c1a9e53a2..17507b183b3 100644 --- a/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java +++ b/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java @@ -1,137 +1,137 @@ package org.jabref.gui.git; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.swing.undo.UndoManager; - -import javafx.beans.property.SimpleStringProperty; -import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.layout.BorderPane; - -import org.jabref.gui.DialogService; -import org.jabref.gui.StateManager; -import org.jabref.gui.collab.ExternalChangesResolverViewModel; -import org.jabref.gui.preview.PreviewViewer; -import org.jabref.gui.theme.ThemeManager; -import org.jabref.gui.util.BaseDialog; -import org.jabref.gui.util.TaskExecutor; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.preferences.PreferencesService; - -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class GitChangesResolverDialog extends BaseDialog { - private final static Logger LOGGER = LoggerFactory.getLogger(GitChangesResolverDialog.class); - /** - * Reconstructing the details view to preview an {@link GitChange} every time it's selected is a heavy operation. - * It is also useless because changes are static and if the change data is static then the view doesn't have to change - * either. This cache is used to ensure that we only create the detail view instance once for each {@link GitChange}. - */ - private final Map DETAILS_VIEW_CACHE = new HashMap<>(); - - @FXML - private TableView changesTableView; - @FXML - private TableColumn changeName; - @FXML - private Button askUserToResolveChangeButton; - @FXML - private BorderPane changeInfoPane; - - private final List changes; - private final BibGitContext git; - - private ExternalChangesResolverViewModel viewModel; - - @Inject private UndoManager undoManager; - @Inject private StateManager stateManager; - @Inject private DialogService dialogService; - @Inject private PreferencesService preferencesService; - @Inject private ThemeManager themeManager; - @Inject private BibEntryTypesManager entryTypesManager; - @Inject private TaskExecutor taskExecutor; - - /** - * A dialog going through given changes, which are diffs to the provided git. - * Each accepted change is written to the provided git. - * - * @param changes The list of changes - * @param git The git to apply the changes to - */ - public GitChangesResolverDialog(List changes, BibGitContext git, String dialogTitle) { - this.changes = changes; - this.git = git; - - this.setTitle(dialogTitle); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); - - this.setResultConverter(button -> { - if (viewModel.areAllChangesResolved()) { - LOGGER.info("External changes are resolved successfully"); - return true; - } else { - LOGGER.info("External changes aren't resolved"); - return false; - } - }); - } - - @FXML - private void initialize() { - PreviewViewer previewViewer = new PreviewViewer(git, dialogService, preferencesService, stateManager, themeManager, taskExecutor); - GitChangeDetailsViewFactory gitChangeDetailsViewFactory = new GitChangeDetailsViewFactory(git, dialogService, stateManager, themeManager, preferencesService, entryTypesManager, previewViewer, taskExecutor); - - viewModel = new ExternalChangesResolverViewModel(changes, undoManager); - - changeName.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getName())); - askUserToResolveChangeButton.disableProperty().bind(viewModel.canAskUserToResolveChangeProperty().not()); - - changesTableView.setItems(viewModel.getVisibleChanges()); - // Think twice before setting this to MULTIPLE... - changesTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); - changesTableView.getSelectionModel().selectFirst(); - - viewModel.selectedChangeProperty().bind(changesTableView.getSelectionModel().selectedItemProperty()); - EasyBind.subscribe(viewModel.selectedChangeProperty(), selectedChange -> { - if (selectedChange != null) { - GitChangeDetailsView detailsView = DETAILS_VIEW_CACHE.computeIfAbsent(selectedChange, gitChangeDetailsViewFactory::create); - changeInfoPane.setCenter(detailsView); - } - }); - - EasyBind.subscribe(viewModel.areAllChangesResolvedProperty(), isResolved -> { - if (isResolved) { - viewModel.applyChanges(); - close(); - } - }); - } - - @FXML - public void denyChanges() { - viewModel.denyChange(); - } - - @FXML - public void acceptChanges() { - viewModel.acceptChange(); - } - - @FXML - public void askUserToResolveChange() { - viewModel.getSelectedChange().flatMap(GitChange::getExternalChangeResolver) - .flatMap(GitChangesResolver::askUserToResolveChange).ifPresent(viewModel::acceptMergedChange); - } -} + import java.util.HashMap; + import java.util.List; + import java.util.Map; + + import javax.swing.undo.UndoManager; + + import javafx.beans.property.SimpleStringProperty; + import javafx.fxml.FXML; + import javafx.scene.control.Button; + import javafx.scene.control.SelectionMode; + import javafx.scene.control.TableColumn; + import javafx.scene.control.TableView; + import javafx.scene.layout.BorderPane; + + import org.jabref.gui.DialogService; + import org.jabref.gui.StateManager; + import org.jabref.gui.collab.ExternalChangesResolverViewModel; + import org.jabref.gui.preview.PreviewViewer; + import org.jabref.gui.theme.ThemeManager; + import org.jabref.gui.util.BaseDialog; + import org.jabref.gui.util.TaskExecutor; + import org.jabref.model.entry.BibEntryTypesManager; + import org.jabref.preferences.PreferencesService; + + import com.airhacks.afterburner.views.ViewLoader; + import com.tobiasdiez.easybind.EasyBind; + import jakarta.inject.Inject; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + public class GitChangesResolverDialog extends BaseDialog { + private final static Logger LOGGER = LoggerFactory.getLogger(GitChangesResolverDialog.class); + /** + * Reconstructing the details view to preview an {@link GitChange} every time it's selected is a heavy operation. + * It is also useless because changes are static and if the change data is static then the view doesn't have to change + * either. This cache is used to ensure that we only create the detail view instance once for each {@link GitChange}. + */ + private final Map DETAILS_VIEW_CACHE = new HashMap<>(); + + @FXML + private TableView changesTableView; + @FXML + private TableColumn changeName; + @FXML + private Button askUserToResolveChangeButton; + @FXML + private BorderPane changeInfoPane; + + private final List changes; + private final BibGitContext git; + + private ExternalChangesResolverViewModel viewModel; + + @Inject private UndoManager undoManager; + @Inject private StateManager stateManager; + @Inject private DialogService dialogService; + @Inject private PreferencesService preferencesService; + @Inject private ThemeManager themeManager; + @Inject private BibEntryTypesManager entryTypesManager; + @Inject private TaskExecutor taskExecutor; + + /** + * A dialog going through given changes, which are diffs to the provided git. + * Each accepted change is written to the provided git. + * + * @param changes The list of changes + * @param git The git to apply the changes to + */ + public GitChangesResolverDialog(List changes, BibGitContext git, String dialogTitle) { + this.changes = changes; + this.git = git; + + this.setTitle(dialogTitle); + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + this.setResultConverter(button -> { + if (viewModel.areAllChangesResolved()) { + LOGGER.info("External changes are resolved successfully"); + return true; + } else { + LOGGER.info("External changes aren't resolved"); + return false; + } + }); + } + + @FXML + private void initialize() { + PreviewViewer previewViewer = new PreviewViewer(git, dialogService, preferencesService, stateManager, themeManager, taskExecutor); + GitChangeDetailsViewFactory gitChangeDetailsViewFactory = new GitChangeDetailsViewFactory(git, dialogService, stateManager, themeManager, preferencesService, entryTypesManager, previewViewer, taskExecutor); + + viewModel = new ExternalChangesResolverViewModel(changes, undoManager); + + changeName.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getName())); + askUserToResolveChangeButton.disableProperty().bind(viewModel.canAskUserToResolveChangeProperty().not()); + + changesTableView.setItems(viewModel.getVisibleChanges()); + // Think twice before setting this to MULTIPLE... + changesTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + changesTableView.getSelectionModel().selectFirst(); + + viewModel.selectedChangeProperty().bind(changesTableView.getSelectionModel().selectedItemProperty()); + EasyBind.subscribe(viewModel.selectedChangeProperty(), selectedChange -> { + if (selectedChange != null) { + GitChangeDetailsView detailsView = DETAILS_VIEW_CACHE.computeIfAbsent(selectedChange, gitChangeDetailsViewFactory::create); + changeInfoPane.setCenter(detailsView); + } + }); + + EasyBind.subscribe(viewModel.areAllChangesResolvedProperty(), isResolved -> { + if (isResolved) { + viewModel.applyChanges(); + close(); + } + }); + } + + @FXML + public void denyChanges() { + viewModel.denyChange(); + } + + @FXML + public void acceptChanges() { + viewModel.acceptChange(); + } + + @FXML + public void askUserToResolveChange() { + viewModel.getSelectedChange().flatMap(GitChange::getExternalChangeResolver) + .flatMap(GitChangesResolver::askUserToResolveChange).ifPresent(viewModel::acceptMergedChange); + } + } \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java b/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java deleted file mode 100644 index 0c4e01969fc..00000000000 --- a/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.gui.git.entryadd; - -import org.jabref.gui.git.GitChange; -import org.jabref.gui.git.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableInsertEntries; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.entry.BibEntry; - -public final class EntryAdd extends GitChange { - private final BibEntry addedEntry; - - public EntryAdd(BibEntry addedEntry, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.addedEntry = addedEntry; - setChangeName(addedEntry.getCitationKey() - .map(key -> Localization.lang("Added entry '%0'", key)) - .orElse(Localization.lang("Added entry"))); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - databaseContext.getGit().insertEntry(addedEntry); - undoEdit.addEdit(new UndoableInsertEntries(databaseContext.getGit(), addedEntry)); - } - - public BibEntry getAddedEntry() { - return addedEntry; - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java deleted file mode 100644 index 7753bb422cc..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.jabref.gui.collab.entrychange; - -import javax.swing.undo.CompoundEdit; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableInsertEntries; -import org.jabref.gui.undo.UndoableRemoveEntries; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.entry.BibEntry; - -public final class EntryChange extends GitChange { - private final BibEntry oldEntry; - private final BibEntry newEntry; - - public EntryChange(BibEntry oldEntry, BibEntry newEntry, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.oldEntry = oldEntry; - this.newEntry = newEntry; - setChangeName(oldEntry.getCitationKey().map(key -> Localization.lang("Modified entry '%0'", key)) - .orElse(Localization.lang("Modified entry"))); - } - - public EntryChange(BibEntry oldEntry, BibEntry newEntry, GitContext databaseContext) { - this(oldEntry, newEntry, databaseContext, null); - } - - public BibEntry getOldEntry() { - return oldEntry; - } - - public BibEntry getNewEntry() { - return newEntry; - } - - @Override - public void applyChange(NamedCompound undoEdit) { - databaseContext.getGit().removeEntry(oldEntry); - databaseContext.getGit().insertEntry(newEntry); - CompoundEdit changeEntryEdit = new CompoundEdit(); - changeEntryEdit.addEdit(new UndoableRemoveEntries(databaseContext.getGit(), oldEntry)); - changeEntryEdit.addEdit(new UndoableInsertEntries(databaseContext.getGit(), newEntry)); - changeEntryEdit.end(); - - undoEdit.addEdit(changeEntryEdit); - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java deleted file mode 100644 index 53a4ce25c87..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.jabref.gui.collab.entrychange; - -import javafx.geometry.Orientation; -import javafx.scene.control.Label; -import javafx.scene.control.SplitPane; -import javafx.scene.control.TabPane; -import javafx.scene.layout.VBox; - -import org.jabref.gui.DialogService; -import org.jabref.gui.StateManager; -import org.jabref.gui.collab.GitChangeDetailsView; -import org.jabref.gui.preview.PreviewViewer; -import org.jabref.gui.theme.ThemeManager; -import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.preferences.PreferencesService; - -import com.tobiasdiez.easybind.EasyBind; - -public final class EntryChangeDetailsView extends GitChangeDetailsView { - private final PreviewWithSourceTab oldPreviewWithSourcesTab = new PreviewWithSourceTab(); - private final PreviewWithSourceTab newPreviewWithSourcesTab = new PreviewWithSourceTab(); - - public EntryChangeDetailsView(BibEntry oldEntry, - BibEntry newEntry, - GitContext databaseContext, - DialogService dialogService, - StateManager stateManager, - ThemeManager themeManager, - PreferencesService preferencesService, - BibEntryTypesManager entryTypesManager, - PreviewViewer previewViewer, - TaskExecutor taskExecutor) { - Label inJabRef = new Label(Localization.lang("In JabRef")); - inJabRef.getStyleClass().add("lib-change-header"); - Label onDisk = new Label(Localization.lang("On disk")); - onDisk.getStyleClass().add("lib-change-header"); - - // we need a copy here as we otherwise would set the same entry twice - PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, preferencesService, stateManager, themeManager, taskExecutor); - - TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, preferencesService, entryTypesManager, previewClone, Localization.lang("Entry Preview")); - TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, preferencesService, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); - - EasyBind.subscribe(oldEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { - newEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); - }); - - EasyBind.subscribe(newEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { - if (oldEntryTabPane.getSelectionModel().getSelectedIndex() != selectedIndex.intValue()) { - oldEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); - } - }); - - VBox containerOld = new VBox(inJabRef, oldEntryTabPane); - VBox containerNew = new VBox(onDisk, newEntryTabPane); - - SplitPane split = new SplitPane(containerOld, containerNew); - split.setOrientation(Orientation.HORIZONTAL); - - setLeftAnchor(split, 8d); - setTopAnchor(split, 8d); - setRightAnchor(split, 8d); - setBottomAnchor(split, 8d); - - this.getChildren().add(split); - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java deleted file mode 100644 index 049138bb5c9..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.jabref.gui.collab.entrychange; - -import java.util.Optional; - -import org.jabref.gui.DialogService; -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolver; -import org.jabref.gui.mergeentries.EntriesMergeResult; -import org.jabref.gui.mergeentries.MergeEntriesDialog; -import org.jabref.gui.mergeentries.newmergedialog.ShowDiffConfig; -import org.jabref.gui.mergeentries.newmergedialog.diffhighlighter.DiffHighlighter.BasicDiffMethod; -import org.jabref.gui.mergeentries.newmergedialog.toolbar.ThreeWayMergeToolbar; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.preferences.PreferencesService; - -public final class EntryChangeResolver extends GitChangeResolver { - private final EntryChange entryChange; - private final GitContext databaseContext; - - private final PreferencesService preferencesService; - - public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, GitContext databaseContext, PreferencesService preferencesService) { - super(dialogService); - this.entryChange = entryChange; - this.databaseContext = databaseContext; - this.preferencesService = preferencesService; - } - - @Override - public Optional askUserToResolveChange() { - MergeEntriesDialog mergeEntriesDialog = new MergeEntriesDialog(entryChange.getOldEntry(), entryChange.getNewEntry(), preferencesService); - mergeEntriesDialog.setLeftHeaderText(Localization.lang("In JabRef")); - mergeEntriesDialog.setRightHeaderText(Localization.lang("On disk")); - mergeEntriesDialog.configureDiff(new ShowDiffConfig(ThreeWayMergeToolbar.DiffView.SPLIT, BasicDiffMethod.WORDS)); - - return dialogService.showCustomDialogAndWait(mergeEntriesDialog) - .map(this::mapMergeResultToExternalChange); - } - - private EntryChange mapMergeResultToExternalChange(EntriesMergeResult entriesMergeResult) { - return new EntryChange( - entryChange.getOldEntry(), - entriesMergeResult.mergedEntry(), - databaseContext - ); - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java deleted file mode 100644 index 2a505871b7e..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.jabref.gui.collab.entrychange; - -import javafx.scene.control.TabPane; - -import org.jabref.gui.collab.GitChangeDetailsView; -import org.jabref.gui.preview.PreviewViewer; -import org.jabref.model.database.GitContext; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.preferences.PreferencesService; - -public final class EntryWithPreviewAndSourceDetailsView extends GitChangeDetailsView { - - private final PreviewWithSourceTab previewWithSourceTab = new PreviewWithSourceTab(); - - public EntryWithPreviewAndSourceDetailsView(BibEntry entry, GitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { - TabPane tabPanePreviewCode = previewWithSourceTab.getPreviewWithSourceTab(entry, bibGitContext, preferencesService, entryTypesManager, previewViewer); - setLeftAnchor(tabPanePreviewCode, 8d); - setTopAnchor(tabPanePreviewCode, 8d); - setRightAnchor(tabPanePreviewCode, 8d); - setBottomAnchor(tabPanePreviewCode, 8d); - - getChildren().setAll(tabPanePreviewCode); - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java b/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java deleted file mode 100644 index c4bda2c4456..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.jabref.gui.collab.entrychange; - -import java.io.IOException; -import java.io.StringWriter; - -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; - -import org.jabref.gui.preview.PreviewViewer; -import org.jabref.logic.bibtex.BibEntryWriter; -import org.jabref.logic.bibtex.FieldPreferences; -import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.logic.exporter.BibWriter; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.OS; -import org.jabref.model.database.GitContext; -import org.jabref.model.database.GitMode; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.strings.StringUtil; -import org.jabref.preferences.PreferencesService; - -import org.fxmisc.richtext.CodeArea; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PreviewWithSourceTab { - - private static final Logger LOGGER = LoggerFactory.getLogger(PreviewWithSourceTab.class); - - public TabPane getPreviewWithSourceTab(BibEntry entry, GitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { - return getPreviewWithSourceTab(entry, bibGitContext, preferencesService, entryTypesManager, previewViewer, ""); - } - - public TabPane getPreviewWithSourceTab(BibEntry entry, GitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, String label) { - previewViewer.setLayout(preferencesService.getPreviewPreferences().getSelectedPreviewLayout()); - previewViewer.setEntry(entry); - - CodeArea codeArea = new CodeArea(); - codeArea.setId("bibtexcodearea"); - codeArea.setWrapText(true); - codeArea.setDisable(true); - - TabPane tabPanePreviewCode = new TabPane(); - Tab previewTab = new Tab(); - previewTab.setContent(previewViewer); - - if (StringUtil.isNullOrEmpty(label)) { - previewTab.setText(Localization.lang("Entry preview")); - } else { - previewTab.setText(label); - } - - try { - codeArea.appendText(getSourceString(entry, bibGitContext.getMode(), preferencesService.getFieldPreferences(), entryTypesManager)); - } catch (IOException e) { - LOGGER.error("Error getting Bibtex: {}", entry); - } - codeArea.setEditable(false); - Tab codeTab = new Tab(Localization.lang("%0 source", bibGitContext.getMode().getFormattedName()), codeArea); - - tabPanePreviewCode.getTabs().addAll(previewTab, codeTab); - return tabPanePreviewCode; - } - - private String getSourceString(BibEntry entry, GitMode type, FieldPreferences fieldPreferences, BibEntryTypesManager entryTypesManager) throws IOException { - StringWriter writer = new StringWriter(); - BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); - FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); - new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); - return writer.toString(); - } -} diff --git a/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java b/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java deleted file mode 100644 index 60ef97d87bc..00000000000 --- a/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.gui.collab.entrydelete; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableRemoveEntries; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.entry.BibEntry; - -public final class EntryDelete extends GitChange { - private final BibEntry deletedEntry; - - public EntryDelete(BibEntry deletedEntry, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.deletedEntry = deletedEntry; - setChangeName(deletedEntry.getCitationKey() - .map(key -> Localization.lang("Deleted entry '%0'", key)) - .orElse(Localization.lang("Deleted entry"))); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - databaseContext.getGit().removeEntry(deletedEntry); - undoEdit.addEdit(new UndoableRemoveEntries(databaseContext.getGit(), deletedEntry)); - } - - public BibEntry getDeletedEntry() { - return deletedEntry; - } -} diff --git a/src/main/java/org/jabref/gui/git/groupchange/GroupChange.java b/src/main/java/org/jabref/gui/git/groupchange/GroupChange.java deleted file mode 100644 index 8e57954486a..00000000000 --- a/src/main/java/org/jabref/gui/git/groupchange/GroupChange.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.jabref.gui.collab.groupchange; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.groups.GroupTreeNodeViewModel; -import org.jabref.gui.groups.UndoableModifySubtree; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.logic.bibtex.comparator.GroupDiff; -import org.jabref.logic.groups.DefaultGroupsFactory; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.groups.GroupTreeNode; - -public final class GroupChange extends GitChange { - private final GroupDiff groupDiff; - - public GroupChange(GroupDiff groupDiff, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.groupDiff = groupDiff; - setChangeName(groupDiff.getOriginalGroupRoot() == null ? Localization.lang("Removed all groups") : Localization - .lang("Modified groups tree")); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - GroupTreeNode oldRoot = groupDiff.getOriginalGroupRoot(); - GroupTreeNode newRoot = groupDiff.getNewGroupRoot(); - - GroupTreeNode root = databaseContext.getMetaData().getGroups().orElseGet(() -> { - GroupTreeNode groupTreeNode = new GroupTreeNode(DefaultGroupsFactory.getAllEntriesGroup()); - databaseContext.getMetaData().setGroups(groupTreeNode); - return groupTreeNode; - }); - - final UndoableModifySubtree undo = new UndoableModifySubtree( - new GroupTreeNodeViewModel(databaseContext.getMetaData().getGroups().orElse(null)), - new GroupTreeNodeViewModel(root), Localization.lang("Modified groups")); - root.removeAllChildren(); - if (newRoot == null) { - // I think setting root to null is not possible - root.setGroup(DefaultGroupsFactory.getAllEntriesGroup(), false, false, null); - } else { - // change root group, even though it'll be AllEntries anyway - root.setGroup(newRoot.getGroup(), false, false, null); - for (GroupTreeNode child : newRoot.getChildren()) { - child.copySubtree().moveTo(root); - } - } - - undoEdit.addEdit(undo); - } - - public GroupDiff getGroupDiff() { - return groupDiff; - } -} diff --git a/src/main/java/org/jabref/gui/git/groupchange/GroupChangeDetailsView.java b/src/main/java/org/jabref/gui/git/groupchange/GroupChangeDetailsView.java deleted file mode 100644 index ae8c1923635..00000000000 --- a/src/main/java/org/jabref/gui/git/groupchange/GroupChangeDetailsView.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.jabref.gui.collab.groupchange; - -import javafx.scene.control.Label; - -import org.jabref.gui.collab.GitChangeDetailsView; -import org.jabref.logic.l10n.Localization; - -public final class GroupChangeDetailsView extends GitChangeDetailsView { - - public GroupChangeDetailsView(GroupChange groupChange) { - String labelValue = ""; - if (groupChange.getGroupDiff().getNewGroupRoot() == null) { - labelValue = groupChange.getName() + '.'; - } else { - labelValue = Localization.lang("%0. Accepting the change replaces the complete groups tree with the externally modified groups tree.", groupChange.getName()); - } - Label label = new Label(labelValue); - setLeftAnchor(label, 8d); - setTopAnchor(label, 8d); - setRightAnchor(label, 8d); - setBottomAnchor(label, 8d); - - getChildren().setAll(label); - } -} diff --git a/src/main/java/org/jabref/gui/git/metedatachange/MetadataChange.java b/src/main/java/org/jabref/gui/git/metedatachange/MetadataChange.java deleted file mode 100644 index eff69451d42..00000000000 --- a/src/main/java/org/jabref/gui/git/metedatachange/MetadataChange.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.jabref.gui.collab.metedatachange; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.logic.bibtex.comparator.MetaDataDiff; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; - -public final class MetadataChange extends GitChange { - private final MetaDataDiff metaDataDiff; - - public MetadataChange(MetaDataDiff metaDataDiff, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.metaDataDiff = metaDataDiff; - setChangeName(Localization.lang("Metadata change")); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - // TODO: Metadata edit should be undoable - databaseContext.setMetaData(metaDataDiff.getNewMetaData()); - // group change is handled by GroupChange, so we set the groups root to the original value - // to prevent any inconsistency - metaDataDiff.getGroupDifferences() - .ifPresent(groupDiff -> databaseContext.getMetaData().setGroups(groupDiff.getOriginalGroupRoot())); - } - - public MetaDataDiff getMetaDataDiff() { - return metaDataDiff; - } -} diff --git a/src/main/java/org/jabref/gui/git/metedatachange/MetadataChangeDetailsView.java b/src/main/java/org/jabref/gui/git/metedatachange/MetadataChangeDetailsView.java deleted file mode 100644 index 71acbe65033..00000000000 --- a/src/main/java/org/jabref/gui/git/metedatachange/MetadataChangeDetailsView.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.jabref.gui.collab.metedatachange; - -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.layout.VBox; - -import org.jabref.gui.collab.GitChangeDetailsView; -import org.jabref.logic.bibtex.comparator.MetaDataDiff; -import org.jabref.logic.l10n.Localization; -import org.jabref.preferences.PreferencesService; - -public final class MetadataChangeDetailsView extends GitChangeDetailsView { - - public MetadataChangeDetailsView(MetadataChange metadataChange, PreferencesService preferencesService) { - VBox container = new VBox(15); - - Label header = new Label(Localization.lang("The following metadata changed:")); - header.getStyleClass().add("sectionHeader"); - container.getChildren().add(header); - - for (MetaDataDiff.Difference diff : metadataChange.getMetaDataDiff().getDifferences(preferencesService)) { - container.getChildren().add(new Label(getDifferenceString(diff.differenceType()))); - container.getChildren().add(new Label(diff.originalObject().toString())); - container.getChildren().add(new Label(diff.newObject().toString())); - } - - ScrollPane scrollPane = new ScrollPane(container); - scrollPane.setFitToWidth(true); - getChildren().setAll(scrollPane); - - setLeftAnchor(scrollPane, 8d); - setTopAnchor(scrollPane, 8d); - setRightAnchor(scrollPane, 8d); - setBottomAnchor(scrollPane, 8d); - } - - private String getDifferenceString(MetaDataDiff.DifferenceType changeType) { - return switch (changeType) { - case PROTECTED -> - Localization.lang("Library protection"); - case GROUPS_ALTERED -> - Localization.lang("Modified groups tree"); - case ENCODING -> - Localization.lang("Library encoding"); - case SAVE_SORT_ORDER -> - Localization.lang("Save sort order"); - case KEY_PATTERNS -> - Localization.lang("Key patterns"); - case USER_FILE_DIRECTORY -> - Localization.lang("User-specific file directory"); - case LATEX_FILE_DIRECTORY -> - Localization.lang("LaTeX file directory"); - case DEFAULT_KEY_PATTERN -> - Localization.lang("Default pattern"); - case SAVE_ACTIONS -> - Localization.lang("Save actions"); - case MODE -> - Localization.lang("Library mode"); - case GENERAL_FILE_DIRECTORY -> - Localization.lang("General file directory"); - case CONTENT_SELECTOR -> - Localization.lang("Content selectors"); - }; - } -} diff --git a/src/main/java/org/jabref/gui/git/preamblechange/PreambleChange.java b/src/main/java/org/jabref/gui/git/preamblechange/PreambleChange.java deleted file mode 100644 index 94d6928c443..00000000000 --- a/src/main/java/org/jabref/gui/git/preamblechange/PreambleChange.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.jabref.gui.collab.preamblechange; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoablePreambleChange; -import org.jabref.logic.bibtex.comparator.PreambleDiff; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class PreambleChange extends GitChange { - private static final Logger LOGGER = LoggerFactory.getLogger(PreambleChange.class); - - private final PreambleDiff preambleDiff; - - public PreambleChange(PreambleDiff preambleDiff, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.preambleDiff = preambleDiff; - - setChangeName(Localization.lang("Changed preamble")); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - databaseContext.getGit().setPreamble(preambleDiff.getNewPreamble()); - undoEdit.addEdit(new UndoablePreambleChange(databaseContext.getGit(), preambleDiff.getOriginalPreamble(), preambleDiff.getNewPreamble())); - } - - public PreambleDiff getPreambleDiff() { - return preambleDiff; - } -} diff --git a/src/main/java/org/jabref/gui/git/preamblechange/PreambleChangeDetailsView.java b/src/main/java/org/jabref/gui/git/preamblechange/PreambleChangeDetailsView.java deleted file mode 100644 index 8630f62c240..00000000000 --- a/src/main/java/org/jabref/gui/git/preamblechange/PreambleChangeDetailsView.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.jabref.gui.collab.preamblechange; - -import javafx.scene.control.Label; -import javafx.scene.layout.VBox; - -import org.jabref.gui.collab.DatabaseChangeDetailsView; -import org.jabref.logic.bibtex.comparator.PreambleDiff; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.strings.StringUtil; - -public final class PreambleChangeDetailsView extends DatabaseChangeDetailsView { - - public PreambleChangeDetailsView(PreambleChange preambleChange) { - PreambleDiff preambleDiff = preambleChange.getPreambleDiff(); - - VBox container = new VBox(); - Label header = new Label(Localization.lang("Changed preamble")); - header.getStyleClass().add("sectionHeader"); - container.getChildren().add(header); - - if (StringUtil.isNotBlank(preambleDiff.getOriginalPreamble())) { - container.getChildren().add(new Label(Localization.lang("Current value: %0", preambleDiff.getOriginalPreamble()))); - } - - if (StringUtil.isNotBlank(preambleDiff.getNewPreamble())) { - container.getChildren().add(new Label(Localization.lang("Value set externally: %0", preambleDiff.getNewPreamble()))); - } else { - container.getChildren().add(new Label(Localization.lang("Value cleared externally"))); - } - setLeftAnchor(container, 8d); - setTopAnchor(container, 8d); - setRightAnchor(container, 8d); - setBottomAnchor(container, 8d); - - getChildren().setAll(container); - } -} diff --git a/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAdd.java b/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAdd.java deleted file mode 100644 index 3aea67fdaf2..00000000000 --- a/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAdd.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.jabref.gui.collab.stringadd; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableInsertString; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.database.KeyCollisionException; -import org.jabref.model.entry.BibtexString; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class BibTexStringAdd extends GitChange { - private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringAdd.class); - - private final BibtexString addedString; - - public BibTexStringAdd(BibtexString addedString, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.addedString = addedString; - setChangeName(Localization.lang("Added string: '%0'", addedString.getName())); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - try { - databaseContext.getGit().addString(addedString); - undoEdit.addEdit(new UndoableInsertString(databaseContext.getGit(), addedString)); - } catch (KeyCollisionException ex) { - LOGGER.warn("Error: could not add string '{}': {}", addedString.getName(), ex.getMessage(), ex); - } - } - - public BibtexString getAddedString() { - return addedString; - } -} diff --git a/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAddDetailsView.java b/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAddDetailsView.java deleted file mode 100644 index fffa0d9c81b..00000000000 --- a/src/main/java/org/jabref/gui/git/stringadd/BibTexStringAddDetailsView.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.jabref.gui.collab.stringadd; - -import javafx.scene.control.Label; -import javafx.scene.layout.VBox; - -import org.jabref.gui.collab.GitChangeDetailsView; -import org.jabref.logic.l10n.Localization; - -public final class BibTexStringAddDetailsView extends GitChangeDetailsView { - - public BibTexStringAddDetailsView(BibTexStringAdd stringAdd) { - VBox container = new VBox(); - Label header = new Label(Localization.lang("Added string")); - header.getStyleClass().add("sectionHeader"); - container.getChildren().addAll( - header, - new Label(Localization.lang("Label: %0", stringAdd.getAddedString().getName())), - new Label(Localization.lang("Content: %0", stringAdd.getAddedString().getContent())) - ); - setLeftAnchor(container, 8d); - setTopAnchor(container, 8d); - setRightAnchor(container, 8d); - setBottomAnchor(container, 8d); - - getChildren().setAll(container); - } -} diff --git a/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChange.java b/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChange.java deleted file mode 100644 index 3901f496e92..00000000000 --- a/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChange.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.jabref.gui.collab.stringchange; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableStringChange; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.entry.BibtexString; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class BibTexStringChange extends GitChange { - private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringChange.class); - - private final BibtexString oldString; - private final BibtexString newString; - - public BibTexStringChange(BibtexString oldString, BibtexString newString, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.oldString = oldString; - this.newString = newString; - - setChangeName(Localization.lang("Modified string: '%0'", oldString.getName())); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - String oldContent = oldString.getContent(); - String newContent = newString.getContent(); - oldString.setContent(newContent); - undoEdit.addEdit(new UndoableStringChange(oldString, false, oldContent, newContent)); - } - - public BibtexString getOldString() { - return oldString; - } - - public BibtexString getNewString() { - return newString; - } -} diff --git a/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChangeDetailsView.java b/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChangeDetailsView.java deleted file mode 100644 index f5638b4da3f..00000000000 --- a/src/main/java/org/jabref/gui/git/stringchange/BibTexStringChangeDetailsView.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.jabref.gui.collab.stringchange; - -import javafx.scene.control.Label; -import javafx.scene.layout.VBox; - -import org.jabref.gui.collab.GitChangeDetailsView; -import org.jabref.logic.l10n.Localization; - -public final class BibTexStringChangeDetailsView extends GitChangeDetailsView { - - public BibTexStringChangeDetailsView(BibTexStringChange stringChange) { - VBox container = new VBox(); - Label header = new Label(Localization.lang("Modified string")); - header.getStyleClass().add("sectionHeader"); - container.getChildren().addAll( - header, - new Label(Localization.lang("Label: %0", stringChange.getOldString().getName())), - new Label(Localization.lang("Content: %0", stringChange.getNewString().getContent())) - ); - - container.getChildren().add(new Label(Localization.lang("Current content: %0", stringChange.getOldString().getContent()))); - setLeftAnchor(container, 8d); - setTopAnchor(container, 8d); - setRightAnchor(container, 8d); - setBottomAnchor(container, 8d); - - getChildren().setAll(container); - } -} diff --git a/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDelete.java b/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDelete.java deleted file mode 100644 index fb8ddbdadb2..00000000000 --- a/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDelete.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.jabref.gui.collab.stringdelete; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableRemoveString; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.entry.BibtexString; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class BibTexStringDelete extends GitChange { - private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringDelete.class); - - private final BibtexString deletedString; - - public BibTexStringDelete(BibtexString deletedString, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.deletedString = deletedString; - setChangeName(Localization.lang("Deleted string: '%0'", deletedString.getName())); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - try { - databaseContext.getGit().removeString(deletedString.getId()); - undoEdit.addEdit(new UndoableRemoveString(databaseContext.getGit(), deletedString)); - } catch (Exception ex) { - LOGGER.warn("Error: could not remove string '{}': {}", deletedString.getName(), ex.getMessage(), ex); - } - } - - public BibtexString getDeletedString() { - return deletedString; - } -} diff --git a/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDeleteDetailsView.java b/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDeleteDetailsView.java deleted file mode 100644 index 163d92df2cc..00000000000 --- a/src/main/java/org/jabref/gui/git/stringdelete/BibTexStringDeleteDetailsView.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.jabref.gui.collab.stringdelete; - -import javafx.scene.control.Label; -import javafx.scene.layout.VBox; - -import org.jabref.gui.collab.GitChangeDetailsView; -import org.jabref.logic.l10n.Localization; - -public final class BibTexStringDeleteDetailsView extends GitChangeDetailsView { - - public BibTexStringDeleteDetailsView(BibTexStringDelete stringDelete) { - VBox container = new VBox(); - Label header = new Label(Localization.lang("Deleted string")); - header.getStyleClass().add("sectionHeader"); - container.getChildren().addAll( - header, - new Label(Localization.lang("Label: %0", stringDelete.getDeletedString().getName())), - new Label(Localization.lang("Content: %0", stringDelete.getDeletedString().getContent())) - ); - setLeftAnchor(container, 8d); - setTopAnchor(container, 8d); - setRightAnchor(container, 8d); - setBottomAnchor(container, 8d); - - getChildren().setAll(container); - } -} diff --git a/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRename.java b/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRename.java deleted file mode 100644 index 9bfb89401dd..00000000000 --- a/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRename.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.jabref.gui.collab.stringrename; - -import org.jabref.gui.collab.GitChange; -import org.jabref.gui.collab.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableStringChange; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.GitContext; -import org.jabref.model.entry.BibtexString; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class BibTexStringRename extends GitChange { - private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringRename.class); - - private final BibtexString oldString; - private final BibtexString newString; - - public BibTexStringRename(BibtexString oldString, BibtexString newString, GitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.oldString = oldString; - this.newString = newString; - - setChangeName(Localization.lang("Renamed string: '%0'", oldString.getName())); - } - - @Override - public void applyChange(NamedCompound undoEdit) { - if (databaseContext.getGit().hasStringByName(newString.getName())) { - // The name to change to is already in the database, so we can't comply. - LOGGER.info("Cannot rename string '{}' to '{}' because the name is already in use", oldString.getName(), newString.getName()); - } - - String currentName = oldString.getName(); - String newName = newString.getName(); - oldString.setName(newName); - undoEdit.addEdit(new UndoableStringChange(oldString, true, currentName, newName)); - } - - public BibtexString getOldString() { - return oldString; - } - - public BibtexString getNewString() { - return newString; - } -} diff --git a/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRenameDetailsView.java b/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRenameDetailsView.java deleted file mode 100644 index c7bd70f79ea..00000000000 --- a/src/main/java/org/jabref/gui/git/stringrename/BibTexStringRenameDetailsView.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.jabref.gui.collab.stringrename; - -import javafx.scene.control.Label; - -import org.jabref.gui.collab.GitChangeDetailsView; - -public final class BibTexStringRenameDetailsView extends GitChangeDetailsView { - - public BibTexStringRenameDetailsView(BibTexStringRename stringRename) { - Label label = new Label(stringRename.getNewString().getName() + " : " + stringRename.getOldString().getContent()); - setLeftAnchor(label, 8d); - setTopAnchor(label, 8d); - setRightAnchor(label, 8d); - setBottomAnchor(label, 8d); - - getChildren().setAll(label); - } -} diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 539037bc4f8..a3a96f3a625 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -323,8 +323,11 @@ public void pullOnCurrentBranch() throws IOException { } catch (GitAPIException e) { if (e.getMessage().equals("origin: not found")) { LOGGER.info("No remote repository detected. Push skipped."); + } else if (e.getMessage().equals("HEAD is detached")) { + throw new IOException("HEAD is detached"); } else { LOGGER.info("Failed to pull"); + System.out.println(e.getMessage()); throw new RuntimeException(e); } } diff --git a/src/main/java/org/jabref/model/git/Git.java b/src/main/java/org/jabref/model/git/Git.java deleted file mode 100644 index 8c545c0126b..00000000000 --- a/src/main/java/org/jabref/model/git/Git.java +++ /dev/null @@ -1,641 +0,0 @@ -package org.jabref.model.git; - -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - -import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.model.database.event.EntriesAddedEvent; -import org.jabref.model.database.event.EntriesRemovedEvent; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibtexString; -import org.jabref.model.entry.Month; -import org.jabref.model.entry.event.EntriesEventSource; -import org.jabref.model.entry.event.EntryChangedEvent; -import org.jabref.model.entry.event.FieldChangedEvent; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.FieldFactory; -import org.jabref.model.entry.field.StandardField; -import org.jabref.model.strings.StringUtil; - -import com.google.common.eventbus.EventBus; -import com.google.common.eventbus.Subscribe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A bibliography database. This is the "bib" file (or the library stored in a shared SQL database) - */ -public class Git { - - private static final Logger LOGGER = LoggerFactory.getLogger(Git.class); - private static final Pattern RESOLVE_CONTENT_PATTERN = Pattern.compile(".*#[^#]+#.*"); - - /** - * State attributes - */ - private final ObservableList entries = FXCollections.synchronizedObservableList(FXCollections.observableArrayList(BibEntry::getObservables)); - private Map bibtexStrings = new ConcurrentHashMap<>(); - - private final EventBus eventBus = new EventBus(); - - private String preamble; - - // All file contents below the last entry in the file - private String epilog = ""; - - private String sharedDatabaseID; - - private String newLineSeparator = System.lineSeparator(); - - public Git(List entries, String newLineSeparator) { - this(entries); - this.newLineSeparator = newLineSeparator; - } - - public Git(List entries) { - this(); - insertEntries(entries); - } - - public Git() { - this.registerListener(new KeyChangeListener(this)); - } - - /** - * Returns a text with references resolved according to an optionally given database. - * - * @param toResolve maybenull The text to resolve. - * @param database maybenull The database to use for resolving the text. - * @return The resolved text or the original text if either the text or the database are null - * @deprecated use {@link Git#resolveForStrings(String)} - */ - @Deprecated - public static String getText(String toResolve, Git database) { - if ((toResolve != null) && (database != null)) { - return database.resolveForStrings(toResolve); - } - return toResolve; - } - - /** - * Returns the number of entries. - */ - public int getEntryCount() { - return entries.size(); - } - - /** - * Checks if the database contains entries. - */ - public boolean hasEntries() { - return !entries.isEmpty(); - } - - /** - * Returns the list of entries sorted by the given comparator. - */ - public List getEntriesSorted(Comparator comparator) { - List entriesSorted = new ArrayList<>(entries); - entriesSorted.sort(comparator); - return entriesSorted; - } - - /** - * Returns whether an entry with the given ID exists (-> entry_type + hashcode). - */ - public boolean containsEntryWithId(String id) { - return entries.stream().anyMatch(entry -> entry.getId().equals(id)); - } - - public ObservableList getEntries() { - return FXCollections.unmodifiableObservableList(entries); - } - - /** - * Returns a set of Strings, that contains all field names that are visible. This means that the fields - * are not internal fields. Internal fields are fields, that are starting with "_". - * - * @return set of fieldnames, that are visible - */ - public Set getAllVisibleFields() { - Set allFields = new TreeSet<>(Comparator.comparing(Field::getName)); - for (BibEntry e : getEntries()) { - allFields.addAll(e.getFields()); - } - return allFields.stream().filter(field -> !FieldFactory.isInternalField(field)) - .collect(Collectors.toSet()); - } - - /** - * Returns the entry with the given citation key. - */ - public synchronized Optional getEntryByCitationKey(String key) { - for (BibEntry entry : entries) { - if (key.equals(entry.getCitationKey().orElse(null))) { - return Optional.of(entry); - } - } - return Optional.empty(); - } - - /** - * Collects entries having the specified citation key and returns these entries as list. - * The order of the entries is the order they appear in the database. - * - * @return list of entries that contains the given key - */ - public synchronized List getEntriesByCitationKey(String key) { - List result = new ArrayList<>(); - - for (BibEntry entry : entries) { - entry.getCitationKey().ifPresent(entryKey -> { - if (key.equals(entryKey)) { - result.add(entry); - } - }); - } - return result; - } - - public synchronized void insertEntry(BibEntry entry) { - insertEntry(entry, EntriesEventSource.LOCAL); - } - - /** - * Inserts the entry. - * - * @param entry entry to insert - * @param eventSource source the event is sent from - */ - public synchronized void insertEntry(BibEntry entry, EntriesEventSource eventSource) { - insertEntries(Collections.singletonList(entry), eventSource); - } - - public synchronized void insertEntries(BibEntry... entries) { - insertEntries(Arrays.asList(entries), EntriesEventSource.LOCAL); - } - - public synchronized void insertEntries(List entries) { - insertEntries(entries, EntriesEventSource.LOCAL); - } - - public synchronized void insertEntries(List newEntries, EntriesEventSource eventSource) { - Objects.requireNonNull(newEntries); - for (BibEntry entry : newEntries) { - entry.registerListener(this); - } - if (newEntries.isEmpty()) { - eventBus.post(new EntriesAddedEvent(newEntries, eventSource)); - } else { - eventBus.post(new EntriesAddedEvent(newEntries, newEntries.get(0), eventSource)); - } - entries.addAll(newEntries); - } - - public synchronized void removeEntry(BibEntry bibEntry) { - removeEntries(Collections.singletonList(bibEntry)); - } - - public synchronized void removeEntry(BibEntry bibEntry, EntriesEventSource eventSource) { - removeEntries(Collections.singletonList(bibEntry), eventSource); - } - - /** - * Removes the given entries. - * The entries removed based on the id {@link BibEntry#getId()} - * - * @param toBeDeleted Entries to delete - */ - public synchronized void removeEntries(List toBeDeleted) { - removeEntries(toBeDeleted, EntriesEventSource.LOCAL); - } - - /** - * Removes the given entries. - * The entries are removed based on the id {@link BibEntry#getId()} - * - * @param toBeDeleted Entry to delete - * @param eventSource Source the event is sent from - */ - public synchronized void removeEntries(List toBeDeleted, EntriesEventSource eventSource) { - Objects.requireNonNull(toBeDeleted); - - List ids = new ArrayList<>(); - for (BibEntry entry : toBeDeleted) { - ids.add(entry.getId()); - } - boolean anyRemoved = entries.removeIf(entry -> ids.contains(entry.getId())); - if (anyRemoved) { - eventBus.post(new EntriesRemovedEvent(toBeDeleted, eventSource)); - } - } - - /** - * Returns the database's preamble. - * If the preamble text consists only of whitespace, then also an empty optional is returned. - */ - public synchronized Optional getPreamble() { - if (StringUtil.isBlank(preamble)) { - return Optional.empty(); - } else { - return Optional.of(preamble); - } - } - - /** - * Sets the database's preamble. - */ - public synchronized void setPreamble(String preamble) { - this.preamble = preamble; - } - - /** - * Inserts a Bibtex String. - */ - public synchronized void addString(BibtexString string) throws KeyCollisionException { - String id = string.getId(); - - if (hasStringByName(string.getName())) { - throw new KeyCollisionException("A string with that label already exists", id); - } - - if (bibtexStrings.containsKey(id)) { - throw new KeyCollisionException("Duplicate BibTeX string id.", id); - } - - bibtexStrings.put(id, string); - } - - /** - * Replaces the existing lists of BibTexString with the given one - * Duplicates throw KeyCollisionException - * - * @param stringsToAdd The collection of strings to set - */ - public void setStrings(List stringsToAdd) { - bibtexStrings = new ConcurrentHashMap<>(); - stringsToAdd.forEach(this::addString); - } - - /** - * Removes the string with the given id. - */ - public void removeString(String id) { - bibtexStrings.remove(id); - } - - /** - * Returns a Set of keys to all BibtexString objects in the database. - * These are in no sorted order. - */ - public Set getStringKeySet() { - return bibtexStrings.keySet(); - } - - /** - * Returns a Collection of all BibtexString objects in the database. - * These are in no particular order. - */ - public Collection getStringValues() { - return bibtexStrings.values(); - } - - /** - * Returns the string with the given id. - */ - public BibtexString getString(String id) { - return bibtexStrings.get(id); - } - - /** - * Returns the string with the given name/label - */ - public Optional getStringByName(String name) { - return getStringValues().stream().filter(string -> string.getName().equals(name)).findFirst(); - } - - /** - * Returns the number of strings. - */ - public int getStringCount() { - return bibtexStrings.size(); - } - - /** - * Check if there are strings. - */ - public boolean hasNoStrings() { - return bibtexStrings.isEmpty(); - } - - /** - * Copies the preamble of another Git. - * - * @param database another Git - */ - public void copyPreamble(Git database) { - setPreamble(database.getPreamble().orElse("")); - } - - /** - * Returns true if a string with the given label already exists. - */ - public synchronized boolean hasStringByName(String label) { - return bibtexStrings.values().stream().anyMatch(value -> value.getName().equals(label)); - } - - /** - * Resolves any references to strings contained in this field content, - * if possible. - */ - public String resolveForStrings(String content) { - Objects.requireNonNull(content, "Content for resolveForStrings must not be null."); - return resolveContent(content, new HashSet<>(), new HashSet<>()); - } - - /** - * Get all strings used in the entries. - */ - public Collection getUsedStrings(Collection entries) { - List result = new ArrayList<>(); - Set allUsedIds = new HashSet<>(); - - // All entries - for (BibEntry entry : entries) { - for (String fieldContent : entry.getFieldValues()) { - resolveContent(fieldContent, new HashSet<>(), allUsedIds); - } - } - - // Preamble - if (preamble != null) { - resolveContent(preamble, new HashSet<>(), allUsedIds); - } - - for (String stringId : allUsedIds) { - result.add((BibtexString) bibtexStrings.get(stringId).clone()); - } - - return result; - } - - /** - * Take the given collection of BibEntry and resolve any string - * references. - * - * @param entriesToResolve A collection of BibtexEntries in which all strings of the form - * #xxx# will be resolved against the hash map of string - * references stored in the database. - * @param inPlace If inPlace is true then the given BibtexEntries will be modified, if false then copies of the BibtexEntries are made before resolving the strings. - * @return a list of bibtexentries, with all strings resolved. It is dependent on the value of inPlace whether copies are made or the given BibtexEntries are modified. - */ - public List resolveForStrings(Collection entriesToResolve, boolean inPlace) { - Objects.requireNonNull(entriesToResolve, "entries must not be null."); - - List results = new ArrayList<>(entriesToResolve.size()); - - for (BibEntry entry : entriesToResolve) { - results.add(this.resolveForStrings(entry, inPlace)); - } - return results; - } - - /** - * Take the given BibEntry and resolve any string references. - * - * @param entry A BibEntry in which all strings of the form #xxx# will be - * resolved against the hash map of string references stored in - * the database. - * @param inPlace If inPlace is true then the given BibEntry will be - * modified, if false then a copy is made using close made before - * resolving the strings. - * @return a BibEntry with all string references resolved. It is - * dependent on the value of inPlace whether a copy is made or the - * given BibtexEntries is modified. - */ - public BibEntry resolveForStrings(BibEntry entry, boolean inPlace) { - BibEntry resultingEntry; - if (inPlace) { - resultingEntry = entry; - } else { - resultingEntry = (BibEntry) entry.clone(); - } - - for (Map.Entry field : resultingEntry.getFieldMap().entrySet()) { - resultingEntry.setField(field.getKey(), this.resolveForStrings(field.getValue())); - } - return resultingEntry; - } - - /** - * If the label represents a string contained in this database, returns - * that string's content. Resolves references to other strings, taking - * care not to follow a circular reference pattern. - * If the string is undefined, returns null. - */ - private String resolveString(String label, Set usedIds, Set allUsedIds) { - Objects.requireNonNull(label); - Objects.requireNonNull(usedIds); - Objects.requireNonNull(allUsedIds); - - for (BibtexString string : bibtexStrings.values()) { - if (string.getName().equalsIgnoreCase(label)) { - // First check if this string label has been resolved - // earlier in this recursion. If so, we have a - // circular reference, and have to stop to avoid - // infinite recursion. - if (usedIds.contains(string.getId())) { - LOGGER.info("Stopped due to circular reference in strings: " + label); - return label; - } - // If not, log this string's ID now. - usedIds.add(string.getId()); - if (allUsedIds != null) { - allUsedIds.add(string.getId()); - } - - // Ok, we found the string. Now we must make sure we - // resolve any references to other strings in this one. - String result = string.getContent(); - result = resolveContent(result, usedIds, allUsedIds); - - // Finished with recursing this branch, so we remove our - // ID again: - usedIds.remove(string.getId()); - - return result; - } - } - - // If we get to this point, the string has obviously not been defined locally. - // Check if one of the standard BibTeX month strings has been used: - Optional month = Month.getMonthByShortName(label); - return month.map(Month::getFullName).orElse(null); - } - - private String resolveContent(String result, Set usedIds, Set allUsedIds) { - String res = result; - if (RESOLVE_CONTENT_PATTERN.matcher(res).matches()) { - StringBuilder newRes = new StringBuilder(); - int piv = 0; - int next; - while ((next = res.indexOf(FieldWriter.BIBTEX_STRING_START_END_SYMBOL, piv)) >= 0) { - // We found the next string ref. Append the text - // up to it. - if (next > 0) { - newRes.append(res, piv, next); - } - int stringEnd = res.indexOf(FieldWriter.BIBTEX_STRING_START_END_SYMBOL, next + 1); - if (stringEnd >= 0) { - // We found the boundaries of the string ref, - // now resolve that one. - String refLabel = res.substring(next + 1, stringEnd); - String resolved = resolveString(refLabel, usedIds, allUsedIds); - - if (resolved == null) { - // Could not resolve string. Display the # - // characters rather than removing them: - newRes.append(res, next, stringEnd + 1); - } else { - // The string was resolved, so we display its meaning only, - // stripping the # characters signifying the string label: - newRes.append(resolved); - } - piv = stringEnd + 1; - } else { - // We did not find the boundaries of the string ref. This - // makes it impossible to interpret it as a string label. - // So we should just append the rest of the text and finish. - newRes.append(res.substring(next)); - piv = res.length(); - break; - } - } - if (piv < (res.length() - 1)) { - newRes.append(res.substring(piv)); - } - res = newRes.toString(); - } - return res; - } - - public String getEpilog() { - return epilog; - } - - public void setEpilog(String epilog) { - this.epilog = epilog; - } - - /** - * Registers a listener object (subscriber) to the internal event bus. - * The following events are posted: - * - * - {@link EntriesAddedEvent} - * - {@link EntryChangedEvent} - * - {@link EntriesRemovedEvent} - * - * @param listener listener (subscriber) to add - */ - public void registerListener(Object listener) { - this.eventBus.register(listener); - } - - /** - * Unregisters an listener object. - * - * @param listener listener (subscriber) to remove - */ - public void unregisterListener(Object listener) { - try { - this.eventBus.unregister(listener); - } catch (IllegalArgumentException e) { - // occurs if the event source has not been registered, should not prevent shutdown - LOGGER.debug("Problem unregistering", e); - } - } - - @Subscribe - private void relayEntryChangeEvent(FieldChangedEvent event) { - eventBus.post(event); - } - - public Optional getReferencedEntry(BibEntry entry) { - return entry.getField(StandardField.CROSSREF).flatMap(this::getEntryByCitationKey); - } - - public Optional getSharedDatabaseID() { - return Optional.ofNullable(this.sharedDatabaseID); - } - - public void setSharedDatabaseID(String sharedDatabaseID) { - this.sharedDatabaseID = sharedDatabaseID; - } - - public boolean isShared() { - return getSharedDatabaseID().isPresent(); - } - - public void clearSharedDatabaseID() { - this.sharedDatabaseID = null; - } - - /** - * Generates and sets a random ID which is globally unique. - * - * @return The generated sharedDatabaseID - */ - public String generateSharedDatabaseID() { - this.sharedDatabaseID = new BigInteger(128, new SecureRandom()).toString(32); - return this.sharedDatabaseID; - } - - /** - * Returns the number of occurrences of the given citation key in this database. - */ - public long getNumberOfCitationKeyOccurrences(String key) { - return entries.stream() - .flatMap(entry -> entry.getCitationKey().stream()) - .filter(key::equals) - .count(); - } - - /** - * Checks if there is more than one occurrence of the citation key. - */ - public boolean isDuplicateCitationKeyExisting(String key) { - return getNumberOfCitationKeyOccurrences(key) > 1; - } - - /** - * Set the newline separator. - */ - public void setNewLineSeparator(String newLineSeparator) { - this.newLineSeparator = newLineSeparator; - } - - /** - * Returns the string used to indicate a linebreak - */ - public String getNewLineSeparator() { - return newLineSeparator; - } -} From bbf1829c3530e6f9e06bd03e820a1ca32c3e2ed1 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Sat, 18 Nov 2023 20:17:48 -0300 Subject: [PATCH 37/48] resolve all erros from file init --- .../ExternalChangesResolverViewModel.java | 4 + .../gui/exporter/SaveDatabaseAction.java | 11 +- .../git/ExternalChangesResolverViewModel.java | 55 ++ .../java/org/jabref/gui/git/GitChange.java | 22 +- .../jabref/gui/git/GitChangeDetailsView.java | 1 - .../gui/git/GitChangeDetailsViewFactory.java | 17 + .../org/jabref/gui/git/GitChangeResolver.java | 16 + ...alog.java => GitChangeResolverDialog.java} | 24 +- .../gui/git/GitChangeResolverFactory.java | 29 + .../org/jabref/gui/git/entryadd/EntryAdd.java | 33 + .../gui/git/entrychange/EntryChange.java | 51 ++ .../entrychange/EntryChangeDetailsView.java | 89 +++ .../git/entrychange/EntryChangeResolver.java | 48 ++ .../EntryWithPreviewAndSourceDetailsView.java | 39 ++ .../git/entrychange/PreviewWithSourceTab.java | 82 +++ .../gui/git/entrydelete/EntryDelete.java | 33 + .../org/jabref/gui/preview/PreviewViewer.java | 4 + .../gui/undo/UndoableInsertEntries.java | 4 + .../gui/undo/UndoableRemoveEntries.java | 4 + .../{gui => model}/git/BibGitContext.java | 44 +- .../org/jabref/model/git/GitDatabase.java | 613 ++++++++++++++++++ .../jabref/model/git/KeyChangeListener.java | 80 +++ .../model/git/KeyCollisionException.java | 27 + .../model/git/event/EntriesRemovedEvent.java | 30 + 24 files changed, 1294 insertions(+), 66 deletions(-) create mode 100644 src/main/java/org/jabref/gui/git/ExternalChangesResolverViewModel.java create mode 100644 src/main/java/org/jabref/gui/git/GitChangeDetailsViewFactory.java create mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolver.java rename src/main/java/org/jabref/gui/git/{GitChangesResolverDialog.java => GitChangeResolverDialog.java} (81%) create mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java create mode 100644 src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChange.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java create mode 100644 src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java create mode 100644 src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java rename src/main/java/org/jabref/{gui => model}/git/BibGitContext.java (86%) create mode 100644 src/main/java/org/jabref/model/git/GitDatabase.java create mode 100644 src/main/java/org/jabref/model/git/KeyChangeListener.java create mode 100644 src/main/java/org/jabref/model/git/KeyCollisionException.java create mode 100644 src/main/java/org/jabref/model/git/event/EntriesRemovedEvent.java diff --git a/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java b/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java index 0f3906594dc..441523db3a4 100644 --- a/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java +++ b/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java @@ -14,6 +14,7 @@ import javafx.collections.ObservableList; import org.jabref.gui.AbstractViewModel; +import org.jabref.gui.git.GitChange; import org.jabref.gui.undo.NamedCompound; import org.jabref.logic.l10n.Localization; @@ -53,6 +54,9 @@ public ExternalChangesResolverViewModel(List externalChanges, Un canAskUserToResolveChange = Bindings.createBooleanBinding(() -> selectedChange.isNotNull().get() && selectedChange.get().getExternalChangeResolver().isPresent(), selectedChange); } + public ExternalChangesResolverViewModel(List changes2, UndoManager undoManager2) { + } + public ObservableList getVisibleChanges() { return visibleChanges; } diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index d6910622750..b857fc34676 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -6,6 +6,7 @@ import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; @@ -26,7 +27,8 @@ import org.jabref.gui.autosaveandbackup.AutosaveManager; import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.git.GitCredentialsDialogView; -import org.jabref.gui.git.GitChangesResolverDialog; +import org.jabref.gui.git.GitChange; +import org.jabref.gui.git.GitChangeResolverDialog; import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.gui.util.BackgroundTask; @@ -46,6 +48,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.ChangePropagation; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.git.BibGitContext; import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.SelfContainedSaveOrder; import org.jabref.preferences.PreferencesService; @@ -331,6 +334,8 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset */ public void automaticGitUpdate(Path filePath) throws IOException, GitAPIException { GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences()); + List changes = new ArrayList(); + BibGitContext bibGitContext = new BibGitContext(); if (preferences.getGitPreferences().getAutoCommit()) { String automaticCommitMsg = "Automatic update via JabRef"; git.createCommitWithSingleFileOnCurrentBranch(filePath.getFileName().toString(), automaticCommitMsg); @@ -354,14 +359,14 @@ public void automaticGitUpdate(Path filePath) throws IOException, GitAPIExceptio git.pullOnCurrentBranch(); } catch (IOException ex2) { if (ex2.getMessage().equals("HEAD is detached")) { - GitChangesResolverDialog GitChangesResolverDialog = new GitChangesResolverDialog(Path filePath); + GitChangeResolverDialog GitChangeResolverDialog = new GitChangeResolverDialog(changes, bibGitContext, "Merge issues"); } else { throw new RuntimeException(ex2.getMessage()); } } } else if (ex1.getMessage().equals("HEAD is detached")) { - GitChangesResolverDialog GitChangesResolverDialog = new GitChangesResolverDialog(); + GitChangeResolverDialog GitChangeResolverDialog = new GitChangeResolverDialog(changes, bibGitContext, "Merge issues"); } else { throw new IOException(ex1.getMessage()); diff --git a/src/main/java/org/jabref/gui/git/ExternalChangesResolverViewModel.java b/src/main/java/org/jabref/gui/git/ExternalChangesResolverViewModel.java new file mode 100644 index 00000000000..43f44bd5f3c --- /dev/null +++ b/src/main/java/org/jabref/gui/git/ExternalChangesResolverViewModel.java @@ -0,0 +1,55 @@ +package org.jabref.gui.git; + +import java.util.List; + +import javax.swing.undo.UndoManager; + +import javafx.beans.binding.BooleanExpression; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; + +public class ExternalChangesResolverViewModel { + private StringProperty selectedChange; + + public ExternalChangesResolverViewModel(List changes, UndoManager undoManager) { + this.selectedChange = new SimpleStringProperty(); + } + + public boolean areAllChangesResolved() { + return false; + } + + public ObservableList getVisibleChanges() { + return null; + } + + public BooleanExpression canAskUserToResolveChangeProperty() { + return null; + } + + public StringProperty selectedChangeProperty() { + return this.selectedChange; + } + + public void denyChange() { + } + + public void acceptChange() { + } + + public ObservableValue getSelectedChange() { + return this.selectedChange; + } + + public ObservableValue areAllChangesResolvedProperty() { + return null; + } + + public void applyChanges() { + } + + public void acceptMergedChange() {} + +} diff --git a/src/main/java/org/jabref/gui/git/GitChange.java b/src/main/java/org/jabref/gui/git/GitChange.java index 17d3d27e59b..61ebd791f9e 100644 --- a/src/main/java/org/jabref/gui/git/GitChange.java +++ b/src/main/java/org/jabref/gui/git/GitChange.java @@ -10,25 +10,19 @@ import org.jabref.gui.git.entryadd.EntryAdd; import org.jabref.gui.git.entrychange.EntryChange; import org.jabref.gui.git.entrydelete.EntryDelete; - import org.jabref.gui.git.groupchange.GroupChange; - import org.jabref.gui.git.metedatachange.MetadataChange; - import org.jabref.gui.git.preamblechange.PreambleChange; - import org.jabref.gui.git.stringadd.BibTexStringAdd; - import org.jabref.gui.git.stringchange.BibTexStringChange; - import org.jabref.gui.git.stringdelete.BibTexStringDelete; - import org.jabref.gui.git.stringrename.BibTexStringRename; + import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.util.OptionalObjectProperty; - import org.jabref.model.git.GitContext; + import org.jabref.model.git.BibGitContext; - public sealed abstract class GitChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename { - protected final GitContext gitContext; - protected final OptionalObjectProperty externalChangeResolver = OptionalObjectProperty.empty(); + public sealed abstract class GitChange permits EntryAdd, EntryChange, EntryDelete { + protected final BibGitContext BibGitContext; + protected final OptionalObjectProperty externalChangeResolver = OptionalObjectProperty.empty(); private final BooleanProperty accepted = new SimpleBooleanProperty(); private final StringProperty name = new SimpleStringProperty(); - protected GitChange(GitContext gitContext, GitChangeResolverFactory gitChangeResolverFactory) { - this.gitContext = gitContext; + protected GitChange(BibGitContext BibGitContext, GitChangeResolverFactory gitChangeResolverFactory) { + this.BibGitContext = BibGitContext; setChangeName("Unnamed Change!"); if (gitChangeResolverFactory != null) { @@ -63,7 +57,7 @@ protected void setChangeName(String changeName) { name.set(changeName); } - public Optional getExternalChangeResolver() { + public Optional getExternalChangeResolver() { return externalChangeResolver.get(); } diff --git a/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java b/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java index 67763c4c971..74729be8c6b 100644 --- a/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java @@ -1,5 +1,4 @@ package org.jabref.gui.git; public class GitChangeDetailsView { - } diff --git a/src/main/java/org/jabref/gui/git/GitChangeDetailsViewFactory.java b/src/main/java/org/jabref/gui/git/GitChangeDetailsViewFactory.java new file mode 100644 index 00000000000..3b56801721b --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChangeDetailsViewFactory.java @@ -0,0 +1,17 @@ +package org.jabref.gui.git; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.preview.PreviewViewer; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.git.BibGitContext; +import org.jabref.preferences.PreferencesService; + +public class GitChangeDetailsViewFactory { + + public GitChangeDetailsViewFactory(BibGitContext git, DialogService dialogService, StateManager stateManager, ThemeManager themeManager, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, TaskExecutor taskExecutor) { + } + +} diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolver.java b/src/main/java/org/jabref/gui/git/GitChangeResolver.java new file mode 100644 index 00000000000..e6e366cf492 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChangeResolver.java @@ -0,0 +1,16 @@ +package org.jabref.gui.git; + +import java.util.Optional; + +import org.jabref.gui.DialogService; +import org.jabref.gui.git.entrychange.EntryChangeResolver; + +public sealed abstract class GitChangeResolver permits EntryChangeResolver { + protected final DialogService dialogService; + + protected GitChangeResolver(DialogService dialogService) { + this.dialogService = dialogService; + } + + public abstract Optional askUserToResolveChange(); +} diff --git a/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java b/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java similarity index 81% rename from src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java rename to src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java index 17507b183b3..2373bf3197c 100644 --- a/src/main/java/org/jabref/gui/git/GitChangesResolverDialog.java +++ b/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java @@ -16,13 +16,14 @@ import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; - import org.jabref.gui.collab.ExternalChangesResolverViewModel; + import org.jabref.gui.git.ExternalChangesResolverViewModel; import org.jabref.gui.preview.PreviewViewer; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.TaskExecutor; import org.jabref.model.entry.BibEntryTypesManager; - import org.jabref.preferences.PreferencesService; +import org.jabref.model.git.BibGitContext; +import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; import com.tobiasdiez.easybind.EasyBind; @@ -30,8 +31,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - public class GitChangesResolverDialog extends BaseDialog { - private final static Logger LOGGER = LoggerFactory.getLogger(GitChangesResolverDialog.class); + public class GitChangeResolverDialog extends BaseDialog { + private final static Logger LOGGER = LoggerFactory.getLogger(GitChangeResolverDialog.class); /** * Reconstructing the details view to preview an {@link GitChange} every time it's selected is a heavy operation. * It is also useless because changes are static and if the change data is static then the view doesn't have to change @@ -68,7 +69,7 @@ public class GitChangesResolverDialog extends BaseDialog { * @param changes The list of changes * @param git The git to apply the changes to */ - public GitChangesResolverDialog(List changes, BibGitContext git, String dialogTitle) { + public GitChangeResolverDialog(List changes, BibGitContext git, String dialogTitle) { this.changes = changes; this.git = git; @@ -103,16 +104,9 @@ private void initialize() { changesTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); changesTableView.getSelectionModel().selectFirst(); - viewModel.selectedChangeProperty().bind(changesTableView.getSelectionModel().selectedItemProperty()); - EasyBind.subscribe(viewModel.selectedChangeProperty(), selectedChange -> { - if (selectedChange != null) { - GitChangeDetailsView detailsView = DETAILS_VIEW_CACHE.computeIfAbsent(selectedChange, gitChangeDetailsViewFactory::create); - changeInfoPane.setCenter(detailsView); - } - }); EasyBind.subscribe(viewModel.areAllChangesResolvedProperty(), isResolved -> { - if (isResolved) { + if ((boolean) isResolved) { viewModel.applyChanges(); close(); } @@ -131,7 +125,7 @@ public void acceptChanges() { @FXML public void askUserToResolveChange() { - viewModel.getSelectedChange().flatMap(GitChange::getExternalChangeResolver) - .flatMap(GitChangesResolver::askUserToResolveChange).ifPresent(viewModel::acceptMergedChange); + viewModel.getSelectedChange().flatMap(null) + .flatMap(null); } } \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java b/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java new file mode 100644 index 00000000000..4963f9ffa0a --- /dev/null +++ b/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java @@ -0,0 +1,29 @@ +package org.jabref.gui.git; + +import java.util.Optional; + +import org.jabref.gui.DialogService; +import org.jabref.gui.git.entrychange.EntryChange; +import org.jabref.gui.git.entrychange.EntryChangeResolver; +import org.jabref.model.git.BibGitContext; +import org.jabref.preferences.PreferencesService; + +public class GitChangeResolverFactory { + private final DialogService dialogService; + private final BibGitContext gitContext; + private final PreferencesService preferencesService; + + public GitChangeResolverFactory(DialogService dialogService, BibGitContext gitContext, PreferencesService preferencesService) { + this.dialogService = dialogService; + this.gitContext = gitContext; + this.preferencesService = preferencesService; + } + + public Optional create(GitChange change) { + if (change instanceof EntryChange entryChange) { + return Optional.of(new EntryChangeResolver(entryChange, dialogService, gitContext, preferencesService)); + } + + return Optional.empty(); + } +} \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java b/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java new file mode 100644 index 00000000000..f7f149dc82f --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java @@ -0,0 +1,33 @@ +package org.jabref.gui.git.entryadd; + +import org.jabref.gui.git.GitChange; +import org.jabref.gui.git.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableInsertEntries; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.git.BibGitContext; + +public final class EntryAdd extends GitChange { + private final BibEntry addedEntry; + private final BibGitContext databaseContext; + + public EntryAdd(BibEntry addedEntry, BibGitContext databaseContext, GitChangeResolverFactory GitChangeResolverFactory) { + super(databaseContext, GitChangeResolverFactory); + this.addedEntry = addedEntry; + setChangeName(addedEntry.getCitationKey() + .map(key -> Localization.lang("Added entry '%0'", key)) + .orElse(Localization.lang("Added entry"))); + this.databaseContext = databaseContext; + } + + @Override + public void applyChange(NamedCompound undoEdit) { + databaseContext.getDatabase().insertEntry(addedEntry); + undoEdit.addEdit(new UndoableInsertEntries(databaseContext.getDatabase(), addedEntry)); + } + + public BibEntry getAddedEntry() { + return addedEntry; + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java new file mode 100644 index 00000000000..845ff17cd6f --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java @@ -0,0 +1,51 @@ +package org.jabref.gui.git.entrychange; + +import javax.swing.undo.CompoundEdit; + +import org.jabref.gui.git.GitChange; +import org.jabref.gui.git.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableInsertEntries; +import org.jabref.gui.undo.UndoableRemoveEntries; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.git.BibGitContext; +import org.jabref.model.entry.BibEntry; + +public final class EntryChange extends GitChange { + private final BibEntry oldEntry; + private final BibEntry newEntry; + private BibGitContext gitContext; + + public EntryChange(BibEntry oldEntry, BibEntry newEntry, BibGitContext gitContext, GitChangeResolverFactory gitChangeResolverFactory) { + super(gitContext, gitChangeResolverFactory); + this.oldEntry = oldEntry; + this.newEntry = newEntry; + this.gitContext = gitContext; + setChangeName(oldEntry.getCitationKey().map(key -> Localization.lang("Modified entry '%0'", key)) + .orElse(Localization.lang("Modified entry"))); + } + + public EntryChange(BibEntry oldEntry, BibEntry newEntry, BibGitContext gitContext) { + this(oldEntry, newEntry, gitContext, null); + } + + public BibEntry getOldEntry() { + return oldEntry; + } + + public BibEntry getNewEntry() { + return newEntry; + } + + @Override + public void applyChange(NamedCompound undoEdit) { + this.gitContext.getDatabase().removeEntry(oldEntry); + this.gitContext.getDatabase().insertEntry(newEntry); + CompoundEdit changeEntryEdit = new CompoundEdit(); + changeEntryEdit.addEdit(new UndoableRemoveEntries(gitContext.getDatabase(), oldEntry)); + changeEntryEdit.addEdit(new UndoableInsertEntries(gitContext.getDatabase(), newEntry)); + changeEntryEdit.end(); + + undoEdit.addEdit(changeEntryEdit); + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java new file mode 100644 index 00000000000..cf2cf0c49ed --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java @@ -0,0 +1,89 @@ +package org.jabref.gui.git.entrychange; + +import javafx.geometry.Orientation; +import javafx.scene.control.Label; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TabPane; +import javafx.scene.layout.VBox; + +import java.util.List; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.git.GitChangeDetailsView; +import org.jabref.gui.preview.PreviewViewer; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.git.BibGitContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.preferences.PreferencesService; + +import com.tobiasdiez.easybind.EasyBind; + +public final class EntryChangeDetailsView extends GitChangeDetailsView { + private final PreviewWithSourceTab oldPreviewWithSourcesTab = new PreviewWithSourceTab(); + private final PreviewWithSourceTab newPreviewWithSourcesTab = new PreviewWithSourceTab(); + + public EntryChangeDetailsView(BibEntry oldEntry, + BibEntry newEntry, + BibGitContext databaseContext, + DialogService dialogService, + StateManager stateManager, + ThemeManager themeManager, + PreferencesService preferencesService, + BibEntryTypesManager entryTypesManager, + PreviewViewer previewViewer, + TaskExecutor taskExecutor) { + Label inJabRef = new Label(Localization.lang("In JabRef")); + inJabRef.getStyleClass().add("lib-change-header"); + Label onDisk = new Label(Localization.lang("On disk")); + onDisk.getStyleClass().add("lib-change-header"); + + // we need a copy here as we otherwise would set the same entry twice + PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, preferencesService, stateManager, themeManager, taskExecutor); + + TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, preferencesService, entryTypesManager, previewClone, Localization.lang("Entry Preview")); + TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, preferencesService, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); + + EasyBind.subscribe(oldEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { + newEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); + }); + + EasyBind.subscribe(newEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { + if (oldEntryTabPane.getSelectionModel().getSelectedIndex() != selectedIndex.intValue()) { + oldEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); + } + }); + + VBox containerOld = new VBox(inJabRef, oldEntryTabPane); + VBox containerNew = new VBox(onDisk, newEntryTabPane); + + SplitPane split = new SplitPane(containerOld, containerNew); + split.setOrientation(Orientation.HORIZONTAL); + + setLeftAnchor(split, 8d); + setTopAnchor(split, 8d); + setRightAnchor(split, 8d); + setBottomAnchor(split, 8d); + + this.getChildren().add(split); + } + + private List getChildren() { + return null; + } + + private void setBottomAnchor(SplitPane split, double d) { + } + + private void setRightAnchor(SplitPane split, double d) { + } + + private void setTopAnchor(SplitPane split, double d) { + } + + private void setLeftAnchor(SplitPane split, double d) { + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java new file mode 100644 index 00000000000..4c5b06737ef --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java @@ -0,0 +1,48 @@ +package org.jabref.gui.git.entrychange; + +import java.util.Optional; + +import org.jabref.gui.DialogService; +import org.jabref.gui.git.GitChange; +import org.jabref.gui.git.GitChangeResolver; +import org.jabref.gui.mergeentries.EntriesMergeResult; +import org.jabref.gui.mergeentries.MergeEntriesDialog; +import org.jabref.gui.mergeentries.newmergedialog.ShowDiffConfig; +import org.jabref.gui.mergeentries.newmergedialog.diffhighlighter.DiffHighlighter.BasicDiffMethod; +import org.jabref.gui.mergeentries.newmergedialog.toolbar.ThreeWayMergeToolbar; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.git.BibGitContext; +import org.jabref.preferences.PreferencesService; + +public final class EntryChangeResolver extends GitChangeResolver { + private final EntryChange entryChange; + private final BibGitContext databaseContext; + + private final PreferencesService preferencesService; + + public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, BibGitContext databaseContext, PreferencesService preferencesService) { + super(dialogService); + this.entryChange = entryChange; + this.databaseContext = databaseContext; + this.preferencesService = preferencesService; + } + + @Override + public Optional askUserToResolveChange() { + MergeEntriesDialog mergeEntriesDialog = new MergeEntriesDialog(entryChange.getOldEntry(), entryChange.getNewEntry(), preferencesService); + mergeEntriesDialog.setLeftHeaderText(Localization.lang("In JabRef")); + mergeEntriesDialog.setRightHeaderText(Localization.lang("On disk")); + mergeEntriesDialog.configureDiff(new ShowDiffConfig(ThreeWayMergeToolbar.DiffView.SPLIT, BasicDiffMethod.WORDS)); + + return dialogService.showCustomDialogAndWait(mergeEntriesDialog) + .map(this::mapMergeResultToExternalChange); + } + + private EntryChange mapMergeResultToExternalChange(EntriesMergeResult entriesMergeResult) { + return new EntryChange( + entryChange.getOldEntry(), + entriesMergeResult.mergedEntry(), + databaseContext + ); + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java new file mode 100644 index 00000000000..a614a14802a --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java @@ -0,0 +1,39 @@ +package org.jabref.gui.git.entrychange; + +import javafx.scene.control.TabPane; + +import org.jabref.gui.git.GitChangeDetailsView; +import org.jabref.gui.preview.PreviewViewer; +import org.jabref.model.git.BibGitContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.preferences.PreferencesService; + +public final class EntryWithPreviewAndSourceDetailsView extends GitChangeDetailsView { + + private final PreviewWithSourceTab previewWithSourceTab = new PreviewWithSourceTab(); + + public EntryWithPreviewAndSourceDetailsView(BibEntry entry, BibGitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { + TabPane tabPanePreviewCode = previewWithSourceTab.getPreviewWithSourceTab(entry, bibGitContext, preferencesService, entryTypesManager, previewViewer); + setLeftAnchor(tabPanePreviewCode, 8d); + setTopAnchor(tabPanePreviewCode, 8d); + setRightAnchor(tabPanePreviewCode, 8d); + setBottomAnchor(tabPanePreviewCode, 8d); + } + + private void setLeftAnchor(TabPane tabPanePreviewCode, double d) { + } + + private void setBottomAnchor(TabPane tabPanePreviewCode, double d) { + } + + private Object getChildren() { + return null; + } + + private void setTopAnchor(TabPane tabPanePreviewCode, double d) { + } + + private void setRightAnchor(TabPane tabPanePreviewCode, double d) { + } +} diff --git a/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java b/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java new file mode 100644 index 00000000000..e7b85fb200e --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java @@ -0,0 +1,82 @@ +package org.jabref.gui.git.entrychange; + +import java.io.IOException; +import java.io.StringWriter; + +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; + +import org.jabref.gui.preview.PreviewViewer; +import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldPreferences; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.OS; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.git.BibGitContext; +import org.jabref.model.strings.StringUtil; +import org.jabref.preferences.PreferencesService; + +import org.fxmisc.richtext.CodeArea; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PreviewWithSourceTab { + + private static final Logger LOGGER = LoggerFactory.getLogger(PreviewWithSourceTab.class); + + public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDatabaseContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { + return getPreviewWithSourceTab(entry, bibDatabaseContext, preferencesService, entryTypesManager, previewViewer, ""); + } + + public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDatabaseContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, String label) { + previewViewer.setLayout(preferencesService.getPreviewPreferences().getSelectedPreviewLayout()); + previewViewer.setEntry(entry); + + CodeArea codeArea = new CodeArea(); + codeArea.setId("bibtexcodearea"); + codeArea.setWrapText(true); + codeArea.setDisable(true); + + TabPane tabPanePreviewCode = new TabPane(); + Tab previewTab = new Tab(); + previewTab.setContent(previewViewer); + + if (StringUtil.isNullOrEmpty(label)) { + previewTab.setText(Localization.lang("Entry preview")); + } else { + previewTab.setText(label); + } + + try { + codeArea.appendText(getSourceString(entry, bibDatabaseContext.getMode(), preferencesService.getFieldPreferences(), entryTypesManager)); + } catch (IOException e) { + LOGGER.error("Error getting Bibtex: {}", entry); + } + codeArea.setEditable(false); + Tab codeTab = new Tab(Localization.lang("%0 source", bibDatabaseContext.getMode().getFormattedName()), codeArea); + + tabPanePreviewCode.getTabs().addAll(previewTab, codeTab); + return tabPanePreviewCode; + } + + private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences, BibEntryTypesManager entryTypesManager) throws IOException { + StringWriter writer = new StringWriter(); + BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); + FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); + new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); + return writer.toString(); + } + + public TabPane getPreviewWithSourceTab(BibEntry oldEntry, BibGitContext databaseContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewClone, String lang) { + return null; + } + + public TabPane getPreviewWithSourceTab(BibEntry entry, BibGitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { + return null; + } +} diff --git a/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java b/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java new file mode 100644 index 00000000000..c3aae272da1 --- /dev/null +++ b/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java @@ -0,0 +1,33 @@ +package org.jabref.gui.git.entrydelete; + +import org.jabref.gui.git.GitChange; +import org.jabref.gui.git.GitChangeResolverFactory; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableRemoveEntries; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.git.BibGitContext; +import org.jabref.model.entry.BibEntry; + +public final class EntryDelete extends GitChange { + private final BibEntry deletedEntry; + private final BibGitContext databaseContext; + + public EntryDelete(BibEntry deletedEntry, BibGitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { + super(databaseContext, databaseChangeResolverFactory); + this.deletedEntry = deletedEntry; + setChangeName(deletedEntry.getCitationKey() + .map(key -> Localization.lang("Deleted entry '%0'", key)) + .orElse(Localization.lang("Deleted entry"))); + this.databaseContext = databaseContext; + } + + @Override + public void applyChange(NamedCompound undoEdit) { + this.databaseContext.getDatabase().removeEntry(deletedEntry); + undoEdit.addEdit(new UndoableRemoveEntries(this.databaseContext.getDatabase(), deletedEntry)); + } + + public BibEntry getDeletedEntry() { + return deletedEntry; + } +} diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 544275f741a..3216659ce45 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -30,6 +30,7 @@ import org.jabref.logic.util.WebViewStore; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import org.jabref.model.git.BibGitContext; import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; @@ -198,6 +199,9 @@ public PreviewViewer(BibDatabaseContext database, themeManager.installCss(previewView.getEngine()); } + public PreviewViewer(BibGitContext git, DialogService dialogService2, PreferencesService preferencesService2, StateManager stateManager, ThemeManager themeManager, TaskExecutor taskExecutor2) { + } + private void highlightSearchPattern() { String callbackForUnmark = ""; if (searchHighlightPattern.isPresent()) { diff --git a/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java b/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java index 3af516b94c1..a84f189057e 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java +++ b/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java @@ -6,6 +6,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; +import org.jabref.model.git.GitDatabase; import org.jabref.model.strings.StringUtil; import org.slf4j.Logger; @@ -37,6 +38,9 @@ public UndoableInsertEntries(BibDatabase database, List entries, boole this.paste = paste; } + public UndoableInsertEntries(GitDatabase database2, BibEntry addedEntry) { + } + @Override public String getPresentationName() { if (paste) { diff --git a/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java b/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java index 65b6ae48331..403ca2ae237 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java +++ b/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java @@ -7,6 +7,7 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.EntriesEventSource; +import org.jabref.model.git.GitDatabase; import org.jabref.model.strings.StringUtil; import org.slf4j.Logger; @@ -40,6 +41,9 @@ public UndoableRemoveEntries(BibDatabase base, List entries, boolean c this.cut = cut; } + public UndoableRemoveEntries(GitDatabase database, BibEntry oldEntry) { + } + @Override public String getPresentationName() { if (cut) { diff --git a/src/main/java/org/jabref/gui/git/BibGitContext.java b/src/main/java/org/jabref/model/git/BibGitContext.java similarity index 86% rename from src/main/java/org/jabref/gui/git/BibGitContext.java rename to src/main/java/org/jabref/model/git/BibGitContext.java index ad64a04b05b..3a59c0cb991 100644 --- a/src/main/java/org/jabref/gui/git/BibGitContext.java +++ b/src/main/java/org/jabref/model/git/BibGitContext.java @@ -1,4 +1,4 @@ -package org.jabref.model.database; +package org.jabref.model.git; import java.nio.file.Files; import java.nio.file.Path; @@ -27,7 +27,7 @@ /** * Represents everything related to a BIB file. * - *

The entries are stored in BibDatabase, the other data in MetaData + *

The entries are stored in GitDatabase, the other data in MetaData * and the options relevant for this file in Defaults. *

*

@@ -49,26 +49,27 @@ public class BibGitContext { private DatabaseSynchronizer dbmsSynchronizer; private CoarseChangeFilter dbmsListener; private DatabaseLocation location; + private GitDatabase database; public BibGitContext() { - this(new BibDatabase()); + this(new GitDatabase()); } - public BibGitContext(BibDatabase database) { + public BibGitContext(GitDatabase database) { this(database, new MetaData()); } - public BibGitContext(BibDatabase database, MetaData metaData) { + public BibGitContext(GitDatabase database, MetaData metaData) { this.database = Objects.requireNonNull(database); this.metaData = Objects.requireNonNull(metaData); this.location = DatabaseLocation.LOCAL; } - public BibGitContext(BibDatabase database, MetaData metaData, Path path) { + public BibGitContext(GitDatabase database, MetaData metaData, Path path) { this(database, metaData, path, DatabaseLocation.LOCAL); } - public BibGitContext(BibDatabase database, MetaData metaData, Path path, DatabaseLocation location) { + public BibGitContext(GitDatabase database, MetaData metaData, Path path, DatabaseLocation location) { this(database, metaData); Objects.requireNonNull(location); this.path = path; @@ -78,14 +79,6 @@ public BibGitContext(BibDatabase database, MetaData metaData, Path path, Databas } } - public BibDatabaseMode getMode() { - return metaData.getMode().orElse(BibDatabaseMode.BIBLATEX); - } - - public void setMode(BibDatabaseMode bibDatabaseMode) { - metaData.setMode(bibDatabaseMode); - } - public void setDatabasePath(Path file) { this.path = file; } @@ -103,7 +96,7 @@ public void clearDatabasePath() { this.path = null; } - public BibDatabase getDatabase() { + public GitDatabase getDatabase() { return database; } @@ -115,10 +108,6 @@ public void setMetaData(MetaData metaData) { this.metaData = Objects.requireNonNull(metaData); } - public boolean isBiblatexMode() { - return getMode() == BibDatabaseMode.BIBLATEX; - } - /** * Returns whether this .bib file belongs to a {@link Study} */ @@ -217,8 +206,8 @@ public DatabaseLocation getLocation() { public void convertToSharedDatabase(DatabaseSynchronizer dmbsSynchronizer) { this.dbmsSynchronizer = dmbsSynchronizer; - this.dbmsListener = new CoarseChangeFilter(this); - dbmsListener.registerListener(dbmsSynchronizer); + //this.dbmsListener = new CoarseChangeFilter(this); + //dbmsListener.registerListener(dbmsSynchronizer); this.location = DatabaseLocation.SHARED; } @@ -250,15 +239,4 @@ public Path getFulltextIndexPath() { LOGGER.debug("Using index for unsaved database: {}", indexPath); return indexPath; } - - @Override - public String toString() { - return "BibGitContext{" + - "metaData=" + metaData + - ", mode=" + getMode() + - ", databasePath=" + getDatabasePath() + - ", biblatexMode=" + isBiblatexMode() + - ", fulltextIndexPath=" + getFulltextIndexPath() + - '}'; - } } diff --git a/src/main/java/org/jabref/model/git/GitDatabase.java b/src/main/java/org/jabref/model/git/GitDatabase.java new file mode 100644 index 00000000000..4cec5c33d7d --- /dev/null +++ b/src/main/java/org/jabref/model/git/GitDatabase.java @@ -0,0 +1,613 @@ +package org.jabref.model.git; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.model.database.event.EntriesAddedEvent; +import org.jabref.model.database.event.EntriesRemovedEvent; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibtexString; +import org.jabref.model.entry.Month; +import org.jabref.model.entry.event.EntriesEventSource; +import org.jabref.model.entry.event.EntryChangedEvent; +import org.jabref.model.entry.event.FieldChangedEvent; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.strings.StringUtil; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A bibliography database. This is the "bib" file (or the library stored in a shared SQL database) + */ +public class GitDatabase { + + private static final Logger LOGGER = LoggerFactory.getLogger(GitDatabase.class); + private static final Pattern RESOLVE_CONTENT_PATTERN = Pattern.compile(".*#[^#]+#.*"); + + /** + * State attributes + */ + private final ObservableList entries = FXCollections.synchronizedObservableList(FXCollections.observableArrayList(BibEntry::getObservables)); + private Map bibtexStrings = new ConcurrentHashMap<>(); + + private final EventBus eventBus = new EventBus(); + + private String preamble; + + // All file contents below the last entry in the file + private String epilog = ""; + + private String sharedDatabaseID; + + private String newLineSeparator = System.lineSeparator(); + + public GitDatabase(List entries, String newLineSeparator) { + this(entries); + this.newLineSeparator = newLineSeparator; + } + + public GitDatabase(List entries) { + this(); + insertEntries(entries); + } + + public GitDatabase() { + this.registerListener(new KeyChangeListener(this)); + } + + /** + * Returns a text with references resolved according to an optionally given database. + * + * @param toResolve maybenull The text to resolve. + * @param database maybenull The database to use for resolving the text. + * @return The resolved text or the original text if either the text or the database are null + * @deprecated use {@link GitDatabase#resolveForStrings(String)} + */ + @Deprecated + public static String getText(String toResolve, GitDatabase database) { + if ((toResolve != null) && (database != null)) { + return database.resolveForStrings(toResolve); + } + return toResolve; + } + + /** + * Returns the number of entries. + */ + public int getEntryCount() { + return entries.size(); + } + + /** + * Checks if the database contains entries. + */ + public boolean hasEntries() { + return !entries.isEmpty(); + } + + /** + * Returns the list of entries sorted by the given comparator. + */ + public List getEntriesSorted(Comparator comparator) { + List entriesSorted = new ArrayList<>(entries); + entriesSorted.sort(comparator); + return entriesSorted; + } + + /** + * Returns whether an entry with the given ID exists (-> entry_type + hashcode). + */ + public boolean containsEntryWithId(String id) { + return entries.stream().anyMatch(entry -> entry.getId().equals(id)); + } + + public ObservableList getEntries() { + return FXCollections.unmodifiableObservableList(entries); + } + + /** + * Returns a set of Strings, that contains all field names that are visible. This means that the fields + * are not internal fields. Internal fields are fields, that are starting with "_". + * + * @return set of fieldnames, that are visible + */ + public Set getAllVisibleFields() { + Set allFields = new TreeSet<>(Comparator.comparing(Field::getName)); + for (BibEntry e : getEntries()) { + allFields.addAll(e.getFields()); + } + return allFields.stream().filter(field -> !FieldFactory.isInternalField(field)) + .collect(Collectors.toSet()); + } + + /** + * Returns the entry with the given citation key. + */ + public synchronized Optional getEntryByCitationKey(String key) { + for (BibEntry entry : entries) { + if (key.equals(entry.getCitationKey().orElse(null))) { + return Optional.of(entry); + } + } + return Optional.empty(); + } + + /** + * Collects entries having the specified citation key and returns these entries as list. + * The order of the entries is the order they appear in the database. + * + * @return list of entries that contains the given key + */ + public synchronized List getEntriesByCitationKey(String key) { + List result = new ArrayList<>(); + + for (BibEntry entry : entries) { + entry.getCitationKey().ifPresent(entryKey -> { + if (key.equals(entryKey)) { + result.add(entry); + } + }); + } + return result; + } + + public synchronized void insertEntry(BibEntry entry) { + insertEntry(entry, EntriesEventSource.LOCAL); + } + + /** + * Inserts the entry. + * + * @param entry entry to insert + * @param eventSource source the event is sent from + */ + public synchronized void insertEntry(BibEntry entry, EntriesEventSource eventSource) { + insertEntries(Collections.singletonList(entry), eventSource); + } + + public synchronized void insertEntries(BibEntry... entries) { + insertEntries(Arrays.asList(entries), EntriesEventSource.LOCAL); + } + + public synchronized void insertEntries(List entries) { + insertEntries(entries, EntriesEventSource.LOCAL); + } + + public synchronized void insertEntries(List newEntries, EntriesEventSource eventSource) { + Objects.requireNonNull(newEntries); + for (BibEntry entry : newEntries) { + entry.registerListener(this); + } + if (newEntries.isEmpty()) { + eventBus.post(new EntriesAddedEvent(newEntries, eventSource)); + } else { + eventBus.post(new EntriesAddedEvent(newEntries, newEntries.get(0), eventSource)); + } + entries.addAll(newEntries); + } + + public synchronized void removeEntry(BibEntry bibEntry) { + removeEntries(Collections.singletonList(bibEntry)); + } + + public synchronized void removeEntry(BibEntry bibEntry, EntriesEventSource eventSource) { + removeEntries(Collections.singletonList(bibEntry), eventSource); + } + + /** + * Removes the given entries. + * The entries removed based on the id {@link BibEntry#getId()} + * + * @param toBeDeleted Entries to delete + */ + public synchronized void removeEntries(List toBeDeleted) { + removeEntries(toBeDeleted, EntriesEventSource.LOCAL); + } + + /** + * Removes the given entries. + * The entries are removed based on the id {@link BibEntry#getId()} + * + * @param toBeDeleted Entry to delete + * @param eventSource Source the event is sent from + */ + public synchronized void removeEntries(List toBeDeleted, EntriesEventSource eventSource) { + Objects.requireNonNull(toBeDeleted); + + List ids = new ArrayList<>(); + for (BibEntry entry : toBeDeleted) { + ids.add(entry.getId()); + } + boolean anyRemoved = entries.removeIf(entry -> ids.contains(entry.getId())); + if (anyRemoved) { + eventBus.post(new EntriesRemovedEvent(toBeDeleted, eventSource)); + } + } + + /** + * Returns the database's preamble. + * If the preamble text consists only of whitespace, then also an empty optional is returned. + */ + public synchronized Optional getPreamble() { + if (StringUtil.isBlank(preamble)) { + return Optional.empty(); + } else { + return Optional.of(preamble); + } + } + + /** + * Sets the database's preamble. + */ + public synchronized void setPreamble(String preamble) { + this.preamble = preamble; + } + + /** + * Removes the string with the given id. + */ + public void removeString(String id) { + bibtexStrings.remove(id); + } + + /** + * Returns a Set of keys to all BibtexString objects in the database. + * These are in no sorted order. + */ + public Set getStringKeySet() { + return bibtexStrings.keySet(); + } + + /** + * Returns a Collection of all BibtexString objects in the database. + * These are in no particular order. + */ + public Collection getStringValues() { + return bibtexStrings.values(); + } + + /** + * Returns the string with the given id. + */ + public BibtexString getString(String id) { + return bibtexStrings.get(id); + } + + /** + * Returns the string with the given name/label + */ + public Optional getStringByName(String name) { + return getStringValues().stream().filter(string -> string.getName().equals(name)).findFirst(); + } + + /** + * Returns the number of strings. + */ + public int getStringCount() { + return bibtexStrings.size(); + } + + /** + * Check if there are strings. + */ + public boolean hasNoStrings() { + return bibtexStrings.isEmpty(); + } + + /** + * Copies the preamble of another GitDatabase. + * + * @param database another GitDatabase + */ + public void copyPreamble(GitDatabase database) { + setPreamble(database.getPreamble().orElse("")); + } + + /** + * Returns true if a string with the given label already exists. + */ + public synchronized boolean hasStringByName(String label) { + return bibtexStrings.values().stream().anyMatch(value -> value.getName().equals(label)); + } + + /** + * Resolves any references to strings contained in this field content, + * if possible. + */ + public String resolveForStrings(String content) { + Objects.requireNonNull(content, "Content for resolveForStrings must not be null."); + return resolveContent(content, new HashSet<>(), new HashSet<>()); + } + + /** + * Get all strings used in the entries. + */ + public Collection getUsedStrings(Collection entries) { + List result = new ArrayList<>(); + Set allUsedIds = new HashSet<>(); + + // All entries + for (BibEntry entry : entries) { + for (String fieldContent : entry.getFieldValues()) { + resolveContent(fieldContent, new HashSet<>(), allUsedIds); + } + } + + // Preamble + if (preamble != null) { + resolveContent(preamble, new HashSet<>(), allUsedIds); + } + + for (String stringId : allUsedIds) { + result.add((BibtexString) bibtexStrings.get(stringId).clone()); + } + + return result; + } + + /** + * Take the given collection of BibEntry and resolve any string + * references. + * + * @param entriesToResolve A collection of BibtexEntries in which all strings of the form + * #xxx# will be resolved against the hash map of string + * references stored in the database. + * @param inPlace If inPlace is true then the given BibtexEntries will be modified, if false then copies of the BibtexEntries are made before resolving the strings. + * @return a list of bibtexentries, with all strings resolved. It is dependent on the value of inPlace whether copies are made or the given BibtexEntries are modified. + */ + public List resolveForStrings(Collection entriesToResolve, boolean inPlace) { + Objects.requireNonNull(entriesToResolve, "entries must not be null."); + + List results = new ArrayList<>(entriesToResolve.size()); + + for (BibEntry entry : entriesToResolve) { + results.add(this.resolveForStrings(entry, inPlace)); + } + return results; + } + + /** + * Take the given BibEntry and resolve any string references. + * + * @param entry A BibEntry in which all strings of the form #xxx# will be + * resolved against the hash map of string references stored in + * the database. + * @param inPlace If inPlace is true then the given BibEntry will be + * modified, if false then a copy is made using close made before + * resolving the strings. + * @return a BibEntry with all string references resolved. It is + * dependent on the value of inPlace whether a copy is made or the + * given BibtexEntries is modified. + */ + public BibEntry resolveForStrings(BibEntry entry, boolean inPlace) { + BibEntry resultingEntry; + if (inPlace) { + resultingEntry = entry; + } else { + resultingEntry = (BibEntry) entry.clone(); + } + + for (Map.Entry field : resultingEntry.getFieldMap().entrySet()) { + resultingEntry.setField(field.getKey(), this.resolveForStrings(field.getValue())); + } + return resultingEntry; + } + + /** + * If the label represents a string contained in this database, returns + * that string's content. Resolves references to other strings, taking + * care not to follow a circular reference pattern. + * If the string is undefined, returns null. + */ + private String resolveString(String label, Set usedIds, Set allUsedIds) { + Objects.requireNonNull(label); + Objects.requireNonNull(usedIds); + Objects.requireNonNull(allUsedIds); + + for (BibtexString string : bibtexStrings.values()) { + if (string.getName().equalsIgnoreCase(label)) { + // First check if this string label has been resolved + // earlier in this recursion. If so, we have a + // circular reference, and have to stop to avoid + // infinite recursion. + if (usedIds.contains(string.getId())) { + LOGGER.info("Stopped due to circular reference in strings: " + label); + return label; + } + // If not, log this string's ID now. + usedIds.add(string.getId()); + if (allUsedIds != null) { + allUsedIds.add(string.getId()); + } + + // Ok, we found the string. Now we must make sure we + // resolve any references to other strings in this one. + String result = string.getContent(); + result = resolveContent(result, usedIds, allUsedIds); + + // Finished with recursing this branch, so we remove our + // ID again: + usedIds.remove(string.getId()); + + return result; + } + } + + // If we get to this point, the string has obviously not been defined locally. + // Check if one of the standard BibTeX month strings has been used: + Optional month = Month.getMonthByShortName(label); + return month.map(Month::getFullName).orElse(null); + } + + private String resolveContent(String result, Set usedIds, Set allUsedIds) { + String res = result; + if (RESOLVE_CONTENT_PATTERN.matcher(res).matches()) { + StringBuilder newRes = new StringBuilder(); + int piv = 0; + int next; + while ((next = res.indexOf(FieldWriter.BIBTEX_STRING_START_END_SYMBOL, piv)) >= 0) { + // We found the next string ref. Append the text + // up to it. + if (next > 0) { + newRes.append(res, piv, next); + } + int stringEnd = res.indexOf(FieldWriter.BIBTEX_STRING_START_END_SYMBOL, next + 1); + if (stringEnd >= 0) { + // We found the boundaries of the string ref, + // now resolve that one. + String refLabel = res.substring(next + 1, stringEnd); + String resolved = resolveString(refLabel, usedIds, allUsedIds); + + if (resolved == null) { + // Could not resolve string. Display the # + // characters rather than removing them: + newRes.append(res, next, stringEnd + 1); + } else { + // The string was resolved, so we display its meaning only, + // stripping the # characters signifying the string label: + newRes.append(resolved); + } + piv = stringEnd + 1; + } else { + // We did not find the boundaries of the string ref. This + // makes it impossible to interpret it as a string label. + // So we should just append the rest of the text and finish. + newRes.append(res.substring(next)); + piv = res.length(); + break; + } + } + if (piv < (res.length() - 1)) { + newRes.append(res.substring(piv)); + } + res = newRes.toString(); + } + return res; + } + + public String getEpilog() { + return epilog; + } + + public void setEpilog(String epilog) { + this.epilog = epilog; + } + + /** + * Registers a listener object (subscriber) to the internal event bus. + * The following events are posted: + * + * - {@link EntriesAddedEvent} + * - {@link EntryChangedEvent} + * - {@link EntriesRemovedEvent} + * + * @param listener listener (subscriber) to add + */ + public void registerListener(Object listener) { + this.eventBus.register(listener); + } + + /** + * Unregisters an listener object. + * + * @param listener listener (subscriber) to remove + */ + public void unregisterListener(Object listener) { + try { + this.eventBus.unregister(listener); + } catch (IllegalArgumentException e) { + // occurs if the event source has not been registered, should not prevent shutdown + LOGGER.debug("Problem unregistering", e); + } + } + + @Subscribe + private void relayEntryChangeEvent(FieldChangedEvent event) { + eventBus.post(event); + } + + public Optional getReferencedEntry(BibEntry entry) { + return entry.getField(StandardField.CROSSREF).flatMap(this::getEntryByCitationKey); + } + + public Optional getSharedDatabaseID() { + return Optional.ofNullable(this.sharedDatabaseID); + } + + public void setSharedDatabaseID(String sharedDatabaseID) { + this.sharedDatabaseID = sharedDatabaseID; + } + + public boolean isShared() { + return getSharedDatabaseID().isPresent(); + } + + public void clearSharedDatabaseID() { + this.sharedDatabaseID = null; + } + + /** + * Generates and sets a random ID which is globally unique. + * + * @return The generated sharedDatabaseID + */ + public String generateSharedDatabaseID() { + this.sharedDatabaseID = new BigInteger(128, new SecureRandom()).toString(32); + return this.sharedDatabaseID; + } + + /** + * Returns the number of occurrences of the given citation key in this database. + */ + public long getNumberOfCitationKeyOccurrences(String key) { + return entries.stream() + .flatMap(entry -> entry.getCitationKey().stream()) + .filter(key::equals) + .count(); + } + + /** + * Checks if there is more than one occurrence of the citation key. + */ + public boolean isDuplicateCitationKeyExisting(String key) { + return getNumberOfCitationKeyOccurrences(key) > 1; + } + + /** + * Set the newline separator. + */ + public void setNewLineSeparator(String newLineSeparator) { + this.newLineSeparator = newLineSeparator; + } + + /** + * Returns the string used to indicate a linebreak + */ + public String getNewLineSeparator() { + return newLineSeparator; + } +} diff --git a/src/main/java/org/jabref/model/git/KeyChangeListener.java b/src/main/java/org/jabref/model/git/KeyChangeListener.java new file mode 100644 index 00000000000..77a2ccc5476 --- /dev/null +++ b/src/main/java/org/jabref/model/git/KeyChangeListener.java @@ -0,0 +1,80 @@ +package org.jabref.model.git; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.jabref.model.git.event.EntriesRemovedEvent; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.event.FieldChangedEvent; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.FieldProperty; +import org.jabref.model.entry.field.InternalField; + +import com.google.common.eventbus.Subscribe; + +public class KeyChangeListener { + + private final GitDatabase database; + + public KeyChangeListener(GitDatabase database) { + this.database = database; + } + + @Subscribe + public void listen(FieldChangedEvent event) { + if (event.getField().equals(InternalField.KEY_FIELD)) { + String newKey = event.getNewValue(); + String oldKey = event.getOldValue(); + updateEntryLinks(newKey, oldKey); + } + } + + @Subscribe + public void listen(EntriesRemovedEvent event) { + List entries = event.getBibEntries(); + for (BibEntry entry : entries) { + Optional citeKey = entry.getCitationKey(); + citeKey.ifPresent(oldkey -> updateEntryLinks(null, oldkey)); + } + } + + private void updateEntryLinks(String newKey, String oldKey) { + for (BibEntry entry : database.getEntries()) { + for (Field field : FieldFactory.getKeyFields()) { + entry.getField(field).ifPresent(fieldContent -> { + if (field.getProperties().contains(FieldProperty.SINGLE_ENTRY_LINK)) { + replaceSingleKeyInField(newKey, oldKey, entry, field, fieldContent); + } else { // MULTIPLE_ENTRY_LINK + replaceKeyInMultiplesKeyField(newKey, oldKey, entry, field, fieldContent); + } + }); + } + } + } + + private void replaceKeyInMultiplesKeyField(String newKey, String oldKey, BibEntry entry, Field field, String fieldContent) { + List keys = new ArrayList<>(Arrays.asList(fieldContent.split(","))); + int index = keys.indexOf(oldKey); + if (index != -1) { + if (newKey == null) { + keys.remove(index); + } else { + keys.set(index, newKey); + } + entry.setField(field, String.join(",", keys)); + } + } + + private void replaceSingleKeyInField(String newKey, String oldKey, BibEntry entry, Field field, String fieldContent) { + if (fieldContent.equals(oldKey)) { + if (newKey == null) { + entry.clearField(field); + } else { + entry.setField(field, newKey); + } + } + } +} diff --git a/src/main/java/org/jabref/model/git/KeyCollisionException.java b/src/main/java/org/jabref/model/git/KeyCollisionException.java new file mode 100644 index 00000000000..c00cb2cc730 --- /dev/null +++ b/src/main/java/org/jabref/model/git/KeyCollisionException.java @@ -0,0 +1,27 @@ +package org.jabref.model.git; + +public class KeyCollisionException extends RuntimeException { + + private String id; + + public KeyCollisionException() { + super(); + } + + public KeyCollisionException(String msg, String id) { + super(msg); + this.id = id; + } + + public KeyCollisionException(String msg, Throwable exception) { + super(msg, exception); + } + + public KeyCollisionException(Throwable exception) { + super(exception); + } + + public String getId() { + return id; + } +} diff --git a/src/main/java/org/jabref/model/git/event/EntriesRemovedEvent.java b/src/main/java/org/jabref/model/git/event/EntriesRemovedEvent.java new file mode 100644 index 00000000000..77b1075f271 --- /dev/null +++ b/src/main/java/org/jabref/model/git/event/EntriesRemovedEvent.java @@ -0,0 +1,30 @@ +package org.jabref.model.git.event; + +import java.util.List; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.event.EntriesEvent; +import org.jabref.model.entry.event.EntriesEventSource; + +/** + * EntriesRemovedEvent is fired when at least one BibEntry is being removed + * from the database. + */ + +public class EntriesRemovedEvent extends EntriesEvent { + + /** + * @param bibEntries List of BibEntry objects which are being removed. + */ + public EntriesRemovedEvent(List bibEntries) { + super(bibEntries); + } + + /** + * @param bibEntries List of BibEntry objects which are being removed. + * @param location Location affected by this event + */ + public EntriesRemovedEvent(List bibEntries, EntriesEventSource location) { + super(bibEntries, location); + } +} From 6b8700f7cf7a8402fd600fa11db7239465cef787 Mon Sep 17 00:00:00 2001 From: Leonardo Benicio <5027245+lbenicio@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:53:10 -0300 Subject: [PATCH 38/48] fix build error --- .../ExternalChangesResolverViewModel.java | 3 -- .../gui/git/GitChangeResolverDialog.java | 30 +++++------ .../org/jabref/gui/git/entryadd/EntryAdd.java | 2 - .../gui/git/entrychange/EntryChange.java | 5 +- .../entrychange/EntryChangeDetailsView.java | 51 +++++++++---------- .../gui/git/entrydelete/EntryDelete.java | 2 +- .../org/jabref/gui/preview/PreviewViewer.java | 23 ++++----- .../gui/undo/UndoableInsertEntries.java | 4 -- .../gui/undo/UndoableRemoveEntries.java | 5 -- 9 files changed, 48 insertions(+), 77 deletions(-) diff --git a/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java b/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java index 441523db3a4..b19ba3bde67 100644 --- a/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java +++ b/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java @@ -54,9 +54,6 @@ public ExternalChangesResolverViewModel(List externalChanges, Un canAskUserToResolveChange = Bindings.createBooleanBinding(() -> selectedChange.isNotNull().get() && selectedChange.get().getExternalChangeResolver().isPresent(), selectedChange); } - public ExternalChangesResolverViewModel(List changes2, UndoManager undoManager2) { - } - public ObservableList getVisibleChanges() { return visibleChanges; } diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java b/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java index 2373bf3197c..7a78246d093 100644 --- a/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java +++ b/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java @@ -6,30 +6,28 @@ import javax.swing.undo.UndoManager; - import javafx.beans.property.SimpleStringProperty; - import javafx.fxml.FXML; - import javafx.scene.control.Button; - import javafx.scene.control.SelectionMode; - import javafx.scene.control.TableColumn; - import javafx.scene.control.TableView; - import javafx.scene.layout.BorderPane; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; - import org.jabref.gui.git.ExternalChangesResolverViewModel; - import org.jabref.gui.preview.PreviewViewer; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.TaskExecutor; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.git.BibGitContext; -import org.jabref.preferences.PreferencesService; + import org.jabref.model.git.BibGitContext; + import org.jabref.preferences.PreferencesService; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; import com.airhacks.afterburner.views.ViewLoader; import com.tobiasdiez.easybind.EasyBind; + import jakarta.inject.Inject; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; + import javafx.beans.property.SimpleStringProperty; + import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.SelectionMode; + import javafx.scene.control.TableColumn; + import javafx.scene.control.TableView; + import javafx.scene.layout.BorderPane; public class GitChangeResolverDialog extends BaseDialog { private final static Logger LOGGER = LoggerFactory.getLogger(GitChangeResolverDialog.class); @@ -91,8 +89,8 @@ public GitChangeResolverDialog(List changes, BibGitContext git, Strin @FXML private void initialize() { - PreviewViewer previewViewer = new PreviewViewer(git, dialogService, preferencesService, stateManager, themeManager, taskExecutor); - GitChangeDetailsViewFactory gitChangeDetailsViewFactory = new GitChangeDetailsViewFactory(git, dialogService, stateManager, themeManager, preferencesService, entryTypesManager, previewViewer, taskExecutor); + //PreviewViewer previewViewer = new PreviewViewer(git, dialogService, preferencesService, stateManager, themeManager, taskExecutor); + //GitChangeDetailsViewFactory gitChangeDetailsViewFactory = new GitChangeDetailsViewFactory(git, dialogService, stateManager, themeManager, preferencesService, entryTypesManager, previewViewer, taskExecutor); viewModel = new ExternalChangesResolverViewModel(changes, undoManager); diff --git a/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java b/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java index f7f149dc82f..af85c843f6e 100644 --- a/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java +++ b/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java @@ -3,7 +3,6 @@ import org.jabref.gui.git.GitChange; import org.jabref.gui.git.GitChangeResolverFactory; import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableInsertEntries; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; import org.jabref.model.git.BibGitContext; @@ -24,7 +23,6 @@ public EntryAdd(BibEntry addedEntry, BibGitContext databaseContext, GitChangeRes @Override public void applyChange(NamedCompound undoEdit) { databaseContext.getDatabase().insertEntry(addedEntry); - undoEdit.addEdit(new UndoableInsertEntries(databaseContext.getDatabase(), addedEntry)); } public BibEntry getAddedEntry() { diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java index 845ff17cd6f..9f92f2c1381 100644 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java @@ -5,8 +5,6 @@ import org.jabref.gui.git.GitChange; import org.jabref.gui.git.GitChangeResolverFactory; import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableInsertEntries; -import org.jabref.gui.undo.UndoableRemoveEntries; import org.jabref.logic.l10n.Localization; import org.jabref.model.git.BibGitContext; import org.jabref.model.entry.BibEntry; @@ -42,8 +40,7 @@ public void applyChange(NamedCompound undoEdit) { this.gitContext.getDatabase().removeEntry(oldEntry); this.gitContext.getDatabase().insertEntry(newEntry); CompoundEdit changeEntryEdit = new CompoundEdit(); - changeEntryEdit.addEdit(new UndoableRemoveEntries(gitContext.getDatabase(), oldEntry)); - changeEntryEdit.addEdit(new UndoableInsertEntries(gitContext.getDatabase(), newEntry)); + changeEntryEdit.end(); undoEdit.addEdit(changeEntryEdit); diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java index cf2cf0c49ed..300430d9f95 100644 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java @@ -1,11 +1,5 @@ package org.jabref.gui.git.entrychange; -import javafx.geometry.Orientation; -import javafx.scene.control.Label; -import javafx.scene.control.SplitPane; -import javafx.scene.control.TabPane; -import javafx.scene.layout.VBox; - import java.util.List; import org.jabref.gui.DialogService; @@ -15,12 +9,13 @@ import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.l10n.Localization; -import org.jabref.model.git.BibGitContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.git.BibGitContext; import org.jabref.preferences.PreferencesService; -import com.tobiasdiez.easybind.EasyBind; +import javafx.scene.control.Label; +import javafx.scene.control.SplitPane; public final class EntryChangeDetailsView extends GitChangeDetailsView { private final PreviewWithSourceTab oldPreviewWithSourcesTab = new PreviewWithSourceTab(); @@ -42,33 +37,33 @@ public EntryChangeDetailsView(BibEntry oldEntry, onDisk.getStyleClass().add("lib-change-header"); // we need a copy here as we otherwise would set the same entry twice - PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, preferencesService, stateManager, themeManager, taskExecutor); + //PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, preferencesService, stateManager, themeManager, taskExecutor); - TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, preferencesService, entryTypesManager, previewClone, Localization.lang("Entry Preview")); - TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, preferencesService, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); + //TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, preferencesService, entryTypesManager, previewClone, Localization.lang("Entry Preview")); + //TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, preferencesService, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); - EasyBind.subscribe(oldEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { - newEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); - }); + //EasyBind.subscribe(oldEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { + // newEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); + //}); - EasyBind.subscribe(newEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { - if (oldEntryTabPane.getSelectionModel().getSelectedIndex() != selectedIndex.intValue()) { - oldEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); - } - }); + //EasyBind.subscribe(newEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { + // if (oldEntryTabPane.getSelectionModel().getSelectedIndex() != selectedIndex.intValue()) { + // oldEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); + // } + //}); - VBox containerOld = new VBox(inJabRef, oldEntryTabPane); - VBox containerNew = new VBox(onDisk, newEntryTabPane); + // VBox containerOld = new VBox(inJabRef, oldEntryTabPane); + // VBox containerNew = new VBox(onDisk, newEntryTabPane); - SplitPane split = new SplitPane(containerOld, containerNew); - split.setOrientation(Orientation.HORIZONTAL); + // SplitPane split = new SplitPane(containerOld, containerNew); + // split.setOrientation(Orientation.HORIZONTAL); - setLeftAnchor(split, 8d); - setTopAnchor(split, 8d); - setRightAnchor(split, 8d); - setBottomAnchor(split, 8d); + // setLeftAnchor(split, 8d); + // setTopAnchor(split, 8d); + // setRightAnchor(split, 8d); + // setBottomAnchor(split, 8d); - this.getChildren().add(split); + // this.getChildren().add(split); } private List getChildren() { diff --git a/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java b/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java index c3aae272da1..68a67680bd3 100644 --- a/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java +++ b/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java @@ -24,7 +24,7 @@ public EntryDelete(BibEntry deletedEntry, BibGitContext databaseContext, GitChan @Override public void applyChange(NamedCompound undoEdit) { this.databaseContext.getDatabase().removeEntry(deletedEntry); - undoEdit.addEdit(new UndoableRemoveEntries(this.databaseContext.getDatabase(), deletedEntry)); + // undoEdit.addEdit(new UndoableRemoveEntries(this.databaseContext.getDatabase(), deletedEntry)); } public BibEntry getDeletedEntry() { diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 3216659ce45..f592f287806 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -6,15 +6,6 @@ import java.util.Optional; import java.util.regex.Pattern; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.beans.value.ChangeListener; -import javafx.concurrent.Worker; -import javafx.print.PrinterJob; -import javafx.scene.control.ScrollPane; -import javafx.scene.input.ClipboardContent; -import javafx.scene.web.WebView; - import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; @@ -30,9 +21,7 @@ import org.jabref.logic.util.WebViewStore; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import org.jabref.model.git.BibGitContext; import org.jabref.preferences.PreferencesService; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -41,6 +30,15 @@ import org.w3c.dom.events.EventTarget; import org.w3c.dom.html.HTMLAnchorElement; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.concurrent.Worker; +import javafx.print.PrinterJob; +import javafx.scene.control.ScrollPane; +import javafx.scene.input.ClipboardContent; +import javafx.scene.web.WebView; + /** * Displays an BibEntry using the given layout format. */ @@ -199,9 +197,6 @@ public PreviewViewer(BibDatabaseContext database, themeManager.installCss(previewView.getEngine()); } - public PreviewViewer(BibGitContext git, DialogService dialogService2, PreferencesService preferencesService2, StateManager stateManager, ThemeManager themeManager, TaskExecutor taskExecutor2) { - } - private void highlightSearchPattern() { String callbackForUnmark = ""; if (searchHighlightPattern.isPresent()) { diff --git a/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java b/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java index a84f189057e..3af516b94c1 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java +++ b/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java @@ -6,7 +6,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; -import org.jabref.model.git.GitDatabase; import org.jabref.model.strings.StringUtil; import org.slf4j.Logger; @@ -38,9 +37,6 @@ public UndoableInsertEntries(BibDatabase database, List entries, boole this.paste = paste; } - public UndoableInsertEntries(GitDatabase database2, BibEntry addedEntry) { - } - @Override public String getPresentationName() { if (paste) { diff --git a/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java b/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java index 403ca2ae237..9d82e70ce27 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java +++ b/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java @@ -7,9 +7,7 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.EntriesEventSource; -import org.jabref.model.git.GitDatabase; import org.jabref.model.strings.StringUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,9 +39,6 @@ public UndoableRemoveEntries(BibDatabase base, List entries, boolean c this.cut = cut; } - public UndoableRemoveEntries(GitDatabase database, BibEntry oldEntry) { - } - @Override public String getPresentationName() { if (cut) { From cfdd3c84c514c08a5518ab345b22f3019059cf59 Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Fri, 24 Nov 2023 18:02:50 -0300 Subject: [PATCH 39/48] fix: git key store --- .../jabref/preferences/JabRefPreferences.java | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 968e047eb34..26232e85ca7 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -6,9 +6,12 @@ import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -31,6 +34,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.crypto.NoSuchPaddingException; + import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -121,6 +126,7 @@ import org.jabref.model.search.rules.SearchRules; import org.jabref.model.strings.StringUtil; +import com.github.javakeyring.BackendNotSupportedException; import com.github.javakeyring.Keyring; import com.github.javakeyring.PasswordAccessException; import com.tobiasdiez.easybind.EasyBind; @@ -1612,12 +1618,27 @@ private String getProxyPassword() { } catch (PasswordAccessException ex) { LOGGER.warn("JabRef uses proxy password from key store but no password is stored"); } catch (Exception ex) { - LOGGER.warn("JabRef could not open the key store"); + LOGGER.warn("JabRef could not open the git key store"); } } return (String) defaults.get(PROXY_PASSWORD); } + private String getGitPassword() { + try { + final Keyring keyring = Keyring.create(); + return new Password( + keyring.getPassword("org.jabref", "git"), + getInternalPreferences().getUserAndHost()) + .decrypt(); + } catch (PasswordAccessException ex) { + LOGGER.warn("JabRef uses git password from key store but no password is stored"); + } catch (Exception ex) { + LOGGER.warn("JabRef could not open the git key store"); + } + return (String) defaults.get(PROXY_PASSWORD); + } + private void setProxyPassword(String password) { if (getProxyPreferences().shouldPersistPassword()) { try (final Keyring keyring = Keyring.create()) { @@ -1635,6 +1656,22 @@ private void setProxyPassword(String password) { } } + private void setGitPassword(String password) { + try { + final Keyring keyring = Keyring.create(); + if (StringUtil.isBlank(password)) { + keyring.deletePassword("org.jabref", "git"); + } else { + keyring.setPassword("org.jabref", "git", new Password( + password.trim(), + getInternalPreferences().getUserAndHost()) + .encrypt()); + } + } catch (Exception ex) { + LOGGER.warn("Unable to open key store", ex); + } + } + @Override public SSLPreferences getSSLPreferences() { if (Objects.nonNull(sslPreferences)) { @@ -2830,13 +2867,13 @@ public GitPreferences getGitPreferences() { gitPreferences = new GitPreferences( get(GIT_USERNAME), - get(GIT_PASSWORD), + getGitPassword(), getBoolean(GIT_AUTOCOMMIT), getBoolean(GIT_AUTOSYNC) ); EasyBind.listen(gitPreferences.getUsernameProperty(), (obs, oldValue, newValue) -> put(GIT_USERNAME, newValue)); - EasyBind.listen(gitPreferences.getPasswordProperty(), (obs, oldValue, newValue) -> put(GIT_PASSWORD, newValue)); + EasyBind.listen(gitPreferences.getPasswordProperty(), (obs, oldValue, newValue) -> setGitPassword(newValue)); EasyBind.listen(gitPreferences.getAutoCommitProperty(), (obs, oldValue, newValue) -> putBoolean(GIT_AUTOCOMMIT, newValue)); EasyBind.listen(gitPreferences.getAutoSyncProperty(), (obs, oldValue, newValue) -> putBoolean(GIT_AUTOSYNC, newValue)); From f9abb2cd70c5fb75c8fd4539261049ffcca65a1d Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Fri, 24 Nov 2023 18:14:17 -0300 Subject: [PATCH 40/48] fix: git key store --- .../java/org/jabref/preferences/JabRefPreferences.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 26232e85ca7..692319b83fe 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -6,12 +6,9 @@ import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -34,8 +31,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.crypto.NoSuchPaddingException; - import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -126,7 +121,6 @@ import org.jabref.model.search.rules.SearchRules; import org.jabref.model.strings.StringUtil; -import com.github.javakeyring.BackendNotSupportedException; import com.github.javakeyring.Keyring; import com.github.javakeyring.PasswordAccessException; import com.tobiasdiez.easybind.EasyBind; @@ -1636,7 +1630,7 @@ private String getGitPassword() { } catch (Exception ex) { LOGGER.warn("JabRef could not open the git key store"); } - return (String) defaults.get(PROXY_PASSWORD); + return (String) defaults.get(GIT_PASSWORD); } private void setProxyPassword(String password) { From 75bce3c8e54f3ae2804e0bdf25e292091a922fbe Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Fri, 24 Nov 2023 19:45:20 -0300 Subject: [PATCH 41/48] Style --- src/main/java/org/jabref/logic/git/GitHandler.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index a3a96f3a625..eb243c6938e 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -7,6 +7,8 @@ import java.nio.file.Path; import java.util.Optional; +import org.jabref.logic.util.io.FileUtil; + import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; @@ -17,7 +19,6 @@ import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; -import org.jabref.logic.util.io.FileUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,7 +85,6 @@ public GitHandler(Path repositoryPath, GitPreferences gitPreferences) { this.repositoryPath = repositoryPath; this.repositoryPathAsFile = this.repositoryPath.toFile(); - if (!isGitRepository()) { try { Git.init() @@ -271,9 +271,9 @@ public void pushCommitsToRemoteRepository() throws IOException { git.push() .setTransportConfigCallback(transportConfigCallback) .call(); - }else if (this.gitPassword.isEmpty() || this.gitUsername.isEmpty()) { + } else if (this.gitPassword.isEmpty() || this.gitUsername.isEmpty()) { throw new IOException("No git credentials"); - } else { + } else { git.push() .setCredentialsProvider(this.credentialsProvider) .call(); @@ -332,8 +332,6 @@ public void pullOnCurrentBranch() throws IOException { } } } - - } /** From 596275ac344198d54def18d9fb84148b2559df3c Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Thu, 14 Dec 2023 15:43:30 -0300 Subject: [PATCH 42/48] conflict resolution with file monitor --- build.gradle | 19 +- .../ExternalChangesResolverViewModel.java | 1 - .../gui/exporter/SaveDatabaseAction.java | 110 ++-- .../git/ExternalChangesResolverViewModel.java | 55 -- .../java/org/jabref/gui/git/GitChange.java | 65 -- .../jabref/gui/git/GitChangeDetailsView.java | 4 - .../gui/git/GitChangeDetailsViewFactory.java | 17 - .../org/jabref/gui/git/GitChangeResolver.java | 16 - .../gui/git/GitChangeResolverDialog.java | 129 ---- .../gui/git/GitChangeResolverFactory.java | 29 - .../org/jabref/gui/git/entryadd/EntryAdd.java | 31 - .../gui/git/entrychange/EntryChange.java | 48 -- .../entrychange/EntryChangeDetailsView.java | 84 --- .../git/entrychange/EntryChangeResolver.java | 48 -- .../EntryWithPreviewAndSourceDetailsView.java | 39 -- .../git/entrychange/PreviewWithSourceTab.java | 82 --- .../gui/git/entrydelete/EntryDelete.java | 33 - .../jabref/gui/preferences/git/GitTab.java | 14 +- .../gui/preferences/git/GitTabViewModel.java | 30 +- .../org/jabref/gui/preview/PreviewViewer.java | 34 +- .../gui/undo/UndoableRemoveEntries.java | 1 + .../java/org/jabref/logic/git/GitHandler.java | 79 +-- .../org/jabref/logic/git/GitPreferences.java | 17 - .../org/jabref/model/git/BibGitContext.java | 242 ------- .../org/jabref/model/git/GitDatabase.java | 613 ------------------ .../jabref/model/git/KeyChangeListener.java | 80 --- .../model/git/KeyCollisionException.java | 27 - .../model/git/event/EntriesRemovedEvent.java | 30 - src/main/resources/l10n/JabRef_en.properties | 8 + 29 files changed, 157 insertions(+), 1828 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/git/ExternalChangesResolverViewModel.java delete mode 100644 src/main/java/org/jabref/gui/git/GitChange.java delete mode 100644 src/main/java/org/jabref/gui/git/GitChangeDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/GitChangeDetailsViewFactory.java delete mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolver.java delete mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java delete mode 100644 src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java delete mode 100644 src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChange.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java delete mode 100644 src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java delete mode 100644 src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java delete mode 100644 src/main/java/org/jabref/model/git/BibGitContext.java delete mode 100644 src/main/java/org/jabref/model/git/GitDatabase.java delete mode 100644 src/main/java/org/jabref/model/git/KeyChangeListener.java delete mode 100644 src/main/java/org/jabref/model/git/KeyCollisionException.java delete mode 100644 src/main/java/org/jabref/model/git/event/EntriesRemovedEvent.java diff --git a/build.gradle b/build.gradle index 14665afd801..ac7fe2774cf 100644 --- a/build.gradle +++ b/build.gradle @@ -144,15 +144,6 @@ dependencies { implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.7.0.202309050840-r' implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit.ssh.apache', version: '6.7.0.202309050840-r' - 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' - configurations.all { - exclude group: "commons-logging", module: "commons-logging" - } implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.15.3' @@ -258,6 +249,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' @@ -727,3 +724,7 @@ jmh { iterations = 10 fork = 2 } + +configurations.all { + exclude group: "commons-logging", module: "commons-logging" +} \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java b/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java index b19ba3bde67..0f3906594dc 100644 --- a/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java +++ b/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java @@ -14,7 +14,6 @@ import javafx.collections.ObservableList; import org.jabref.gui.AbstractViewModel; -import org.jabref.gui.git.GitChange; import org.jabref.gui.undo.NamedCompound; import org.jabref.logic.l10n.Localization; diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index b857fc34676..c4408b5c755 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -6,7 +6,6 @@ import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; @@ -19,16 +18,10 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Text; -import org.eclipse.jgit.api.errors.GitAPIException; - -import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.autosaveandbackup.AutosaveManager; import org.jabref.gui.autosaveandbackup.BackupManager; -import org.jabref.gui.git.GitCredentialsDialogView; -import org.jabref.gui.git.GitChange; -import org.jabref.gui.git.GitChangeResolverDialog; import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.gui.util.BackgroundTask; @@ -48,11 +41,15 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.ChangePropagation; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.git.BibGitContext; import org.jabref.model.metadata.SaveOrder; 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; @@ -247,17 +244,25 @@ 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")); - if (success) { - this.automaticGitUpdate(targetPath); - } return success; - } catch (SaveException | IOException | GitAPIException ex) { + } catch (SaveException ex) { LOGGER.error(String.format("A problem occurred when trying to save the file %s", targetPath), ex); dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); return false; @@ -332,47 +337,56 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset /** * Handle JabRef git integration action. This method is called when the user save the database. */ - public void automaticGitUpdate(Path filePath) throws IOException, GitAPIException { + public boolean automaticGitPull(Path filePath) { GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences()); - List changes = new ArrayList(); - BibGitContext bibGitContext = new BibGitContext(); - if (preferences.getGitPreferences().getAutoCommit()) { - String automaticCommitMsg = "Automatic update via JabRef"; - git.createCommitWithSingleFileOnCurrentBranch(filePath.getFileName().toString(), automaticCommitMsg); + 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) { + 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"); + } 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); + } catch (IOException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Git"), Localization.lang("Failed to open repository")); + LOGGER.info("Failed to open repository"); } - if (preferences.getGitPreferences().getAutoSync()) { + return true; + } + + public boolean automaticGitCommit(Path filePath) { + GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences()); + String automaticCommitMsg = "Automatic update via JabRef"; + if (preferences.getGitPreferences().getAutoCommit()) { try { - git.pullOnCurrentBranch(); - } catch (IOException ex1) { - if (ex1.getMessage().equals("No git credentials")) { - GitCredentialsDialogView gitCredentialsDialogView = new GitCredentialsDialogView(); - - gitCredentialsDialogView.showGitCredentialsDialog(); - - UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider( - gitCredentialsDialogView.getGitUsername(), - gitCredentialsDialogView.getGitPassword() - ); - - git.setCredentialsProvider(credentialsProvider); - try { - git.pullOnCurrentBranch(); - } catch (IOException ex2) { - if (ex2.getMessage().equals("HEAD is detached")) { - GitChangeResolverDialog GitChangeResolverDialog = new GitChangeResolverDialog(changes, bibGitContext, "Merge issues"); - } else { - throw new RuntimeException(ex2.getMessage()); - } - } - - } else if (ex1.getMessage().equals("HEAD is detached")) { - GitChangeResolverDialog GitChangeResolverDialog = new GitChangeResolverDialog(changes, bibGitContext, "Merge issues"); - } - else { - throw new IOException(ex1.getMessage()); - } + 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; } + } + return true; + } + + 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"); } } } diff --git a/src/main/java/org/jabref/gui/git/ExternalChangesResolverViewModel.java b/src/main/java/org/jabref/gui/git/ExternalChangesResolverViewModel.java deleted file mode 100644 index 43f44bd5f3c..00000000000 --- a/src/main/java/org/jabref/gui/git/ExternalChangesResolverViewModel.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.jabref.gui.git; - -import java.util.List; - -import javax.swing.undo.UndoManager; - -import javafx.beans.binding.BooleanExpression; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.beans.value.ObservableValue; -import javafx.collections.ObservableList; - -public class ExternalChangesResolverViewModel { - private StringProperty selectedChange; - - public ExternalChangesResolverViewModel(List changes, UndoManager undoManager) { - this.selectedChange = new SimpleStringProperty(); - } - - public boolean areAllChangesResolved() { - return false; - } - - public ObservableList getVisibleChanges() { - return null; - } - - public BooleanExpression canAskUserToResolveChangeProperty() { - return null; - } - - public StringProperty selectedChangeProperty() { - return this.selectedChange; - } - - public void denyChange() { - } - - public void acceptChange() { - } - - public ObservableValue getSelectedChange() { - return this.selectedChange; - } - - public ObservableValue areAllChangesResolvedProperty() { - return null; - } - - public void applyChanges() { - } - - public void acceptMergedChange() {} - -} diff --git a/src/main/java/org/jabref/gui/git/GitChange.java b/src/main/java/org/jabref/gui/git/GitChange.java deleted file mode 100644 index 61ebd791f9e..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChange.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.jabref.gui.git; - - 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.git.entryadd.EntryAdd; - import org.jabref.gui.git.entrychange.EntryChange; - import org.jabref.gui.git.entrydelete.EntryDelete; - - import org.jabref.gui.undo.NamedCompound; - import org.jabref.gui.util.OptionalObjectProperty; - import org.jabref.model.git.BibGitContext; - - public sealed abstract class GitChange permits EntryAdd, EntryChange, EntryDelete { - protected final BibGitContext BibGitContext; - protected final OptionalObjectProperty externalChangeResolver = OptionalObjectProperty.empty(); - private final BooleanProperty accepted = new SimpleBooleanProperty(); - private final StringProperty name = new SimpleStringProperty(); - - protected GitChange(BibGitContext BibGitContext, GitChangeResolverFactory gitChangeResolverFactory) { - this.BibGitContext = BibGitContext; - setChangeName("Unnamed Change!"); - - if (gitChangeResolverFactory != null) { - externalChangeResolver.set(gitChangeResolverFactory.create(this)); - } - } - - public boolean isAccepted() { - return accepted.get(); - } - - public BooleanProperty acceptedProperty() { - return accepted; - } - - public void setAccepted(boolean accepted) { - this.accepted.set(accepted); - } - - /** - * Convenience method for accepting changes - * */ - public void accept() { - setAccepted(true); - } - - public String getName() { - return name.get(); - } - - protected void setChangeName(String changeName) { - name.set(changeName); - } - - public Optional getExternalChangeResolver() { - return externalChangeResolver.get(); - } - - public abstract void applyChange(NamedCompound undoEdit); - } \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java b/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java deleted file mode 100644 index 74729be8c6b..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChangeDetailsView.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.jabref.gui.git; - -public class GitChangeDetailsView { -} diff --git a/src/main/java/org/jabref/gui/git/GitChangeDetailsViewFactory.java b/src/main/java/org/jabref/gui/git/GitChangeDetailsViewFactory.java deleted file mode 100644 index 3b56801721b..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChangeDetailsViewFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.jabref.gui.git; - -import org.jabref.gui.DialogService; -import org.jabref.gui.StateManager; -import org.jabref.gui.preview.PreviewViewer; -import org.jabref.gui.theme.ThemeManager; -import org.jabref.gui.util.TaskExecutor; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.git.BibGitContext; -import org.jabref.preferences.PreferencesService; - -public class GitChangeDetailsViewFactory { - - public GitChangeDetailsViewFactory(BibGitContext git, DialogService dialogService, StateManager stateManager, ThemeManager themeManager, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, TaskExecutor taskExecutor) { - } - -} diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolver.java b/src/main/java/org/jabref/gui/git/GitChangeResolver.java deleted file mode 100644 index e6e366cf492..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChangeResolver.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jabref.gui.git; - -import java.util.Optional; - -import org.jabref.gui.DialogService; -import org.jabref.gui.git.entrychange.EntryChangeResolver; - -public sealed abstract class GitChangeResolver permits EntryChangeResolver { - protected final DialogService dialogService; - - protected GitChangeResolver(DialogService dialogService) { - this.dialogService = dialogService; - } - - public abstract Optional askUserToResolveChange(); -} diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java b/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java deleted file mode 100644 index 7a78246d093..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChangeResolverDialog.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.jabref.gui.git; - - import java.util.HashMap; - import java.util.List; - import java.util.Map; - - import javax.swing.undo.UndoManager; - - import org.jabref.gui.DialogService; - import org.jabref.gui.StateManager; - import org.jabref.gui.theme.ThemeManager; - import org.jabref.gui.util.BaseDialog; - import org.jabref.gui.util.TaskExecutor; - import org.jabref.model.entry.BibEntryTypesManager; - import org.jabref.model.git.BibGitContext; - import org.jabref.preferences.PreferencesService; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; - - import com.airhacks.afterburner.views.ViewLoader; - import com.tobiasdiez.easybind.EasyBind; - - import jakarta.inject.Inject; - import javafx.beans.property.SimpleStringProperty; - import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.SelectionMode; - import javafx.scene.control.TableColumn; - import javafx.scene.control.TableView; - import javafx.scene.layout.BorderPane; - - public class GitChangeResolverDialog extends BaseDialog { - private final static Logger LOGGER = LoggerFactory.getLogger(GitChangeResolverDialog.class); - /** - * Reconstructing the details view to preview an {@link GitChange} every time it's selected is a heavy operation. - * It is also useless because changes are static and if the change data is static then the view doesn't have to change - * either. This cache is used to ensure that we only create the detail view instance once for each {@link GitChange}. - */ - private final Map DETAILS_VIEW_CACHE = new HashMap<>(); - - @FXML - private TableView changesTableView; - @FXML - private TableColumn changeName; - @FXML - private Button askUserToResolveChangeButton; - @FXML - private BorderPane changeInfoPane; - - private final List changes; - private final BibGitContext git; - - private ExternalChangesResolverViewModel viewModel; - - @Inject private UndoManager undoManager; - @Inject private StateManager stateManager; - @Inject private DialogService dialogService; - @Inject private PreferencesService preferencesService; - @Inject private ThemeManager themeManager; - @Inject private BibEntryTypesManager entryTypesManager; - @Inject private TaskExecutor taskExecutor; - - /** - * A dialog going through given changes, which are diffs to the provided git. - * Each accepted change is written to the provided git. - * - * @param changes The list of changes - * @param git The git to apply the changes to - */ - public GitChangeResolverDialog(List changes, BibGitContext git, String dialogTitle) { - this.changes = changes; - this.git = git; - - this.setTitle(dialogTitle); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); - - this.setResultConverter(button -> { - if (viewModel.areAllChangesResolved()) { - LOGGER.info("External changes are resolved successfully"); - return true; - } else { - LOGGER.info("External changes aren't resolved"); - return false; - } - }); - } - - @FXML - private void initialize() { - //PreviewViewer previewViewer = new PreviewViewer(git, dialogService, preferencesService, stateManager, themeManager, taskExecutor); - //GitChangeDetailsViewFactory gitChangeDetailsViewFactory = new GitChangeDetailsViewFactory(git, dialogService, stateManager, themeManager, preferencesService, entryTypesManager, previewViewer, taskExecutor); - - viewModel = new ExternalChangesResolverViewModel(changes, undoManager); - - changeName.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getName())); - askUserToResolveChangeButton.disableProperty().bind(viewModel.canAskUserToResolveChangeProperty().not()); - - changesTableView.setItems(viewModel.getVisibleChanges()); - // Think twice before setting this to MULTIPLE... - changesTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); - changesTableView.getSelectionModel().selectFirst(); - - - EasyBind.subscribe(viewModel.areAllChangesResolvedProperty(), isResolved -> { - if ((boolean) isResolved) { - viewModel.applyChanges(); - close(); - } - }); - } - - @FXML - public void denyChanges() { - viewModel.denyChange(); - } - - @FXML - public void acceptChanges() { - viewModel.acceptChange(); - } - - @FXML - public void askUserToResolveChange() { - viewModel.getSelectedChange().flatMap(null) - .flatMap(null); - } - } \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java b/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java deleted file mode 100644 index 4963f9ffa0a..00000000000 --- a/src/main/java/org/jabref/gui/git/GitChangeResolverFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.jabref.gui.git; - -import java.util.Optional; - -import org.jabref.gui.DialogService; -import org.jabref.gui.git.entrychange.EntryChange; -import org.jabref.gui.git.entrychange.EntryChangeResolver; -import org.jabref.model.git.BibGitContext; -import org.jabref.preferences.PreferencesService; - -public class GitChangeResolverFactory { - private final DialogService dialogService; - private final BibGitContext gitContext; - private final PreferencesService preferencesService; - - public GitChangeResolverFactory(DialogService dialogService, BibGitContext gitContext, PreferencesService preferencesService) { - this.dialogService = dialogService; - this.gitContext = gitContext; - this.preferencesService = preferencesService; - } - - public Optional create(GitChange change) { - if (change instanceof EntryChange entryChange) { - return Optional.of(new EntryChangeResolver(entryChange, dialogService, gitContext, preferencesService)); - } - - return Optional.empty(); - } -} \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java b/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java deleted file mode 100644 index af85c843f6e..00000000000 --- a/src/main/java/org/jabref/gui/git/entryadd/EntryAdd.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.gui.git.entryadd; - -import org.jabref.gui.git.GitChange; -import org.jabref.gui.git.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.git.BibGitContext; - -public final class EntryAdd extends GitChange { - private final BibEntry addedEntry; - private final BibGitContext databaseContext; - - public EntryAdd(BibEntry addedEntry, BibGitContext databaseContext, GitChangeResolverFactory GitChangeResolverFactory) { - super(databaseContext, GitChangeResolverFactory); - this.addedEntry = addedEntry; - setChangeName(addedEntry.getCitationKey() - .map(key -> Localization.lang("Added entry '%0'", key)) - .orElse(Localization.lang("Added entry"))); - this.databaseContext = databaseContext; - } - - @Override - public void applyChange(NamedCompound undoEdit) { - databaseContext.getDatabase().insertEntry(addedEntry); - } - - public BibEntry getAddedEntry() { - return addedEntry; - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java deleted file mode 100644 index 9f92f2c1381..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryChange.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.jabref.gui.git.entrychange; - -import javax.swing.undo.CompoundEdit; - -import org.jabref.gui.git.GitChange; -import org.jabref.gui.git.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.git.BibGitContext; -import org.jabref.model.entry.BibEntry; - -public final class EntryChange extends GitChange { - private final BibEntry oldEntry; - private final BibEntry newEntry; - private BibGitContext gitContext; - - public EntryChange(BibEntry oldEntry, BibEntry newEntry, BibGitContext gitContext, GitChangeResolverFactory gitChangeResolverFactory) { - super(gitContext, gitChangeResolverFactory); - this.oldEntry = oldEntry; - this.newEntry = newEntry; - this.gitContext = gitContext; - setChangeName(oldEntry.getCitationKey().map(key -> Localization.lang("Modified entry '%0'", key)) - .orElse(Localization.lang("Modified entry"))); - } - - public EntryChange(BibEntry oldEntry, BibEntry newEntry, BibGitContext gitContext) { - this(oldEntry, newEntry, gitContext, null); - } - - public BibEntry getOldEntry() { - return oldEntry; - } - - public BibEntry getNewEntry() { - return newEntry; - } - - @Override - public void applyChange(NamedCompound undoEdit) { - this.gitContext.getDatabase().removeEntry(oldEntry); - this.gitContext.getDatabase().insertEntry(newEntry); - CompoundEdit changeEntryEdit = new CompoundEdit(); - - changeEntryEdit.end(); - - undoEdit.addEdit(changeEntryEdit); - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java deleted file mode 100644 index 300430d9f95..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeDetailsView.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.jabref.gui.git.entrychange; - -import java.util.List; - -import org.jabref.gui.DialogService; -import org.jabref.gui.StateManager; -import org.jabref.gui.git.GitChangeDetailsView; -import org.jabref.gui.preview.PreviewViewer; -import org.jabref.gui.theme.ThemeManager; -import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.git.BibGitContext; -import org.jabref.preferences.PreferencesService; - -import javafx.scene.control.Label; -import javafx.scene.control.SplitPane; - -public final class EntryChangeDetailsView extends GitChangeDetailsView { - private final PreviewWithSourceTab oldPreviewWithSourcesTab = new PreviewWithSourceTab(); - private final PreviewWithSourceTab newPreviewWithSourcesTab = new PreviewWithSourceTab(); - - public EntryChangeDetailsView(BibEntry oldEntry, - BibEntry newEntry, - BibGitContext databaseContext, - DialogService dialogService, - StateManager stateManager, - ThemeManager themeManager, - PreferencesService preferencesService, - BibEntryTypesManager entryTypesManager, - PreviewViewer previewViewer, - TaskExecutor taskExecutor) { - Label inJabRef = new Label(Localization.lang("In JabRef")); - inJabRef.getStyleClass().add("lib-change-header"); - Label onDisk = new Label(Localization.lang("On disk")); - onDisk.getStyleClass().add("lib-change-header"); - - // we need a copy here as we otherwise would set the same entry twice - //PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, preferencesService, stateManager, themeManager, taskExecutor); - - //TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, preferencesService, entryTypesManager, previewClone, Localization.lang("Entry Preview")); - //TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, preferencesService, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); - - //EasyBind.subscribe(oldEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { - // newEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); - //}); - - //EasyBind.subscribe(newEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { - // if (oldEntryTabPane.getSelectionModel().getSelectedIndex() != selectedIndex.intValue()) { - // oldEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); - // } - //}); - - // VBox containerOld = new VBox(inJabRef, oldEntryTabPane); - // VBox containerNew = new VBox(onDisk, newEntryTabPane); - - // SplitPane split = new SplitPane(containerOld, containerNew); - // split.setOrientation(Orientation.HORIZONTAL); - - // setLeftAnchor(split, 8d); - // setTopAnchor(split, 8d); - // setRightAnchor(split, 8d); - // setBottomAnchor(split, 8d); - - // this.getChildren().add(split); - } - - private List getChildren() { - return null; - } - - private void setBottomAnchor(SplitPane split, double d) { - } - - private void setRightAnchor(SplitPane split, double d) { - } - - private void setTopAnchor(SplitPane split, double d) { - } - - private void setLeftAnchor(SplitPane split, double d) { - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java b/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java deleted file mode 100644 index 4c5b06737ef..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryChangeResolver.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.jabref.gui.git.entrychange; - -import java.util.Optional; - -import org.jabref.gui.DialogService; -import org.jabref.gui.git.GitChange; -import org.jabref.gui.git.GitChangeResolver; -import org.jabref.gui.mergeentries.EntriesMergeResult; -import org.jabref.gui.mergeentries.MergeEntriesDialog; -import org.jabref.gui.mergeentries.newmergedialog.ShowDiffConfig; -import org.jabref.gui.mergeentries.newmergedialog.diffhighlighter.DiffHighlighter.BasicDiffMethod; -import org.jabref.gui.mergeentries.newmergedialog.toolbar.ThreeWayMergeToolbar; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.git.BibGitContext; -import org.jabref.preferences.PreferencesService; - -public final class EntryChangeResolver extends GitChangeResolver { - private final EntryChange entryChange; - private final BibGitContext databaseContext; - - private final PreferencesService preferencesService; - - public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, BibGitContext databaseContext, PreferencesService preferencesService) { - super(dialogService); - this.entryChange = entryChange; - this.databaseContext = databaseContext; - this.preferencesService = preferencesService; - } - - @Override - public Optional askUserToResolveChange() { - MergeEntriesDialog mergeEntriesDialog = new MergeEntriesDialog(entryChange.getOldEntry(), entryChange.getNewEntry(), preferencesService); - mergeEntriesDialog.setLeftHeaderText(Localization.lang("In JabRef")); - mergeEntriesDialog.setRightHeaderText(Localization.lang("On disk")); - mergeEntriesDialog.configureDiff(new ShowDiffConfig(ThreeWayMergeToolbar.DiffView.SPLIT, BasicDiffMethod.WORDS)); - - return dialogService.showCustomDialogAndWait(mergeEntriesDialog) - .map(this::mapMergeResultToExternalChange); - } - - private EntryChange mapMergeResultToExternalChange(EntriesMergeResult entriesMergeResult) { - return new EntryChange( - entryChange.getOldEntry(), - entriesMergeResult.mergedEntry(), - databaseContext - ); - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java b/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java deleted file mode 100644 index a614a14802a..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/EntryWithPreviewAndSourceDetailsView.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.jabref.gui.git.entrychange; - -import javafx.scene.control.TabPane; - -import org.jabref.gui.git.GitChangeDetailsView; -import org.jabref.gui.preview.PreviewViewer; -import org.jabref.model.git.BibGitContext; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.preferences.PreferencesService; - -public final class EntryWithPreviewAndSourceDetailsView extends GitChangeDetailsView { - - private final PreviewWithSourceTab previewWithSourceTab = new PreviewWithSourceTab(); - - public EntryWithPreviewAndSourceDetailsView(BibEntry entry, BibGitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { - TabPane tabPanePreviewCode = previewWithSourceTab.getPreviewWithSourceTab(entry, bibGitContext, preferencesService, entryTypesManager, previewViewer); - setLeftAnchor(tabPanePreviewCode, 8d); - setTopAnchor(tabPanePreviewCode, 8d); - setRightAnchor(tabPanePreviewCode, 8d); - setBottomAnchor(tabPanePreviewCode, 8d); - } - - private void setLeftAnchor(TabPane tabPanePreviewCode, double d) { - } - - private void setBottomAnchor(TabPane tabPanePreviewCode, double d) { - } - - private Object getChildren() { - return null; - } - - private void setTopAnchor(TabPane tabPanePreviewCode, double d) { - } - - private void setRightAnchor(TabPane tabPanePreviewCode, double d) { - } -} diff --git a/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java b/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java deleted file mode 100644 index e7b85fb200e..00000000000 --- a/src/main/java/org/jabref/gui/git/entrychange/PreviewWithSourceTab.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.jabref.gui.git.entrychange; - -import java.io.IOException; -import java.io.StringWriter; - -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; - -import org.jabref.gui.preview.PreviewViewer; -import org.jabref.logic.bibtex.BibEntryWriter; -import org.jabref.logic.bibtex.FieldPreferences; -import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.logic.exporter.BibWriter; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.OS; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.database.BibDatabaseMode; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.git.BibGitContext; -import org.jabref.model.strings.StringUtil; -import org.jabref.preferences.PreferencesService; - -import org.fxmisc.richtext.CodeArea; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PreviewWithSourceTab { - - private static final Logger LOGGER = LoggerFactory.getLogger(PreviewWithSourceTab.class); - - public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDatabaseContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { - return getPreviewWithSourceTab(entry, bibDatabaseContext, preferencesService, entryTypesManager, previewViewer, ""); - } - - public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDatabaseContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, String label) { - previewViewer.setLayout(preferencesService.getPreviewPreferences().getSelectedPreviewLayout()); - previewViewer.setEntry(entry); - - CodeArea codeArea = new CodeArea(); - codeArea.setId("bibtexcodearea"); - codeArea.setWrapText(true); - codeArea.setDisable(true); - - TabPane tabPanePreviewCode = new TabPane(); - Tab previewTab = new Tab(); - previewTab.setContent(previewViewer); - - if (StringUtil.isNullOrEmpty(label)) { - previewTab.setText(Localization.lang("Entry preview")); - } else { - previewTab.setText(label); - } - - try { - codeArea.appendText(getSourceString(entry, bibDatabaseContext.getMode(), preferencesService.getFieldPreferences(), entryTypesManager)); - } catch (IOException e) { - LOGGER.error("Error getting Bibtex: {}", entry); - } - codeArea.setEditable(false); - Tab codeTab = new Tab(Localization.lang("%0 source", bibDatabaseContext.getMode().getFormattedName()), codeArea); - - tabPanePreviewCode.getTabs().addAll(previewTab, codeTab); - return tabPanePreviewCode; - } - - private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences, BibEntryTypesManager entryTypesManager) throws IOException { - StringWriter writer = new StringWriter(); - BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); - FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); - new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); - return writer.toString(); - } - - public TabPane getPreviewWithSourceTab(BibEntry oldEntry, BibGitContext databaseContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewClone, String lang) { - return null; - } - - public TabPane getPreviewWithSourceTab(BibEntry entry, BibGitContext bibGitContext, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { - return null; - } -} diff --git a/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java b/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java deleted file mode 100644 index 68a67680bd3..00000000000 --- a/src/main/java/org/jabref/gui/git/entrydelete/EntryDelete.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.jabref.gui.git.entrydelete; - -import org.jabref.gui.git.GitChange; -import org.jabref.gui.git.GitChangeResolverFactory; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableRemoveEntries; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.git.BibGitContext; -import org.jabref.model.entry.BibEntry; - -public final class EntryDelete extends GitChange { - private final BibEntry deletedEntry; - private final BibGitContext databaseContext; - - public EntryDelete(BibEntry deletedEntry, BibGitContext databaseContext, GitChangeResolverFactory databaseChangeResolverFactory) { - super(databaseContext, databaseChangeResolverFactory); - this.deletedEntry = deletedEntry; - setChangeName(deletedEntry.getCitationKey() - .map(key -> Localization.lang("Deleted entry '%0'", key)) - .orElse(Localization.lang("Deleted entry"))); - this.databaseContext = databaseContext; - } - - @Override - public void applyChange(NamedCompound undoEdit) { - this.databaseContext.getDatabase().removeEntry(deletedEntry); - // undoEdit.addEdit(new UndoableRemoveEntries(this.databaseContext.getDatabase(), deletedEntry)); - } - - public BibEntry getDeletedEntry() { - return deletedEntry; - } -} diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTab.java b/src/main/java/org/jabref/gui/preferences/git/GitTab.java index 15c97dd674e..cdf9965b3e5 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTab.java +++ b/src/main/java/org/jabref/gui/preferences/git/GitTab.java @@ -35,17 +35,7 @@ private void initialize() { username.textProperty().bindBidirectional(viewModel.getUsernameProperty()); password.textProperty().bindBidirectional(viewModel.getPasswordProperty()); - autoCommit.selectedProperty().addListener( - (observable, oldValue, newValue) -> { - if (newValue) { - autoCommit.selectedProperty().setValue(false); - } - }); - autoSync.selectedProperty().addListener( - (observable, oldValue, newValue) -> { - if (newValue) { - autoSync.selectedProperty().setValue(false); - } - }); + autoCommit.selectedProperty().bindBidirectional(viewModel.getAutoCommitProperty()); + autoSync.selectedProperty().bindBidirectional(viewModel.getAutoSyncProperty()); } } diff --git a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java index 4de13e5918d..3a97e522ae4 100644 --- a/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/git/GitTabViewModel.java @@ -2,22 +2,18 @@ import java.util.List; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.StringProperty; -import org.jabref.logic.git.GitPreferences; import org.jabref.gui.preferences.PreferenceTabViewModel; +import org.jabref.logic.git.GitPreferences; public class GitTabViewModel implements PreferenceTabViewModel { - - private StringProperty username = new SimpleStringProperty(); - private StringProperty password = new SimpleStringProperty(); - private BooleanProperty autoCommit = new SimpleBooleanProperty(); - - private BooleanProperty autoSync = new SimpleBooleanProperty(); - private GitPreferences gitPreferences; + 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; @@ -31,8 +27,8 @@ public GitTabViewModel(GitPreferences gitPreferences) { public void setValues() { this.username.setValue(this.gitPreferences.getUsername()); this.password.setValue(this.gitPreferences.getPassword()); - this.autoCommit.setValue(this.gitPreferences.getAutoCommit()); - this.autoCommit.setValue(this.gitPreferences.getAutoSync()); + this.autoCommit.setValue(this.gitPreferences.getAutoCommit() || this.gitPreferences.getAutoSync()); + this.autoSync.setValue(this.gitPreferences.getAutoSync()); } @Override @@ -65,18 +61,10 @@ public StringProperty getPasswordProperty() { return this.password; } - public Boolean getAutoCommit() { - return this.autoCommit.get(); - } - public BooleanProperty getAutoCommitProperty() { return this.autoCommit; } - public Boolean getAutoSync() { - return this.autoSync.get(); - } - public BooleanProperty getAutoSyncProperty() { return this.autoSync; } diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index f592f287806..ebe6f5c08c4 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -1,11 +1,22 @@ package org.jabref.gui.preview; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.MalformedURLException; import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.concurrent.Worker; +import javafx.print.PrinterJob; +import javafx.scene.control.ScrollPane; +import javafx.scene.input.ClipboardContent; +import javafx.scene.web.WebView; + import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; @@ -22,6 +33,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.preferences.PreferencesService; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -30,15 +42,6 @@ import org.w3c.dom.events.EventTarget; import org.w3c.dom.html.HTMLAnchorElement; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.beans.value.ChangeListener; -import javafx.concurrent.Worker; -import javafx.print.PrinterJob; -import javafx.scene.control.ScrollPane; -import javafx.scene.input.ClipboardContent; -import javafx.scene.web.WebView; - /** * Displays an BibEntry using the given layout format. */ @@ -256,13 +259,22 @@ private void update() { Number.serialExportNumber = 1; // Set entry number in case that is included in the preview layout. + final BibEntry theEntry = entry.get(); BackgroundTask - .wrap(() -> layout.generatePreview(entry.get(), database)) + .wrap(() -> layout.generatePreview(theEntry, database)) .onRunning(() -> setPreviewText("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + ": " + layout.getDisplayName() + " ..." + "")) .onSuccess(this::setPreviewText) .onFailure(exception -> { LOGGER.error("Error while generating citation style", exception); - setPreviewText(Localization.lang("Error while generating citation style")); + + // Convert stack trace to a string + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + exception.printStackTrace(printWriter); + String stackTraceString = stringWriter.toString(); + + // Set the preview text with the localized error message and the stack trace + setPreviewText(Localization.lang("Error while generating citation style") + "\n\n" + exception.getLocalizedMessage() + "\n\nBibTeX (internal):\n" + theEntry + "\n\nStack Trace:\n" + stackTraceString); }) .executeWith(taskExecutor); } diff --git a/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java b/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java index 9d82e70ce27..65b6ae48331 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java +++ b/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java @@ -8,6 +8,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.strings.StringUtil; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index eb243c6938e..2a272509eeb 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -10,10 +10,12 @@ import org.jabref.logic.util.io.FileUtil; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.TransportConfigCallback; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.merge.MergeStrategy; @@ -226,7 +228,6 @@ public boolean createCommitWithSingleFileOnCurrentBranch(String filename, String .setMessage(commitMessage) .call(); } - return commitCreated; } @@ -292,45 +293,22 @@ public void pushCommitsToRemoteRepository() throws IOException { * Pulls all commits made to the branch that is tracked by the currently checked out branch. * If pulling to remote fails, it fails silently. */ - public void pullOnCurrentBranch() throws IOException { + public void pullOnCurrentBranch() throws IOException, GitAPIException { Git git = Git.open(this.repositoryPathAsFile); - String remoteURL = git.getRepository().getConfig().getString("remote", "origin", "url"); boolean isSshRemoteRepository = remoteURL != null && remoteURL.contains("git@"); - git.verifySignature(); - if (isSshRemoteRepository) { - try { - TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); - git.pull() - .setTransportConfigCallback(transportConfigCallback) - .call(); - } catch (GitAPIException e) { - if (e.getMessage().equals("origin: not found")) { - LOGGER.info("No remote repository detected. Push skipped."); - } else { - LOGGER.info("Failed to pull"); - throw new RuntimeException(e); - } - } + TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); + git.pull() + .setTransportConfigCallback(transportConfigCallback) + .call(); } else if (this.gitPassword.isEmpty() || this.gitUsername.isEmpty()) { - throw new IOException("No git credentials"); + throw new TransportException("No git credentials"); } else { - try { - git.pull() - .setCredentialsProvider(this.credentialsProvider).call(); - } catch (GitAPIException e) { - if (e.getMessage().equals("origin: not found")) { - LOGGER.info("No remote repository detected. Push skipped."); - } else if (e.getMessage().equals("HEAD is detached")) { - throw new IOException("HEAD is detached"); - } else { - LOGGER.info("Failed to pull"); - System.out.println(e.getMessage()); - throw new RuntimeException(e); - } - } + git.pull() + .setCredentialsProvider(this.credentialsProvider) + .call(); } } @@ -347,9 +325,36 @@ public String getCurrentlyCheckedOutBranch() { } } - public void setCredentialsProvider(CredentialsProvider credentialsProvider) { - this.credentialsProvider = credentialsProvider; - this.gitUsername = "credentialsProvider"; - this.gitPassword = "credentialsProvider"; + public void forceGitPull() { + try { + Git git = Git.open(this.repositoryPathAsFile); + String remoteURL = git.getRepository().getConfig().getString("remote", "origin", "url"); + boolean isSshRemoteRepository = remoteURL != null && remoteURL.contains("git@"); + if (isSshRemoteRepository) { + TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback(); + git.verifySignature(); + git.fetch() + .setTransportConfigCallback(transportConfigCallback) + .setRemote("origin") + .call(); + git.reset() + .setMode(ResetCommand.ResetType.HARD) + .call(); + git.merge().include(git.getRepository().findRef("origin/" + "main")).call(); + } else { + git.verifySignature(); + git.fetch() + .setCredentialsProvider(this.credentialsProvider) + .setRemote("origin") + .call(); + git.reset() + .setMode(ResetCommand.ResetType.HARD) + .call(); + git.merge().include(git.getRepository().findRef("origin/" + "main")).call(); + } + } catch (GitAPIException | IOException e) { + LOGGER.error("Failed to force git pull", e); + throw new RuntimeException(e); + } } } diff --git a/src/main/java/org/jabref/logic/git/GitPreferences.java b/src/main/java/org/jabref/logic/git/GitPreferences.java index 3599c2d6846..6398169703d 100644 --- a/src/main/java/org/jabref/logic/git/GitPreferences.java +++ b/src/main/java/org/jabref/logic/git/GitPreferences.java @@ -9,7 +9,6 @@ public class GitPreferences { private StringProperty username; private StringProperty password; private BooleanProperty autoCommit; - private BooleanProperty autoSync; public GitPreferences(String username, String password, Boolean autoCommit, Boolean autoSync) { @@ -46,22 +45,6 @@ public BooleanProperty getAutoSyncProperty() { return this.autoSync; } - public void setAutoCommit(Boolean autoCommit) { - this.autoCommit = new SimpleBooleanProperty(autoCommit); - } - - public void setAutoCommitProperty(BooleanProperty autoCommit) { - this.autoCommit = autoCommit; - } - - public void setAutoSync(Boolean autoSync) { - this.autoSync = new SimpleBooleanProperty(autoSync); - } - - public void setAutoSyncProperty(BooleanProperty autoSync) { - this.autoSync = autoSync; - } - public String getUsername() { return this.username.get(); } diff --git a/src/main/java/org/jabref/model/git/BibGitContext.java b/src/main/java/org/jabref/model/git/BibGitContext.java deleted file mode 100644 index 3a59c0cb991..00000000000 --- a/src/main/java/org/jabref/model/git/BibGitContext.java +++ /dev/null @@ -1,242 +0,0 @@ -package org.jabref.model.git; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.jabref.architecture.AllowedToUseLogic; -import org.jabref.gui.LibraryTab; -import org.jabref.logic.crawler.Crawler; -import org.jabref.logic.crawler.StudyRepository; -import org.jabref.logic.shared.DatabaseLocation; -import org.jabref.logic.shared.DatabaseSynchronizer; -import org.jabref.logic.util.CoarseChangeFilter; -import org.jabref.logic.util.OS; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.metadata.MetaData; -import org.jabref.model.study.Study; -import org.jabref.preferences.FilePreferences; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Represents everything related to a BIB file. - * - *

The entries are stored in GitDatabase, the other data in MetaData - * and the options relevant for this file in Defaults. - *

- *

- * To get an instance for a .bib file, use {@link org.jabref.logic.importer.fileformat.BibtexParser}. - *

- */ -@AllowedToUseLogic("because it needs access to shared database features") -public class BibGitContext { - - private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); - - private MetaData metaData; - - /** - * The path where this database was last saved to. - */ - private Path path; - - private DatabaseSynchronizer dbmsSynchronizer; - private CoarseChangeFilter dbmsListener; - private DatabaseLocation location; - private GitDatabase database; - - public BibGitContext() { - this(new GitDatabase()); - } - - public BibGitContext(GitDatabase database) { - this(database, new MetaData()); - } - - public BibGitContext(GitDatabase database, MetaData metaData) { - this.database = Objects.requireNonNull(database); - this.metaData = Objects.requireNonNull(metaData); - this.location = DatabaseLocation.LOCAL; - } - - public BibGitContext(GitDatabase database, MetaData metaData, Path path) { - this(database, metaData, path, DatabaseLocation.LOCAL); - } - - public BibGitContext(GitDatabase database, MetaData metaData, Path path, DatabaseLocation location) { - this(database, metaData); - Objects.requireNonNull(location); - this.path = path; - - if (location == DatabaseLocation.LOCAL) { - convertToLocalDatabase(); - } - } - - public void setDatabasePath(Path file) { - this.path = file; - } - - /** - * Get the path where this database was last saved to or loaded from, if any. - * - * @return Optional of the relevant Path, or Optional.empty() if none is defined. - */ - public Optional getDatabasePath() { - return Optional.ofNullable(path); - } - - public void clearDatabasePath() { - this.path = null; - } - - public GitDatabase getDatabase() { - return database; - } - - public MetaData getMetaData() { - return metaData; - } - - public void setMetaData(MetaData metaData) { - this.metaData = Objects.requireNonNull(metaData); - } - - /** - * Returns whether this .bib file belongs to a {@link Study} - */ - public boolean isStudy() { - return this.getDatabasePath() - .map(path -> path.getFileName().toString().equals(Crawler.FILENAME_STUDY_RESULT_BIB) && - Files.exists(path.resolveSibling(StudyRepository.STUDY_DEFINITION_FILE_NAME))) - .orElse(false); - } - - /** - * Look up the directories set up for this database. - * There can be up to four directories definitions for these files: - *
    - *
  1. next to the .bib file.
  2. - *
  3. the preferences can specify a default one.
  4. - *
  5. the database's metadata can specify a general directory.
  6. - *
  7. the database's metadata can specify a user-specific directory.
  8. - *
- *

- * The settings are prioritized in the following order, and the first defined setting is used: - *

    - *
  1. user-specific metadata directory
  2. - *
  3. general metadata directory
  4. - *
  5. BIB file directory (if configured in the preferences AND none of the two above directories are configured)
  6. - *
  7. preferences directory (if .bib file directory should not be used according to the preferences)
  8. - *
- * - * @param preferences The fileDirectory preferences - */ - public List getFileDirectories(FilePreferences preferences) { - List fileDirs = new ArrayList<>(); - - // 1. Metadata user-specific directory - metaData.getUserFileDirectory(preferences.getUserAndHost()) - .ifPresent(userFileDirectory -> fileDirs.add(getFileDirectoryPath(userFileDirectory))); - - // 2. Metadata general directory - metaData.getDefaultFileDirectory() - .ifPresent(metaDataDirectory -> fileDirs.add(getFileDirectoryPath(metaDataDirectory))); - - // 3. BIB file directory or Main file directory - // fileDirs.isEmpty in the case, 1) no user-specific file directory and 2) no general file directory is set - // (in the metadata of the bib file) - if (fileDirs.isEmpty() && preferences.shouldStoreFilesRelativeToBibFile()) { - getDatabasePath().ifPresent(dbPath -> { - Path parentPath = dbPath.getParent(); - if (parentPath == null) { - parentPath = Path.of(System.getProperty("user.dir")); - } - Objects.requireNonNull(parentPath, "BibTeX database parent path is null"); - fileDirs.add(parentPath); - }); - } else { - // Main file directory - preferences.getMainFileDirectory().ifPresent(fileDirs::add); - } - - return fileDirs.stream().map(Path::toAbsolutePath).collect(Collectors.toList()); - } - - /** - * Returns the first existing file directory from {@link #getFileDirectories(FilePreferences)} - * - * @return the path - or an empty optional, if none of the directories exists - */ - public Optional getFirstExistingFileDir(FilePreferences preferences) { - return getFileDirectories(preferences).stream() - .filter(Files::exists) - .findFirst(); - } - - private Path getFileDirectoryPath(String directoryName) { - Path directory = Path.of(directoryName); - // If this directory is relative, we try to interpret it as relative to - // the file path of this BIB file: - Optional databaseFile = getDatabasePath(); - if (!directory.isAbsolute() && databaseFile.isPresent()) { - return databaseFile.get().getParent().resolve(directory).normalize(); - } - return directory; - } - - public DatabaseSynchronizer getDBMSSynchronizer() { - return this.dbmsSynchronizer; - } - - public void clearDBMSSynchronizer() { - this.dbmsSynchronizer = null; - } - - public DatabaseLocation getLocation() { - return this.location; - } - - public void convertToSharedDatabase(DatabaseSynchronizer dmbsSynchronizer) { - this.dbmsSynchronizer = dmbsSynchronizer; - - //this.dbmsListener = new CoarseChangeFilter(this); - //dbmsListener.registerListener(dbmsSynchronizer); - - this.location = DatabaseLocation.SHARED; - } - - public void convertToLocalDatabase() { - if (Objects.nonNull(dbmsListener) && (location == DatabaseLocation.SHARED)) { - dbmsListener.unregisterListener(dbmsSynchronizer); - dbmsListener.shutdown(); - } - - this.location = DatabaseLocation.LOCAL; - } - - public List getEntries() { - return database.getEntries(); - } - - public Path getFulltextIndexPath() { - Path appData = OS.getNativeDesktop().getFulltextIndexBaseDirectory(); - Path indexPath; - - if (getDatabasePath().isPresent()) { - indexPath = appData.resolve(String.valueOf(this.getDatabasePath().get().hashCode())); - LOGGER.debug("Index path for {} is {}", getDatabasePath().get(), indexPath); - return indexPath; - } - - indexPath = appData.resolve("unsaved"); - LOGGER.debug("Using index for unsaved database: {}", indexPath); - return indexPath; - } -} diff --git a/src/main/java/org/jabref/model/git/GitDatabase.java b/src/main/java/org/jabref/model/git/GitDatabase.java deleted file mode 100644 index 4cec5c33d7d..00000000000 --- a/src/main/java/org/jabref/model/git/GitDatabase.java +++ /dev/null @@ -1,613 +0,0 @@ -package org.jabref.model.git; - -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - -import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.model.database.event.EntriesAddedEvent; -import org.jabref.model.database.event.EntriesRemovedEvent; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibtexString; -import org.jabref.model.entry.Month; -import org.jabref.model.entry.event.EntriesEventSource; -import org.jabref.model.entry.event.EntryChangedEvent; -import org.jabref.model.entry.event.FieldChangedEvent; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.FieldFactory; -import org.jabref.model.entry.field.StandardField; -import org.jabref.model.strings.StringUtil; - -import com.google.common.eventbus.EventBus; -import com.google.common.eventbus.Subscribe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A bibliography database. This is the "bib" file (or the library stored in a shared SQL database) - */ -public class GitDatabase { - - private static final Logger LOGGER = LoggerFactory.getLogger(GitDatabase.class); - private static final Pattern RESOLVE_CONTENT_PATTERN = Pattern.compile(".*#[^#]+#.*"); - - /** - * State attributes - */ - private final ObservableList entries = FXCollections.synchronizedObservableList(FXCollections.observableArrayList(BibEntry::getObservables)); - private Map bibtexStrings = new ConcurrentHashMap<>(); - - private final EventBus eventBus = new EventBus(); - - private String preamble; - - // All file contents below the last entry in the file - private String epilog = ""; - - private String sharedDatabaseID; - - private String newLineSeparator = System.lineSeparator(); - - public GitDatabase(List entries, String newLineSeparator) { - this(entries); - this.newLineSeparator = newLineSeparator; - } - - public GitDatabase(List entries) { - this(); - insertEntries(entries); - } - - public GitDatabase() { - this.registerListener(new KeyChangeListener(this)); - } - - /** - * Returns a text with references resolved according to an optionally given database. - * - * @param toResolve maybenull The text to resolve. - * @param database maybenull The database to use for resolving the text. - * @return The resolved text or the original text if either the text or the database are null - * @deprecated use {@link GitDatabase#resolveForStrings(String)} - */ - @Deprecated - public static String getText(String toResolve, GitDatabase database) { - if ((toResolve != null) && (database != null)) { - return database.resolveForStrings(toResolve); - } - return toResolve; - } - - /** - * Returns the number of entries. - */ - public int getEntryCount() { - return entries.size(); - } - - /** - * Checks if the database contains entries. - */ - public boolean hasEntries() { - return !entries.isEmpty(); - } - - /** - * Returns the list of entries sorted by the given comparator. - */ - public List getEntriesSorted(Comparator comparator) { - List entriesSorted = new ArrayList<>(entries); - entriesSorted.sort(comparator); - return entriesSorted; - } - - /** - * Returns whether an entry with the given ID exists (-> entry_type + hashcode). - */ - public boolean containsEntryWithId(String id) { - return entries.stream().anyMatch(entry -> entry.getId().equals(id)); - } - - public ObservableList getEntries() { - return FXCollections.unmodifiableObservableList(entries); - } - - /** - * Returns a set of Strings, that contains all field names that are visible. This means that the fields - * are not internal fields. Internal fields are fields, that are starting with "_". - * - * @return set of fieldnames, that are visible - */ - public Set getAllVisibleFields() { - Set allFields = new TreeSet<>(Comparator.comparing(Field::getName)); - for (BibEntry e : getEntries()) { - allFields.addAll(e.getFields()); - } - return allFields.stream().filter(field -> !FieldFactory.isInternalField(field)) - .collect(Collectors.toSet()); - } - - /** - * Returns the entry with the given citation key. - */ - public synchronized Optional getEntryByCitationKey(String key) { - for (BibEntry entry : entries) { - if (key.equals(entry.getCitationKey().orElse(null))) { - return Optional.of(entry); - } - } - return Optional.empty(); - } - - /** - * Collects entries having the specified citation key and returns these entries as list. - * The order of the entries is the order they appear in the database. - * - * @return list of entries that contains the given key - */ - public synchronized List getEntriesByCitationKey(String key) { - List result = new ArrayList<>(); - - for (BibEntry entry : entries) { - entry.getCitationKey().ifPresent(entryKey -> { - if (key.equals(entryKey)) { - result.add(entry); - } - }); - } - return result; - } - - public synchronized void insertEntry(BibEntry entry) { - insertEntry(entry, EntriesEventSource.LOCAL); - } - - /** - * Inserts the entry. - * - * @param entry entry to insert - * @param eventSource source the event is sent from - */ - public synchronized void insertEntry(BibEntry entry, EntriesEventSource eventSource) { - insertEntries(Collections.singletonList(entry), eventSource); - } - - public synchronized void insertEntries(BibEntry... entries) { - insertEntries(Arrays.asList(entries), EntriesEventSource.LOCAL); - } - - public synchronized void insertEntries(List entries) { - insertEntries(entries, EntriesEventSource.LOCAL); - } - - public synchronized void insertEntries(List newEntries, EntriesEventSource eventSource) { - Objects.requireNonNull(newEntries); - for (BibEntry entry : newEntries) { - entry.registerListener(this); - } - if (newEntries.isEmpty()) { - eventBus.post(new EntriesAddedEvent(newEntries, eventSource)); - } else { - eventBus.post(new EntriesAddedEvent(newEntries, newEntries.get(0), eventSource)); - } - entries.addAll(newEntries); - } - - public synchronized void removeEntry(BibEntry bibEntry) { - removeEntries(Collections.singletonList(bibEntry)); - } - - public synchronized void removeEntry(BibEntry bibEntry, EntriesEventSource eventSource) { - removeEntries(Collections.singletonList(bibEntry), eventSource); - } - - /** - * Removes the given entries. - * The entries removed based on the id {@link BibEntry#getId()} - * - * @param toBeDeleted Entries to delete - */ - public synchronized void removeEntries(List toBeDeleted) { - removeEntries(toBeDeleted, EntriesEventSource.LOCAL); - } - - /** - * Removes the given entries. - * The entries are removed based on the id {@link BibEntry#getId()} - * - * @param toBeDeleted Entry to delete - * @param eventSource Source the event is sent from - */ - public synchronized void removeEntries(List toBeDeleted, EntriesEventSource eventSource) { - Objects.requireNonNull(toBeDeleted); - - List ids = new ArrayList<>(); - for (BibEntry entry : toBeDeleted) { - ids.add(entry.getId()); - } - boolean anyRemoved = entries.removeIf(entry -> ids.contains(entry.getId())); - if (anyRemoved) { - eventBus.post(new EntriesRemovedEvent(toBeDeleted, eventSource)); - } - } - - /** - * Returns the database's preamble. - * If the preamble text consists only of whitespace, then also an empty optional is returned. - */ - public synchronized Optional getPreamble() { - if (StringUtil.isBlank(preamble)) { - return Optional.empty(); - } else { - return Optional.of(preamble); - } - } - - /** - * Sets the database's preamble. - */ - public synchronized void setPreamble(String preamble) { - this.preamble = preamble; - } - - /** - * Removes the string with the given id. - */ - public void removeString(String id) { - bibtexStrings.remove(id); - } - - /** - * Returns a Set of keys to all BibtexString objects in the database. - * These are in no sorted order. - */ - public Set getStringKeySet() { - return bibtexStrings.keySet(); - } - - /** - * Returns a Collection of all BibtexString objects in the database. - * These are in no particular order. - */ - public Collection getStringValues() { - return bibtexStrings.values(); - } - - /** - * Returns the string with the given id. - */ - public BibtexString getString(String id) { - return bibtexStrings.get(id); - } - - /** - * Returns the string with the given name/label - */ - public Optional getStringByName(String name) { - return getStringValues().stream().filter(string -> string.getName().equals(name)).findFirst(); - } - - /** - * Returns the number of strings. - */ - public int getStringCount() { - return bibtexStrings.size(); - } - - /** - * Check if there are strings. - */ - public boolean hasNoStrings() { - return bibtexStrings.isEmpty(); - } - - /** - * Copies the preamble of another GitDatabase. - * - * @param database another GitDatabase - */ - public void copyPreamble(GitDatabase database) { - setPreamble(database.getPreamble().orElse("")); - } - - /** - * Returns true if a string with the given label already exists. - */ - public synchronized boolean hasStringByName(String label) { - return bibtexStrings.values().stream().anyMatch(value -> value.getName().equals(label)); - } - - /** - * Resolves any references to strings contained in this field content, - * if possible. - */ - public String resolveForStrings(String content) { - Objects.requireNonNull(content, "Content for resolveForStrings must not be null."); - return resolveContent(content, new HashSet<>(), new HashSet<>()); - } - - /** - * Get all strings used in the entries. - */ - public Collection getUsedStrings(Collection entries) { - List result = new ArrayList<>(); - Set allUsedIds = new HashSet<>(); - - // All entries - for (BibEntry entry : entries) { - for (String fieldContent : entry.getFieldValues()) { - resolveContent(fieldContent, new HashSet<>(), allUsedIds); - } - } - - // Preamble - if (preamble != null) { - resolveContent(preamble, new HashSet<>(), allUsedIds); - } - - for (String stringId : allUsedIds) { - result.add((BibtexString) bibtexStrings.get(stringId).clone()); - } - - return result; - } - - /** - * Take the given collection of BibEntry and resolve any string - * references. - * - * @param entriesToResolve A collection of BibtexEntries in which all strings of the form - * #xxx# will be resolved against the hash map of string - * references stored in the database. - * @param inPlace If inPlace is true then the given BibtexEntries will be modified, if false then copies of the BibtexEntries are made before resolving the strings. - * @return a list of bibtexentries, with all strings resolved. It is dependent on the value of inPlace whether copies are made or the given BibtexEntries are modified. - */ - public List resolveForStrings(Collection entriesToResolve, boolean inPlace) { - Objects.requireNonNull(entriesToResolve, "entries must not be null."); - - List results = new ArrayList<>(entriesToResolve.size()); - - for (BibEntry entry : entriesToResolve) { - results.add(this.resolveForStrings(entry, inPlace)); - } - return results; - } - - /** - * Take the given BibEntry and resolve any string references. - * - * @param entry A BibEntry in which all strings of the form #xxx# will be - * resolved against the hash map of string references stored in - * the database. - * @param inPlace If inPlace is true then the given BibEntry will be - * modified, if false then a copy is made using close made before - * resolving the strings. - * @return a BibEntry with all string references resolved. It is - * dependent on the value of inPlace whether a copy is made or the - * given BibtexEntries is modified. - */ - public BibEntry resolveForStrings(BibEntry entry, boolean inPlace) { - BibEntry resultingEntry; - if (inPlace) { - resultingEntry = entry; - } else { - resultingEntry = (BibEntry) entry.clone(); - } - - for (Map.Entry field : resultingEntry.getFieldMap().entrySet()) { - resultingEntry.setField(field.getKey(), this.resolveForStrings(field.getValue())); - } - return resultingEntry; - } - - /** - * If the label represents a string contained in this database, returns - * that string's content. Resolves references to other strings, taking - * care not to follow a circular reference pattern. - * If the string is undefined, returns null. - */ - private String resolveString(String label, Set usedIds, Set allUsedIds) { - Objects.requireNonNull(label); - Objects.requireNonNull(usedIds); - Objects.requireNonNull(allUsedIds); - - for (BibtexString string : bibtexStrings.values()) { - if (string.getName().equalsIgnoreCase(label)) { - // First check if this string label has been resolved - // earlier in this recursion. If so, we have a - // circular reference, and have to stop to avoid - // infinite recursion. - if (usedIds.contains(string.getId())) { - LOGGER.info("Stopped due to circular reference in strings: " + label); - return label; - } - // If not, log this string's ID now. - usedIds.add(string.getId()); - if (allUsedIds != null) { - allUsedIds.add(string.getId()); - } - - // Ok, we found the string. Now we must make sure we - // resolve any references to other strings in this one. - String result = string.getContent(); - result = resolveContent(result, usedIds, allUsedIds); - - // Finished with recursing this branch, so we remove our - // ID again: - usedIds.remove(string.getId()); - - return result; - } - } - - // If we get to this point, the string has obviously not been defined locally. - // Check if one of the standard BibTeX month strings has been used: - Optional month = Month.getMonthByShortName(label); - return month.map(Month::getFullName).orElse(null); - } - - private String resolveContent(String result, Set usedIds, Set allUsedIds) { - String res = result; - if (RESOLVE_CONTENT_PATTERN.matcher(res).matches()) { - StringBuilder newRes = new StringBuilder(); - int piv = 0; - int next; - while ((next = res.indexOf(FieldWriter.BIBTEX_STRING_START_END_SYMBOL, piv)) >= 0) { - // We found the next string ref. Append the text - // up to it. - if (next > 0) { - newRes.append(res, piv, next); - } - int stringEnd = res.indexOf(FieldWriter.BIBTEX_STRING_START_END_SYMBOL, next + 1); - if (stringEnd >= 0) { - // We found the boundaries of the string ref, - // now resolve that one. - String refLabel = res.substring(next + 1, stringEnd); - String resolved = resolveString(refLabel, usedIds, allUsedIds); - - if (resolved == null) { - // Could not resolve string. Display the # - // characters rather than removing them: - newRes.append(res, next, stringEnd + 1); - } else { - // The string was resolved, so we display its meaning only, - // stripping the # characters signifying the string label: - newRes.append(resolved); - } - piv = stringEnd + 1; - } else { - // We did not find the boundaries of the string ref. This - // makes it impossible to interpret it as a string label. - // So we should just append the rest of the text and finish. - newRes.append(res.substring(next)); - piv = res.length(); - break; - } - } - if (piv < (res.length() - 1)) { - newRes.append(res.substring(piv)); - } - res = newRes.toString(); - } - return res; - } - - public String getEpilog() { - return epilog; - } - - public void setEpilog(String epilog) { - this.epilog = epilog; - } - - /** - * Registers a listener object (subscriber) to the internal event bus. - * The following events are posted: - * - * - {@link EntriesAddedEvent} - * - {@link EntryChangedEvent} - * - {@link EntriesRemovedEvent} - * - * @param listener listener (subscriber) to add - */ - public void registerListener(Object listener) { - this.eventBus.register(listener); - } - - /** - * Unregisters an listener object. - * - * @param listener listener (subscriber) to remove - */ - public void unregisterListener(Object listener) { - try { - this.eventBus.unregister(listener); - } catch (IllegalArgumentException e) { - // occurs if the event source has not been registered, should not prevent shutdown - LOGGER.debug("Problem unregistering", e); - } - } - - @Subscribe - private void relayEntryChangeEvent(FieldChangedEvent event) { - eventBus.post(event); - } - - public Optional getReferencedEntry(BibEntry entry) { - return entry.getField(StandardField.CROSSREF).flatMap(this::getEntryByCitationKey); - } - - public Optional getSharedDatabaseID() { - return Optional.ofNullable(this.sharedDatabaseID); - } - - public void setSharedDatabaseID(String sharedDatabaseID) { - this.sharedDatabaseID = sharedDatabaseID; - } - - public boolean isShared() { - return getSharedDatabaseID().isPresent(); - } - - public void clearSharedDatabaseID() { - this.sharedDatabaseID = null; - } - - /** - * Generates and sets a random ID which is globally unique. - * - * @return The generated sharedDatabaseID - */ - public String generateSharedDatabaseID() { - this.sharedDatabaseID = new BigInteger(128, new SecureRandom()).toString(32); - return this.sharedDatabaseID; - } - - /** - * Returns the number of occurrences of the given citation key in this database. - */ - public long getNumberOfCitationKeyOccurrences(String key) { - return entries.stream() - .flatMap(entry -> entry.getCitationKey().stream()) - .filter(key::equals) - .count(); - } - - /** - * Checks if there is more than one occurrence of the citation key. - */ - public boolean isDuplicateCitationKeyExisting(String key) { - return getNumberOfCitationKeyOccurrences(key) > 1; - } - - /** - * Set the newline separator. - */ - public void setNewLineSeparator(String newLineSeparator) { - this.newLineSeparator = newLineSeparator; - } - - /** - * Returns the string used to indicate a linebreak - */ - public String getNewLineSeparator() { - return newLineSeparator; - } -} diff --git a/src/main/java/org/jabref/model/git/KeyChangeListener.java b/src/main/java/org/jabref/model/git/KeyChangeListener.java deleted file mode 100644 index 77a2ccc5476..00000000000 --- a/src/main/java/org/jabref/model/git/KeyChangeListener.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.jabref.model.git; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import org.jabref.model.git.event.EntriesRemovedEvent; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.event.FieldChangedEvent; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.FieldFactory; -import org.jabref.model.entry.field.FieldProperty; -import org.jabref.model.entry.field.InternalField; - -import com.google.common.eventbus.Subscribe; - -public class KeyChangeListener { - - private final GitDatabase database; - - public KeyChangeListener(GitDatabase database) { - this.database = database; - } - - @Subscribe - public void listen(FieldChangedEvent event) { - if (event.getField().equals(InternalField.KEY_FIELD)) { - String newKey = event.getNewValue(); - String oldKey = event.getOldValue(); - updateEntryLinks(newKey, oldKey); - } - } - - @Subscribe - public void listen(EntriesRemovedEvent event) { - List entries = event.getBibEntries(); - for (BibEntry entry : entries) { - Optional citeKey = entry.getCitationKey(); - citeKey.ifPresent(oldkey -> updateEntryLinks(null, oldkey)); - } - } - - private void updateEntryLinks(String newKey, String oldKey) { - for (BibEntry entry : database.getEntries()) { - for (Field field : FieldFactory.getKeyFields()) { - entry.getField(field).ifPresent(fieldContent -> { - if (field.getProperties().contains(FieldProperty.SINGLE_ENTRY_LINK)) { - replaceSingleKeyInField(newKey, oldKey, entry, field, fieldContent); - } else { // MULTIPLE_ENTRY_LINK - replaceKeyInMultiplesKeyField(newKey, oldKey, entry, field, fieldContent); - } - }); - } - } - } - - private void replaceKeyInMultiplesKeyField(String newKey, String oldKey, BibEntry entry, Field field, String fieldContent) { - List keys = new ArrayList<>(Arrays.asList(fieldContent.split(","))); - int index = keys.indexOf(oldKey); - if (index != -1) { - if (newKey == null) { - keys.remove(index); - } else { - keys.set(index, newKey); - } - entry.setField(field, String.join(",", keys)); - } - } - - private void replaceSingleKeyInField(String newKey, String oldKey, BibEntry entry, Field field, String fieldContent) { - if (fieldContent.equals(oldKey)) { - if (newKey == null) { - entry.clearField(field); - } else { - entry.setField(field, newKey); - } - } - } -} diff --git a/src/main/java/org/jabref/model/git/KeyCollisionException.java b/src/main/java/org/jabref/model/git/KeyCollisionException.java deleted file mode 100644 index c00cb2cc730..00000000000 --- a/src/main/java/org/jabref/model/git/KeyCollisionException.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.jabref.model.git; - -public class KeyCollisionException extends RuntimeException { - - private String id; - - public KeyCollisionException() { - super(); - } - - public KeyCollisionException(String msg, String id) { - super(msg); - this.id = id; - } - - public KeyCollisionException(String msg, Throwable exception) { - super(msg, exception); - } - - public KeyCollisionException(Throwable exception) { - super(exception); - } - - public String getId() { - return id; - } -} diff --git a/src/main/java/org/jabref/model/git/event/EntriesRemovedEvent.java b/src/main/java/org/jabref/model/git/event/EntriesRemovedEvent.java deleted file mode 100644 index 77b1075f271..00000000000 --- a/src/main/java/org/jabref/model/git/event/EntriesRemovedEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.jabref.model.git.event; - -import java.util.List; - -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.event.EntriesEvent; -import org.jabref.model.entry.event.EntriesEventSource; - -/** - * EntriesRemovedEvent is fired when at least one BibEntry is being removed - * from the database. - */ - -public class EntriesRemovedEvent extends EntriesEvent { - - /** - * @param bibEntries List of BibEntry objects which are being removed. - */ - public EntriesRemovedEvent(List bibEntries) { - super(bibEntries); - } - - /** - * @param bibEntries List of BibEntry objects which are being removed. - * @param location Location affected by this event - */ - public EntriesRemovedEvent(List bibEntries, EntriesEventSource location) { - super(bibEntries, location); - } -} diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 9906e6e6d22..e9aa8f2bd4a 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1295,6 +1295,14 @@ You\ already\ added\ this\ certificate=You already added this certificate Git=Git Credential=Credential Email=Email +Enable\ git\ auto\ commit=Enable git auto commit +Enable\ git\ auto\ push\ and\ merge=Enable git auto push and merge +Local\ repository\ is\ out\ of\ date,\ please\ review\ the\ library\ and\ save\ again=The local repository is out of date, please review the library and save again. +No\ remote\ repository\ detected=No remote repository detected +Git\ detached\ head=Git detached head +Git\ credentials\ error=Git credentials error +Failed\ to\ open\ repository=Failed to open repository +Failed\ to\ push\ file\ in\ remote\ repository=Failed to push file in remote repository Open\ folder=Open folder Export\ sort\ order=Export sort order From ce2e1d3901393bb9b9aa1d35559d33da98df8bbd Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Thu, 14 Dec 2023 15:58:30 -0300 Subject: [PATCH 43/48] fix: push when status is clean --- .../java/org/jabref/gui/exporter/SaveDatabaseAction.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index c4408b5c755..18968fd4641 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -370,14 +370,13 @@ public boolean automaticGitCommit(Path filePath) { String automaticCommitMsg = "Automatic update via JabRef"; if (preferences.getGitPreferences().getAutoCommit()) { try { - git.createCommitWithSingleFileOnCurrentBranch(filePath.getFileName().toString(), automaticCommitMsg); + 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; } } - return true; + return false; } public void automaticGitPush(Path filePath) { From 18a3373182975bc1fda6106a10ebe73b72aebbfd Mon Sep 17 00:00:00 2001 From: Marcelo Nascimento Date: Thu, 14 Dec 2023 16:26:11 -0300 Subject: [PATCH 44/48] fix: comments and forceGitPull --- .../org/jabref/gui/exporter/SaveDatabaseAction.java | 10 +++++++++- src/main/java/org/jabref/logic/git/GitHandler.java | 7 +++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 18968fd4641..96018bf5a5a 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -335,7 +335,8 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset } /** - * Handle JabRef git integration action. This method is called when the user save the database. + * @param filePath of library + * @return true on successful git pull */ public boolean automaticGitPull(Path filePath) { GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences()); @@ -365,6 +366,10 @@ public boolean automaticGitPull(Path filePath) { 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"; @@ -379,6 +384,9 @@ public boolean automaticGitCommit(Path filePath) { return false; } + /** + * @param filePath of library + */ public void automaticGitPush(Path filePath) { GitHandler git = new GitHandler(filePath.getParent(), preferences.getGitPreferences()); try { diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 2a272509eeb..c18d52b6410 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -325,6 +325,9 @@ public String getCurrentlyCheckedOutBranch() { } } + /** + * Force git pull and overwrite files. + */ public void forceGitPull() { try { Git git = Git.open(this.repositoryPathAsFile); @@ -340,7 +343,7 @@ public void forceGitPull() { git.reset() .setMode(ResetCommand.ResetType.HARD) .call(); - git.merge().include(git.getRepository().findRef("origin/" + "main")).call(); + git.merge().include(git.getRepository().findRef("origin/" + getCurrentlyCheckedOutBranch())).call(); } else { git.verifySignature(); git.fetch() @@ -350,7 +353,7 @@ public void forceGitPull() { git.reset() .setMode(ResetCommand.ResetType.HARD) .call(); - git.merge().include(git.getRepository().findRef("origin/" + "main")).call(); + git.merge().include(git.getRepository().findRef("origin/" + getCurrentlyCheckedOutBranch())).call(); } } catch (GitAPIException | IOException e) { LOGGER.error("Failed to force git pull", e); From 4ebfd8550af159b8a183f384ca5ab72e300436b7 Mon Sep 17 00:00:00 2001 From: Marcelo Junior Date: Sat, 13 Jan 2024 11:57:01 -0300 Subject: [PATCH 45/48] fix: set credentials on test and style --- .../gui/git/GitCredentialsDialogView.java | 2 - .../org/jabref/logic/git/GitHandlerTest.java | 43 +++++++++---------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java index ac7a915cecf..44abf025b7d 100644 --- a/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java +++ b/src/main/java/org/jabref/gui/git/GitCredentialsDialogView.java @@ -29,7 +29,6 @@ public class GitCredentialsDialogView extends BaseDialog { private TextField inputGitUsername; private PasswordField inputGitPassword; - public GitCredentialsDialogView() { this.setTitle(Localization.lang("Git credentials")); this.dialogService = Injector.instantiateModelOrService(DialogService.class); @@ -47,7 +46,6 @@ public GitCredentialsDialogView() { vBox.getChildren().add(this.inputGitPassword); this.pane.setContent(vBox); - } public void showGitCredentialsDialog() { diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index c050edd2379..59523ffa297 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -7,6 +7,8 @@ import java.util.Iterator; import java.util.Objects; +import org.jabref.preferences.PreferencesService; + import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; @@ -28,14 +30,13 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; -import org.jabref.logic.git.GitPreferences; -import org.jabref.preferences.PreferencesService; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -45,13 +46,12 @@ class GitHandlerTest { private GitHandler gitHandler; private GitPreferences gitPreferences; - private static Repository createRepository() throws IOException, GitAPIException { File localPath = File.createTempFile("TestGitRepository", ""); - if(!localPath.delete()) { + if (!localPath.delete()) { throw new IOException("Could not delete temporary file " + localPath); } - if(!localPath.mkdirs()) { + if (!localPath.mkdirs()) { throw new IOException("Could not create directory " + localPath); } Repository repository = FileRepositoryBuilder.create(new File(localPath, ".git")); @@ -61,12 +61,13 @@ private static Repository createRepository() throws IOException, GitAPIException git.commit().setMessage("Initial commit").call(); return repository; } + private static Path createFolder() throws IOException { File localPath = File.createTempFile("TestGitRepository", ""); - if(!localPath.delete()) { + if (!localPath.delete()) { throw new IOException("Could not delete temporary file " + localPath); } - if(!localPath.mkdirs()) { + if (!localPath.mkdirs()) { throw new IOException("Could not create directory " + localPath); } return localPath.toPath(); @@ -128,7 +129,7 @@ void getCurrentlyCheckedOutBranch() { } @Test - void pushSingleFile () throws Exception { + void pushSingleFile() throws Exception { String username = "test"; String password = "test"; @@ -137,35 +138,32 @@ void pushSingleFile () throws Exception { Server server = createServer(username, password, repository); server.start(); - //Clone + // Clone CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password); Path clonedRepPath = createFolder(); Git.cloneRepository() .setCredentialsProvider(credentialsProvider) - .setURI( "http://localhost:8080/repoTest/.git") + .setURI("http://localhost:8080/repoTest/.git") .setDirectory(clonedRepPath.toFile()) .call(); - //Add files + // Add files Files.createFile(Path.of(clonedRepPath.toString(), "bib_1.bib")); - //Commit - GitHandler git = new GitHandler(clonedRepPath); - assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_1.bib", "PushSingleFile")); - - //Push + // Commit gitPreferences = new GitPreferences(username, password, true, true); PreferencesService preferences = mock(PreferencesService.class); when(preferences.getGitPreferences()).thenReturn(gitPreferences); - git.setGitPreferences(preferences.getGitPreferences()); + GitHandler git = new GitHandler(clonedRepPath, preferences.getGitPreferences()); + git.createCommitWithSingleFileOnCurrentBranch("bib_1.bib", "PushSingleFile"); + + // Push git.pushCommitsToRemoteRepository(); - assertTrue(git.createCommitWithSingleFileOnCurrentBranch("bib_2.bib", "PushSingleFile")); server.stop(); } private static SecurityHandler basicAuth(String username, String password) { - HashLoginService l = new HashLoginService(); UserStore userStore = new UserStore(); String[] roles = new String[] {"user"}; @@ -190,7 +188,6 @@ private static SecurityHandler basicAuth(String username, String password) { csh.setLoginService(l); return csh; - } private Server createServer(String username, String password, Repository repository) { From ceab32ca34a172fab8ef2ee021599101526c6c5c Mon Sep 17 00:00:00 2001 From: Marcelo Junior Date: Sat, 13 Jan 2024 21:42:08 -0300 Subject: [PATCH 46/48] fix: keys in language file and mock on SaveDatabaseActionTest --- src/main/resources/csl-styles | 2 +- src/main/resources/l10n/JabRef_en.properties | 5 ++++- .../java/org/jabref/gui/exporter/SaveDatabaseActionTest.java | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index 9c43a7dacc1..5bea241e5a2 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit 9c43a7dacc170d7f0225b53603e5dbc1666aaeb0 +Subproject commit 5bea241e5a2acacc29b947e21539be9cbe79c8bd diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index dfaac8c1976..38a413d6052 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1289,10 +1289,13 @@ You\ already\ added\ this\ certificate=You already added this certificate Git=Git Credential=Credential +Git\ credentials=Git credentials +Git\ password=Git password +Git\ username=Git username Email=Email Enable\ git\ auto\ commit=Enable git auto commit Enable\ git\ auto\ push\ and\ merge=Enable git auto push and merge -Local\ repository\ is\ out\ of\ date,\ please\ review\ the\ library\ and\ save\ again=The local repository is out of date, please review the library and save again. +Local\ repository\ is\ out\ of\ date,\ please\ review\ the\ library\ and\ save\ again=Local repository is out of date, please review the library and save again No\ remote\ repository\ detected=No remote repository detected Git\ detached\ head=Git detached head Git\ credentials\ error=Git credentials error diff --git a/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java b/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java index 9b36a5a712b..6901c1b9d2b 100644 --- a/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java +++ b/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java @@ -19,6 +19,7 @@ import org.jabref.logic.citationkeypattern.GlobalCitationKeyPattern; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.SaveConfiguration; +import org.jabref.logic.git.GitPreferences; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; @@ -62,6 +63,7 @@ public void setUp() { when(filePreferences.getWorkingDirectory()).thenReturn(Path.of(TEST_BIBTEX_LIBRARY_LOCATION)); when(preferences.getFilePreferences()).thenReturn(filePreferences); when(preferences.getExportPreferences()).thenReturn(mock(ExportPreferences.class)); + when(preferences.getGitPreferences()).thenReturn(mock(GitPreferences.class)); saveDatabaseAction = spy(new SaveDatabaseAction(libraryTab, dialogService, preferences, mock(BibEntryTypesManager.class))); } From bc0fd86bd4be3a73d29e8ccbed57d41e75918c33 Mon Sep 17 00:00:00 2001 From: Marcelo Junior Date: Sun, 14 Jan 2024 20:02:34 -0300 Subject: [PATCH 47/48] fix: changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bb36316045..1e53c0e76b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -83,7 +84,6 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added a link "Get more themes..." in the preferences to that points to [themes.jabref.org](https://themes.jabref.org) allowing the user to download new themes. [#10243](https://github.com/JabRef/jabref/issues/10243) - We added a fetcher for [LOBID](https://lobid.org/resources/api) resources. [koppor#386](https://github.com/koppor/jabref/issues/386) - When in `biblatex` mode, the [integrity check](https://docs.jabref.org/finding-sorting-and-cleaning-entries/checkintegrity) for journal titles now also checks the field `journal`. -- 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) - We added support for exporting to Hayagriva YAML format. [#10382](https://github.com/JabRef/jabref/issues/10382) - We added support for pushing citations to [TeXShop](https://pages.uoregon.edu/koch/texshop/) on macOS [forum#2699](https://discourse.jabref.org/t/push-to-texshop-mac/2699). - We added the 'Bachelor's thesis' type for Biblatex's 'Thesis' EntryType [#10029](https://github.com/JabRef/jabref/issues/10029). From 6b5d9e1c8d9bf181adacb5b2a90ecafb2e91c577 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 28 Feb 2024 23:49:48 +0100 Subject: [PATCH 48/48] Update src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java --- src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 7404cd3d0c0..92b5e63bb1b 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -264,7 +264,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { return success; } catch (SaveException ex) { - LOGGER.error(String.format("A problem occurred when trying to save the file %s", targetPath), ex); + LOGGER.error("A problem occurred when trying to save the file {}", targetPath, ex); dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); return false; } finally {