Fix remote sync bugs and improve error handling

Fixes two critical issues:
1. Remote clone/sync was never triggered because wmill sync ran before
   checking if workspace was empty. Reordered flow to check/clone/sync
   git repo BEFORE running wmill sync.

2. Push failures were reported as success because GitPython's push()
   doesn't raise exceptions for rejections. Added explicit checking of
   push result flags (ERROR and REJECTED).

Additional improvements:
- When workspace has files but no .git, delete contents and clone from
  remote to ensure proper sync state
- All three cases now properly sync with remote before Windmill overwrites

New flow:
1. Check workspace state and init/clone/sync git repo
2. Run wmill sync (Windmill overwrites files)
3. Commit and push (with proper error detection)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-09 22:20:52 -05:00
parent f276beae3b
commit 5ad72fe986

View File

@@ -3,6 +3,7 @@
Core sync logic for pulling Windmill workspace and pushing to Git. Core sync logic for pulling Windmill workspace and pushing to Git.
""" """
import os import os
import shutil
import subprocess import subprocess
import logging import logging
from pathlib import Path from pathlib import Path
@@ -223,15 +224,18 @@ def init_or_update_git_repo(config: Dict[str, Any]) -> Repo:
return repo return repo
else: else:
# Workspace has files but no git repo - initialize new repo # Workspace has files but no git repo - delete contents and clone
logger.info("Initializing new Git repository") logger.info("Workspace has files but no git repository, cleaning and cloning from remote")
repo = Repo.init(WORKSPACE_DIR)
# Configure user # Delete all contents in workspace
repo.config_writer().set_value("user", "name", git_user_name).release() for item in WORKSPACE_DIR.iterdir():
repo.config_writer().set_value("user", "email", git_user_email).release() if item.is_dir():
shutil.rmtree(item)
else:
item.unlink()
return repo logger.info("Workspace cleaned, attempting to clone from remote")
return clone_remote_repository(config)
def commit_and_push_changes(repo: Repo, config: Dict[str, Any]) -> bool: def commit_and_push_changes(repo: Repo, config: Dict[str, Any]) -> bool:
@@ -279,9 +283,21 @@ def commit_and_push_changes(repo: Repo, config: Dict[str, Any]) -> bool:
# Push to remote # Push to remote
logger.info(f"Pushing to {git_remote_url} (branch: {git_branch})") logger.info(f"Pushing to {git_remote_url} (branch: {git_branch})")
origin.push(refspec=f'HEAD:{git_branch}', force=False) push_info = origin.push(refspec=f'HEAD:{git_branch}', force=False)
logger.info("Push completed successfully")
# Check push results for errors
if push_info:
for info in push_info:
if info.flags & info.ERROR:
error_msg = f"Push failed: {info.summary}"
logger.error(error_msg)
raise RuntimeError(error_msg)
elif info.flags & info.REJECTED:
error_msg = f"Push rejected (non-fast-forward): {info.summary}"
logger.error(error_msg)
raise RuntimeError(error_msg)
logger.info("Push completed successfully")
return True return True
except GitCommandError as e: except GitCommandError as e:
@@ -312,12 +328,12 @@ def sync_windmill_to_git(config: Dict[str, Any]) -> Dict[str, Any]:
workspace = config.get('workspace', 'admins') workspace = config.get('workspace', 'admins')
# Pull from Windmill # Initialize/update Git repo (must happen BEFORE wmill sync to clone if needed)
run_wmill_sync(config)
# Initialize/update Git repo
repo = init_or_update_git_repo(config) repo = init_or_update_git_repo(config)
# Pull from Windmill (overwrites files with Windmill workspace content)
run_wmill_sync(config)
# Commit and push changes # Commit and push changes
has_changes = commit_and_push_changes(repo, config) has_changes = commit_and_push_changes(repo, config)