diff --git a/svn_mirror/git_to_svn.py b/svn_mirror/git_to_svn.py index 19f3ce3..f99c1e2 100644 --- a/svn_mirror/git_to_svn.py +++ b/svn_mirror/git_to_svn.py @@ -129,15 +129,60 @@ def _ensure_wc(mirror: Mirror, svn_branch: str): if not (wc / ".svn").exists(): logger.info("Checking out SVN WC for %s …", svn_branch) wc.mkdir(parents=True, exist_ok=True) - _run_svn(["checkout", "--ignore-externals", svn_url, str(wc)], - timeout=300, auth_args=auth_args) + try: + _run_svn(["checkout", "--ignore-externals", svn_url, str(wc)], + timeout=900, auth_args=auth_args) + except GitToSVNError: + _cleanup_failed_wc(wc) + raise else: - logger.debug("Updating SVN WC for %s …", svn_branch) - _run_svn(["revert", "-R", "."], timeout=120, wc=wc, auth_args=auth_args) - _run_svn(["update", "--ignore-externals"], timeout=120, wc=wc, auth_args=auth_args) + _try_revert(wc, auth_args) + try: + _run_svn(["update", "--ignore-externals"], timeout=120, + wc=wc, auth_args=auth_args) + except GitToSVNError as e: + if _is_lock_error(e): + logger.warning("WC locked – running cleanup for %s", svn_branch) + _run_svn(["cleanup"], timeout=120, wc=wc, auth_args=auth_args) + _try_revert(wc, auth_args) + _run_svn(["update", "--ignore-externals"], timeout=120, + wc=wc, auth_args=auth_args) + else: + raise return wc +def _is_lock_error(e: GitToSVNError) -> bool: + return "E155004" in str(e) or "locked" in str(e).lower() + + +def _try_revert(wc: Path, auth_args: Optional[list]): + try: + _run_svn(["revert", "-R", "."], timeout=120, wc=wc, auth_args=auth_args) + except GitToSVNError as e: + if _is_lock_error(e): + logger.warning("WC locked during revert – running cleanup") + _run_svn(["cleanup"], timeout=120, wc=wc, auth_args=auth_args) + _run_svn(["revert", "-R", "."], timeout=120, + wc=wc, auth_args=auth_args) + else: + raise + + +def _cleanup_failed_wc(wc: Path): + """Remove a partial/failed SVN working copy.""" + import shutil + import time + for attempt in range(3): + try: + if wc.exists(): + shutil.rmtree(wc) + return + except PermissionError: + time.sleep(1) + logger.warning("Could not fully remove failed WC at %s", wc) + + # ─── Author mapping (reverse) ──────────────────────────────────