git_deploy: improve recovery implementation
After performing a recovery attempt to reset to the commit the repo was at prior to the repo failure. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
4443bfbd41
commit
0437d1623e
|
@ -98,26 +98,20 @@ class GitDeploy(AppDeploy):
|
||||||
self.notify_status("Attempting Repo Recovery...")
|
self.notify_status("Attempting Repo Recovery...")
|
||||||
dep_info = await self._collect_dependency_info()
|
dep_info = await self._collect_dependency_info()
|
||||||
if hard:
|
if hard:
|
||||||
if self.repo.is_submodule_or_worktree():
|
|
||||||
raise self.server.error(
|
|
||||||
f"Cannot re-clone git repo {self.name}, it is either "
|
|
||||||
f"a submodule or worktree."
|
|
||||||
)
|
|
||||||
await self.repo.clone()
|
await self.repo.clone()
|
||||||
if self.channel != Channel.DEV:
|
|
||||||
if self.repo.upstream_commit != "?":
|
|
||||||
# If on beta or stable reset to the latest tagged
|
|
||||||
# upstream commit
|
|
||||||
await self.repo.reset()
|
|
||||||
else:
|
|
||||||
self.notify_status(
|
|
||||||
f"No upstream commit for repo on {self.channel} channel, "
|
|
||||||
"skipping reset."
|
|
||||||
)
|
|
||||||
await self._update_repo_state()
|
await self._update_repo_state()
|
||||||
else:
|
else:
|
||||||
self.notify_status("Resetting Git Repo...")
|
self.notify_status("Resetting Git Repo...")
|
||||||
await self.repo.reset()
|
reset_ref = await self.repo.get_recovery_ref()
|
||||||
|
if self.repo.is_dirty():
|
||||||
|
# Try to restore modified files. If the attempt fails we
|
||||||
|
# can still try the reset
|
||||||
|
try:
|
||||||
|
await self.repo.checkout("-- .")
|
||||||
|
except self.server.error:
|
||||||
|
pass
|
||||||
|
await self.repo.checkout(self.primary_branch)
|
||||||
|
await self.repo.reset(reset_ref)
|
||||||
await self._update_repo_state()
|
await self._update_repo_state()
|
||||||
self.repo.set_rollback_state(None)
|
self.repo.set_rollback_state(None)
|
||||||
|
|
||||||
|
@ -647,24 +641,29 @@ class GitRepo:
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
f"Git Repo {self.alias}: Initialization failure")
|
f"Git Repo {self.alias}: Initialization failure")
|
||||||
|
|
||||||
async def check_diverged(self) -> bool:
|
async def is_ancestor(self, ancestor_ref: str, descendent_ref: str) -> bool:
|
||||||
self._verify_repo(check_remote=True)
|
self._verify_repo()
|
||||||
if self.head_detached:
|
cmd = f"merge-base --is-ancestor {ancestor_ref} {descendent_ref}"
|
||||||
return False
|
|
||||||
async with self.git_operation_lock:
|
async with self.git_operation_lock:
|
||||||
cmd = f"merge-base --is-ancestor HEAD {self.git_remote}/{self.git_branch}"
|
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
try:
|
try:
|
||||||
await self._run_git_cmd(cmd, retries=1, corrupt_msg="error: ")
|
await self._run_git_cmd(cmd, retries=1, corrupt_msg="error: ")
|
||||||
except self.cmd_helper.scmd_error as err:
|
except self.cmd_helper.scmd_error as err:
|
||||||
if err.return_code == 1:
|
if err.return_code == 1:
|
||||||
return True
|
return False
|
||||||
if self.repo_corrupt:
|
if self.repo_corrupt:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
await asyncio.sleep(.5)
|
await asyncio.sleep(.2)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def check_diverged(self) -> bool:
|
||||||
|
self._verify_repo(check_remote=True)
|
||||||
|
if self.head_detached:
|
||||||
return False
|
return False
|
||||||
|
descendent = f"{self.git_remote}/{self.git_branch}"
|
||||||
|
return not (await self.is_ancestor("HEAD", descendent))
|
||||||
|
|
||||||
def log_repo_info(self) -> None:
|
def log_repo_info(self) -> None:
|
||||||
warnings = ""
|
warnings = ""
|
||||||
|
@ -868,6 +867,11 @@ class GitRepo:
|
||||||
await self._run_git_cmd("fsck --full", timeout=300., retries=1)
|
await self._run_git_cmd("fsck --full", timeout=300., retries=1)
|
||||||
|
|
||||||
async def clone(self) -> None:
|
async def clone(self) -> None:
|
||||||
|
if self.is_submodule_or_worktree():
|
||||||
|
raise self.server.error(
|
||||||
|
f"Cannot clone git repo {self.alias}, it is a {self.get_repo_type()} "
|
||||||
|
"of another git repo."
|
||||||
|
)
|
||||||
async with self.git_operation_lock:
|
async with self.git_operation_lock:
|
||||||
if self.recovery_url == "?":
|
if self.recovery_url == "?":
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
|
@ -891,8 +895,20 @@ class GitRepo:
|
||||||
await event_loop.run_in_thread(
|
await event_loop.run_in_thread(
|
||||||
shutil.move, str(self.backup_path), str(self.src_path))
|
shutil.move, str(self.backup_path), str(self.src_path))
|
||||||
self.repo_corrupt = False
|
self.repo_corrupt = False
|
||||||
|
self.valid_git_repo = True
|
||||||
self.cmd_helper.notify_update_response(
|
self.cmd_helper.notify_update_response(
|
||||||
f"Git Repo {self.alias}: Git Clone Complete")
|
f"Git Repo {self.alias}: Git Clone Complete")
|
||||||
|
if self.current_commit != "?":
|
||||||
|
try:
|
||||||
|
can_reset = await self.is_ancestor(self.current_commit, "HEAD")
|
||||||
|
except self.server.error:
|
||||||
|
can_reset = False
|
||||||
|
if can_reset:
|
||||||
|
self.cmd_helper.notify_update_response(
|
||||||
|
f"Git Repo {self.alias}: Moving HEAD to previous "
|
||||||
|
f"commit {self.current_commit}"
|
||||||
|
)
|
||||||
|
await self.reset(self.current_commit)
|
||||||
|
|
||||||
async def rollback(self) -> bool:
|
async def rollback(self) -> bool:
|
||||||
if self.rollback_commit == "?" or self.rollback_branch == "?":
|
if self.rollback_commit == "?" or self.rollback_branch == "?":
|
||||||
|
@ -1069,6 +1085,35 @@ class GitRepo:
|
||||||
detached_err
|
detached_err
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_repo_type(self) -> str:
|
||||||
|
type_name = self.git_folder_path.parent.name
|
||||||
|
if type_name == "modules":
|
||||||
|
return "submodule"
|
||||||
|
elif type_name == "worktrees":
|
||||||
|
return "worktree"
|
||||||
|
return "repo"
|
||||||
|
|
||||||
|
async def get_recovery_ref(self) -> str:
|
||||||
|
""" Fetch the best reference for a 'reset' recovery attempt
|
||||||
|
|
||||||
|
Returns the ref to reset to for "soft" recovery requests. The
|
||||||
|
preference is to reset to the current commit, however that is
|
||||||
|
only possible if the commit is known and if it is an ancestor of
|
||||||
|
the primary branch.
|
||||||
|
"""
|
||||||
|
remote = await self.config_get(f"branch.{self.primary_branch}.remote")
|
||||||
|
if remote is None:
|
||||||
|
raise self.server.error(
|
||||||
|
f"Failed to find remote for primary branch '{self.primary_branch}'"
|
||||||
|
)
|
||||||
|
upstream_ref = f"{remote}/{self.primary_branch}"
|
||||||
|
if (
|
||||||
|
self.current_commit != "?" and
|
||||||
|
await self.is_ancestor(self.current_commit, upstream_ref)
|
||||||
|
):
|
||||||
|
return self.current_commit
|
||||||
|
return upstream_ref
|
||||||
|
|
||||||
async def _check_lock_file_exists(self, remove: bool = False) -> bool:
|
async def _check_lock_file_exists(self, remove: bool = False) -> bool:
|
||||||
lock_path = self.git_folder_path.joinpath("index.lock")
|
lock_path = self.git_folder_path.joinpath("index.lock")
|
||||||
if lock_path.is_file():
|
if lock_path.is_file():
|
||||||
|
|
Loading…
Reference in New Issue