@@ -396,133 +396,4 @@ public File getRepositoryPathAsFile() {
396396 public Git open () throws IOException {
397397 return Git .open (this .repositoryPathAsFile );
398398 }
399-
400- public boolean hasRemote (String remoteName ) {
401- try (Git git = Git .open (this .repositoryPathAsFile )) {
402- return git .getRepository ().getConfig ()
403- .getSubsections ("remote" )
404- .contains (remoteName );
405- } catch (IOException e ) {
406- LOGGER .error ("Failed to check remote configuration" , e );
407- return false ;
408- }
409- }
410-
411- /// Pre-stage a merge (two parents) but do NOT commit yet.
412- /// Equivalent to: `git merge -s ours --no-commit <remote>`
413- /// Puts the repo into MERGING state, sets MERGE_HEAD=remote; working tree becomes "ours".
414- public void beginOursMergeNoCommit (RevCommit remote ) throws IOException , GitAPIException {
415- try (Git git = Git .open (this .repositoryPathAsFile )) {
416- git .merge ()
417- .include (remote )
418- .setStrategy (org .eclipse .jgit .merge .MergeStrategy .OURS )
419- .setFastForward (org .eclipse .jgit .api .MergeCommand .FastForwardMode .NO_FF )
420- .setCommit (false )
421- .call ();
422- }
423- }
424-
425- /// Fast-forward only to <remote> (when local is strictly behind).
426- /// Equivalent to: `git merge --ff-only <remote>`
427- public void fastForwardTo (RevCommit remote ) throws IOException , GitAPIException {
428- try (Git git = Git .open (this .repositoryPathAsFile )) {
429- git .merge ()
430- .include (remote )
431- .setFastForward (org .eclipse .jgit .api .MergeCommand .FastForwardMode .FF_ONLY )
432- .setCommit (true )
433- .call ();
434- }
435- }
436-
437- /// Abort a pre-commit semantic merge in a minimal/safe way:
438- /// 1) Clear merge state files (MERGE_HEAD / MERGE_MSG, etc.). Since there is no direct equivalent for git merge --abort in JGit.
439- /// 2) Restore ONLY the given file back to HEAD (both index + working tree).
440- ///
441- /// NOTE: Callers should ensure the working tree was clean before starting,
442- /// otherwise this can overwrite the user's uncommitted changes for that file.
443- public void abortSemanticMerge (Path absoluteFilePath , boolean allowHardReset ) throws IOException , GitAPIException {
444- try (Git git = Git .open (this .repositoryPathAsFile )) {
445- Repository repo = git .getRepository ();
446-
447- // Only act if a branch is actually in a merge state
448- RepositoryState state = repo .getRepositoryState ();
449- boolean inMerging = (state == RepositoryState .MERGING ) || (state == RepositoryState .MERGING_RESOLVED );
450- if (!inMerging ) {
451- return ;
452- }
453-
454- // 1) Clear merge state files + possible REVERT/CHERRY_PICK state
455- repo .writeMergeCommitMsg (null );
456- repo .writeMergeHeads (null );
457- repo .writeRevertHead (null );
458- repo .writeCherryPickHead (null );
459-
460- // 2) Targeted rollback: only restore the file we touched back to HEAD
461- Path workTree = repo .getWorkTree ().toPath ().toRealPath ();
462- Path targetAbs = absoluteFilePath .toRealPath ();
463- if (!targetAbs .startsWith (workTree )) {
464- return ;
465- }
466- String rel = workTree .relativize (targetAbs ).toString ().replace ('\\' , '/' );
467-
468- // 2.1 Reset the file in the index to HEAD (Equivalent to: `git reset -- <path>`)
469- git .reset ()
470- .addPath (rel )
471- .call ();
472-
473- // 2.2 Restore the file in the working tree from HEAD (Equivalent to: `git checkout -- <path>`)
474- git .checkout ()
475- .setStartPoint ("HEAD" )
476- .addPath (rel )
477- .call ();
478- }
479- }
480-
481- /// Start a "semantic-merge merge-state" and return a guard:
482- public MergeGuard beginSemanticMergeGuard (RevCommit remote , Path bibFilePath ) throws IOException , GitAPIException {
483- beginOursMergeNoCommit (remote );
484- return new MergeGuard (this , bibFilePath );
485- }
486-
487- public static final class MergeGuard implements AutoCloseable {
488- private final GitHandler handler ;
489- private final Path bibFilePath ;
490- private final AtomicBoolean active = new AtomicBoolean (true );
491- private volatile boolean committed = false ;
492-
493- private MergeGuard (GitHandler handler , Path bibFilePath ) {
494- this .handler = handler ;
495- this .bibFilePath = bibFilePath ;
496- }
497-
498- // Finalize: create the commit (in MERGING this becomes a merge commit with two parents).
499- public void commit (String message ) throws IOException , GitAPIException {
500- if (!active .get ()) {
501- return ;
502- }
503- handler .createCommitOnCurrentBranch (message , false );
504- committed = true ;
505- }
506-
507- // If not committed and still active, best-effort rollback:
508- // only this .bib file + clear MERGE_*; never throw from close().
509- @ Override
510- public void close () {
511- if (!active .compareAndSet (true , false )) {
512- return ;
513- }
514- if (committed ) {
515- return ;
516- }
517- try {
518- handler .abortSemanticMerge (bibFilePath , false );
519- } catch (IOException | GitAPIException e ) {
520- LOGGER .debug ("Abort semantic merge failed (best-effort cleanup). path={}" , bibFilePath , e );
521- } catch (RuntimeException e ) {
522- // Deliberately catching RuntimeException here because this is a best-effort cleanup in AutoCloseable.close().
523- // have to NOT throw from close() to avoid masking the primary failure/result of pull/merge.
524- LOGGER .warn ("Unexpected runtime exception during cleanup; rethrowing. path={}" , bibFilePath , e );
525- }
526- }
527- }
528399}
0 commit comments