diff --git a/scmrepo/git/backend/base.py b/scmrepo/git/backend/base.py index 118b54f7..0feef3ef 100644 --- a/scmrepo/git/backend/base.py +++ b/scmrepo/git/backend/base.py @@ -218,6 +218,7 @@ def push_refspecs( url: str, refspecs: Union[str, Iterable[str]], force: bool = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, progress: Callable[["GitProgressEvent"], None] = None, **kwargs, ) -> Mapping[str, SyncStatus]: @@ -228,6 +229,11 @@ def push_refspecs( refspecs: Iterable containing refspecs to fetch. Note that this will not match subkeys. force: If True, remote refs will be overwritten. + on_diverged: Callback function which will be called if local ref + and remote have diverged and force is False. If the callback + returns True the remote ref will be overwritten. + Callback will be of the form: + on_diverged(local_refname, remote_sha) """ @abstractmethod @@ -236,6 +242,7 @@ def fetch_refspecs( url: str, refspecs: Union[str, Iterable[str]], force: bool = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, progress: Callable[["GitProgressEvent"], None] = None, **kwargs, ) -> Mapping[str, SyncStatus]: @@ -247,6 +254,11 @@ def fetch_refspecs( refspecs: Iterable containing refspecs to fetch. Note that this will not match subkeys. force: If True, local refs will be overwritten. + on_diverged: Callback function which will be called if local ref + and remote have diverged and force is False. If the callback + returns True the local ref will be overwritten. + Callback will be of the form: + on_diverged(local_refname, remote_sha) """ @abstractmethod diff --git a/scmrepo/git/backend/dulwich/__init__.py b/scmrepo/git/backend/dulwich/__init__.py index b45680b8..eff02942 100644 --- a/scmrepo/git/backend/dulwich/__init__.py +++ b/scmrepo/git/backend/dulwich/__init__.py @@ -493,6 +493,7 @@ def push_refspecs( url: str, refspecs: Union[str, Iterable[str]], force: bool = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, progress: Callable[["GitProgressEvent"], None] = None, **kwargs, ) -> Mapping[str, SyncStatus]: @@ -533,8 +534,16 @@ def update_refs(refs): check_diverged(self.repo, refs[rh], self.repo.refs[lh]) except DivergedBranches: if not force: - change_result[refname] = SyncStatus.DIVERGED - continue + overwrite = ( + on_diverged( + os.fsdecode(lh), os.fsdecode(refs[rh]) + ) + if on_diverged + else False + ) + if not overwrite: + change_result[refname] = SyncStatus.DIVERGED + continue if lh is None: value = ZERO_SHA @@ -556,7 +565,8 @@ def update_refs(refs): ), ) except (NotGitRepository, SendPackError) as exc: - raise SCMError(f"Git failed to push ref to '{url}'") from exc + src = [lh for (lh, _, _) in selected_refs] + raise SCMError(f"Git failed to push '{src}' to '{url}'") from exc except HTTPUnauthorized: raise AuthError(url) return change_result @@ -566,6 +576,7 @@ def fetch_refspecs( url: str, refspecs: Union[str, Iterable[str]], force: Optional[bool] = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, progress: Callable[["GitProgressEvent"], None] = None, **kwargs, ) -> Mapping[str, SyncStatus]: @@ -629,8 +640,18 @@ def determine_wants(remote_refs): ) except DivergedBranches: if not force: - result[refname] = SyncStatus.DIVERGED - continue + overwrite = ( + on_diverged( + os.fsdecode(rh), + os.fsdecode(fetch_result.refs[lh]), + ) + if on_diverged + else False + ) + if not overwrite: + result[refname] = SyncStatus.DIVERGED + continue + self.repo.refs[rh] = fetch_result.refs[lh] result[refname] = SyncStatus.SUCCESS return result diff --git a/scmrepo/git/backend/gitpython.py b/scmrepo/git/backend/gitpython.py index bafc588d..5d4adb6a 100644 --- a/scmrepo/git/backend/gitpython.py +++ b/scmrepo/git/backend/gitpython.py @@ -479,6 +479,7 @@ def push_refspecs( url: str, refspecs: Union[str, Iterable[str]], force: bool = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, progress: Callable[["GitProgressEvent"], None] = None, **kwargs, ) -> Mapping[str, SyncStatus]: @@ -489,6 +490,7 @@ def fetch_refspecs( url: str, refspecs: Union[str, Iterable[str]], force: bool = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, progress: Callable[["GitProgressEvent"], None] = None, **kwargs, ) -> Mapping[str, SyncStatus]: diff --git a/scmrepo/git/backend/pygit2.py b/scmrepo/git/backend/pygit2.py index 6fdef3a9..58e6e3b0 100644 --- a/scmrepo/git/backend/pygit2.py +++ b/scmrepo/git/backend/pygit2.py @@ -419,6 +419,7 @@ def push_refspecs( url: str, refspecs: Union[str, Iterable[str]], force: bool = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, progress: Callable[["GitProgressEvent"], None] = None, **kwargs, ) -> Mapping[str, SyncStatus]: @@ -429,6 +430,7 @@ def fetch_refspecs( url: str, refspecs: Union[str, Iterable[str]], force: bool = False, + on_diverged: Optional[Callable[[str, str], bool]] = None, progress: Callable[["GitProgressEvent"], None] = None, **kwargs, ) -> Mapping[str, SyncStatus]: