-
Notifications
You must be signed in to change notification settings - Fork 15
WIP: GSoC Git Support - Support semantic pull merge without conflicts #714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was not easy - really beautiful work, Wanling. For a WIP, this is exceptionally good!
I am leaving some comments - the earlier we get habituated to some idiomatic practices (some JabRef-specific, some general), the easier it will be to stick to them during the rest of the project.
Please don't be overwhelmed by the number of comments - most of them are minor. Your code is already good, so the only way I could help make it better was by nit-picking.
private static final Logger LOGGER = LoggerFactory.getLogger(GitSyncService.class); | ||
private final ImportFormatPreferences importFormatPreferences; | ||
private GitHandler gitHandler; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: I usually separate the logger from the other variables with a newline
private static final Logger LOGGER = LoggerFactory.getLogger(GitSyncService.class); | |
private final ImportFormatPreferences importFormatPreferences; | |
private GitHandler gitHandler; | |
private static final Logger LOGGER = LoggerFactory.getLogger(GitSyncService.class); | |
private final ImportFormatPreferences importFormatPreferences; | |
private GitHandler gitHandler; |
|
||
// 4. Automatic merge | ||
if (result.successful()) { | ||
gitHandler.createCommitOnCurrentBranch("Auto-merged by JabRef", false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declare a constant called AMEND = true
in the class and use !AMEND
here to make the code more self-documenting.
/** | ||
* Called when user clicks Pull | ||
*/ | ||
public MergeResult pullAndMerge(Path bibFilePath) throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid throwing generic exceptions. Since it might be tedious work to replace them, I will leave suggestions based on how the final exception list should look like, provided you have replaced them for the internal callee methods as well.
This is just to help - do not commit suggestion directly, as you will need some new imports.
public MergeResult pullAndMerge(Path bibFilePath) throws Exception { | |
public MergeResult pullAndMerge(Path bibFilePath) throws GitAPIException, IOException, JabRefException { |
RevCommit baseCommit, | ||
RevCommit localCommit, | ||
RevCommit remoteCommit, | ||
Path bibFilePath) throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path bibFilePath) throws Exception { | |
Path bibFilePath) throws IOException, JabRefException { |
MergeResult result = performSemanticMerge(git, triple.base(), triple.local(), triple.remote(), bibFilePath); | ||
|
||
// 4. Automatic merge | ||
if (result.successful()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: about how code "sounds" when reading.
Hypothetically, if result
was of type ResultType
, ResultType
being an enum for success states, we could do something like if(result == ResultType.SUCCESS)
- which sounds "natural".
Since here, result is a MergeResult
record instance, and we are using a(n implicit) method to evaluate, can you change the name of the record parameter to isSuccessful
so that the implicit method name also changes? if(result.isSuccessful())
will read better.
In general, method names should be imperatives for actions, and predicates for boolean queries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I hadn't thought about how much smoother that reads. Naming really is an art 😄
List<BibEntry> entries = context.getEntries(); | ||
assertEquals(1, entries.size()); | ||
|
||
BibEntry entry = entries.get(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BibEntry entry = entries.get(0); | |
BibEntry entry = entries.getFirst(); |
assertEquals(inputEntries.get(0).getCitationKey(), outputEntries.get(0).getCitationKey()); | ||
assertEquals(inputEntries.get(0).getField(StandardField.AUTHOR), outputEntries.get(0).getField(StandardField.AUTHOR)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assertEquals(inputEntries.get(0).getCitationKey(), outputEntries.get(0).getCitationKey()); | |
assertEquals(inputEntries.get(0).getField(StandardField.AUTHOR), outputEntries.get(0).getField(StandardField.AUTHOR)); | |
assertEquals(inputEntries.getFirst().getCitationKey(), outputEntries.getFirst().getCitationKey()); | |
assertEquals(inputEntries.getFirst().getField(StandardField.AUTHOR), outputEntries.getFirst().getField(StandardField.AUTHOR)); |
BibDatabaseContext baseCtx = parse(baseCommit); | ||
BibDatabaseContext remoteCtx = parse(remoteCommit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
baseDatabaseContext, remoteDatabaseContext
BibDatabaseContext baseCtx = GitBibParser.parseBibFromGit(base, importFormatPreferences); | ||
BibDatabaseContext localCtx = GitBibParser.parseBibFromGit(local, importFormatPreferences); | ||
BibDatabaseContext remoteCtx = GitBibParser.parseBibFromGit(remote, importFormatPreferences); | ||
|
||
MergePlan plan = SemanticConflictDetector.extractMergePlan(baseCtx, remoteCtx); | ||
SemanticMerger.applyMergePlan(localCtx, plan); | ||
|
||
BibEntry patched = localCtx.getDatabase().getEntryByCitationKey("a").orElseThrow(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All Ctx -> DatabaseContext
BibDatabaseContext baseCtx = parse(baseCommit); | ||
BibDatabaseContext localCtx = parse(localCommit); | ||
BibDatabaseContext remoteCtx = parse(remoteCommit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all Ctx -> DatabaseContext
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small comment on the process
Git git = Git.open(bibFilePath.getParent().toFile()); | ||
|
||
// 1. fetch latest remote branch | ||
gitHandler.pullOnCurrentBranch(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
git.fetch().call();
(because pull is fetch+merge, but JabRef should do the merge (see below)
public class GitSyncService { | ||
private static final Logger LOGGER = LoggerFactory.getLogger(GitSyncService.class); | ||
private final ImportFormatPreferences importFormatPreferences; | ||
private GitHandler gitHandler; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
git handler could be changed? if not, then it's better to add final
// 4. Automatic merge | ||
if (result.successful()) { | ||
gitHandler.createCommitOnCurrentBranch("Auto-merged by JabRef", false); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
performSemanticMerge
is a function that mutates some state? What if the result of semantic merge would be unsuccessful, will there be like part of the work finished and part of the work left untouched?
Oh, as we only handle non-conflicting cases, for now, we can skip this moment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhm, I see. If there is a conflict, then it would not change anything, but if there are no conflicts, then the result will be successful
if (bibPath.startsWith(workTree)) { | ||
relativePath = workTree.relativize(bibPath); | ||
} else { | ||
throw new IllegalStateException("Given .bib file is not inside repository"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can leave the exception for now, but in future we will check beforehand if a bib file is inside a repository, right?
|
||
// WIP | ||
public void push(Path bibFilePath) throws Exception { | ||
this.gitHandler = new GitHandler(bibFilePath.getParent()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One interesting moment to check for: will this command find a git repository in upper-level directories?
If I use git
command, while I'm in some org/jabref/logic/...
folders, I still can use git commit
. I just don't know whether this is supported on some standard-level, or this is a feature if git
CLI application
|
||
/** | ||
* Implementation-only merge logic: applies changes from remote (relative to base) to local. | ||
* does not check for "modifications" or "conflicts" — all decisions should be handled in advance by the SemanticConflictDetector |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small moment:
* does not check for "modifications" or "conflicts" — all decisions should be handled in advance by the SemanticConflictDetector | |
* does not check for "modifications" or "conflicts" — all decisions should be handled in advance by the {@link SemanticConflictDetector} |
Closes JabRef#12350
Steps to test
This PR implements the first part of Git sync support by enabling automatic semantic merges when there are no conflicts. The scenario is tested via TDD in GitSyncServiceTest, simulating the following steps:
Since they changed different entries, we expect a clean merge without user intervention. The orchestrator is
GitSyncService
, and helper utilities and value objects are temporarily located inorg.jabref.logic.git.util
.Mandatory checks
CHANGELOG.md
described in a way that is understandable for the average user (if change is visible to the user)