@@ -596,37 +596,86 @@ def merge(
596596 msg : Optional [str ] = None ,
597597 squash : bool = False ,
598598 ) -> Optional [str ]:
599- from pygit2 import GIT_RESET_MIXED , GitError
599+ from pygit2 import (
600+ GIT_MERGE_ANALYSIS_FASTFORWARD ,
601+ GIT_MERGE_ANALYSIS_NONE ,
602+ GIT_MERGE_ANALYSIS_UNBORN ,
603+ GIT_MERGE_ANALYSIS_UP_TO_DATE ,
604+ GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY ,
605+ GIT_MERGE_PREFERENCE_NO_FASTFORWARD ,
606+ GitError ,
607+ )
600608
601609 if commit and squash :
602610 raise SCMError ("Cannot merge with 'squash' and 'commit'" )
603611
604- if commit and not msg :
605- raise SCMError ("Merge commit message is required" )
606-
607612 with self .release_odb_handles ():
613+ self .repo .index .read (False )
614+ obj , _ref = self .repo .resolve_refish (rev )
615+ analysis , ff_pref = self .repo .merge_analysis (obj .id )
616+
617+ if analysis == GIT_MERGE_ANALYSIS_NONE :
618+ raise SCMError (f"'{ rev } ' cannot be merged into HEAD" )
619+ if analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE :
620+ return None
621+
608622 try :
609- self .repo .index .read (False )
610- self .repo .merge (rev )
623+ self .repo .merge (obj .id )
611624 self .repo .index .write ()
612625 except GitError as exc :
613626 raise SCMError ("Merge failed" ) from exc
614627
615628 if self .repo .index .conflicts :
616629 raise MergeConflictError ("Merge contained conflicts" )
617630
618- if commit :
619- user = self .default_signature
620- tree = self .repo .index .write_tree ()
621- merge_commit = self .repo .create_commit (
622- "HEAD" , user , user , msg , tree , [self .repo .head .target , rev ]
623- )
624- return str (merge_commit )
625- if squash :
626- self .repo .reset (self .repo .head .target , GIT_RESET_MIXED )
631+ try :
632+ if not (
633+ squash or ff_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD
634+ ):
635+ if analysis & GIT_MERGE_ANALYSIS_FASTFORWARD :
636+ return self ._merge_ff (rev , obj )
637+
638+ if analysis & GIT_MERGE_ANALYSIS_UNBORN :
639+ self .repo .set_head (obj .id )
640+ return str (obj .id )
641+
642+ if ff_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY :
643+ raise SCMError ("Cannot fast-forward HEAD to '{rev}'" )
644+
645+ if commit :
646+ if not msg :
647+ raise SCMError ("Merge commit message is required" )
648+ user = self .default_signature
649+ tree = self .repo .index .write_tree ()
650+ merge_commit = self .repo .create_commit (
651+ "HEAD" ,
652+ user ,
653+ user ,
654+ msg ,
655+ tree ,
656+ [self .repo .head .target , obj .id ],
657+ )
658+ return str (merge_commit )
659+
660+ # --squash merge:
661+ # HEAD is not moved and merge changes stay in index
662+ return None
663+ finally :
627664 self .repo .state_cleanup ()
628665 self .repo .index .write ()
629- return None
666+
667+ def _merge_ff (self , rev : str , obj ) -> str :
668+ if self .repo .head_is_detached :
669+ self .repo .set_head (obj .id )
670+ else :
671+ branch = self .get_ref ("HEAD" , follow = False )
672+ assert branch
673+ self .set_ref (
674+ branch ,
675+ str (obj .id ),
676+ message = f"merge { rev } : Fast-forward" ,
677+ )
678+ return str (obj .id )
630679
631680 def validate_git_remote (self , url : str , ** kwargs ):
632681 raise NotImplementedError
0 commit comments