update_manager: Implement clone method for GitRepo class

Replace the existing rsync "hard" recovery method with a call to git clone.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Arksine 2021-05-20 18:06:01 -04:00
parent 81f2393b46
commit 58f7aa0a57
1 changed files with 49 additions and 26 deletions

View File

@ -518,6 +518,8 @@ class CommandHelper:
resp: Union[str, bytes], resp: Union[str, bytes],
is_complete: bool = False is_complete: bool = False
) -> None: ) -> None:
if self.cur_update_app is None:
return
resp = resp.strip() resp = resp.strip()
if isinstance(resp, bytes): if isinstance(resp, bytes):
resp = resp.decode() resp = resp.decode()
@ -667,16 +669,12 @@ class GitUpdater(BaseUpdater):
f"Repo validation checks failed:\n{msgs}") f"Repo validation checks failed:\n{msgs}")
if self.debug: if self.debug:
self.is_valid = True self.is_valid = True
if not self.repo.is_dirty():
await self.repo.backup_repo()
self._log_info( self._log_info(
"Repo debug enabled, overriding validity checks") "Repo debug enabled, overriding validity checks")
else: else:
self._log_info("Updates on repo disabled") self._log_info("Updates on repo disabled")
else: else:
self.is_valid = True self.is_valid = True
if not self.repo.is_dirty():
await self.repo.backup_repo()
self._log_info("Validity check for git repo passed") self._log_info("Validity check for git repo passed")
async def update(self) -> None: async def update(self) -> None:
@ -863,13 +861,8 @@ class GitUpdater(BaseUpdater):
npm_mtime = self._get_file_mtime(self.npm_pkg_json) npm_mtime = self._get_file_mtime(self.npm_pkg_json)
if hard: if hard:
self._notify_status("Restoring repo from backup...") await self.repo.clone()
if os.path.exists(self.repo_path):
shutil.rmtree(self.repo_path)
os.mkdir(self.repo_path)
await self.repo.restore_repo()
await self._update_repo_state() await self._update_repo_state()
await self._pull_repo()
else: else:
self._notify_status("Resetting Git Repo...") self._notify_status("Resetting Git Repo...")
await self.repo.reset() await self.repo.reset()
@ -894,8 +887,8 @@ class GitUpdater(BaseUpdater):
return status return status
GIT_FETCH_TIMEOUT = 300. GIT_ASYNC_TIMEOUT = 300.
GIT_FETCH_ENV_VARS = { GIT_ENV_VARS = {
'GIT_HTTP_LOW_SPEED_LIMIT': "1000", 'GIT_HTTP_LOW_SPEED_LIMIT': "1000",
'GIT_HTTP_LOW_SPEED_TIME ': "20" 'GIT_HTTP_LOW_SPEED_TIME ': "20"
} }
@ -1201,6 +1194,26 @@ class GitRepo:
async with self.git_operation_lock: async with self.git_operation_lock:
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 with self.git_operation_lock:
self.cmd_helper.notify_update_response(
f"Git Repo {self.alias}: Starting Clone Recovery...")
if os.path.exists(self.backup_path):
shutil.rmtree(self.backup_path)
self._check_lock_file_exists(remove=True)
git_cmd = f"clone {self.origin_url} {self.backup_path}"
try:
await self._run_git_cmd_async(git_cmd, 1, False, False)
except Exception as e:
self.cmd_helper.notify_update_response(
f"Git Repo {self.alias}: Git Clone Failed")
raise self.server.error("Git Clone Error") from e
if os.path.exists(self.git_path):
shutil.rmtree(self.git_path)
shutil.move(self.backup_path, self.git_path)
self.cmd_helper.notify_update_response(
f"Git Repo {self.alias}: Git Clone Complete")
async def get_commits_behind(self) -> List[Dict[str, Any]]: async def get_commits_behind(self) -> List[Dict[str, Any]]:
self._verify_repo() self._verify_repo()
if self.is_current(): if self.is_current():
@ -1339,14 +1352,22 @@ class GitRepo:
return return
self._check_lock_file_exists(remove=True) self._check_lock_file_exists(remove=True)
async def _run_git_cmd_async(self, cmd: str, retries: int = 5) -> None: async def _run_git_cmd_async(self,
cmd: str,
retries: int = 5,
need_git_path: bool = True,
fix_loose: bool = True
) -> None:
# Fetch and pull require special handling. If the request # Fetch and pull require special handling. If the request
# gets delayed we do not want to terminate it while the command # gets delayed we do not want to terminate it while the command
# is processing. # is processing.
await self._wait_for_lock_release() await self._wait_for_lock_release()
env = os.environ.copy() env = os.environ.copy()
env.update(GIT_FETCH_ENV_VARS) env.update(GIT_ENV_VARS)
git_cmd = f"git -C {self.git_path} {cmd}" if need_git_path:
git_cmd = f"git -C {self.git_path} {cmd}"
else:
git_cmd = f"git {cmd}"
scmd = self.cmd_helper.build_shell_command( scmd = self.cmd_helper.build_shell_command(
git_cmd, callback=self._handle_process_output, git_cmd, callback=self._handle_process_output,
env=env) env=env)
@ -1355,8 +1376,8 @@ class GitRepo:
ioloop = IOLoop.current() ioloop = IOLoop.current()
self.fetch_input_recd = False self.fetch_input_recd = False
self.fetch_timeout_handle = ioloop.call_later( self.fetch_timeout_handle = ioloop.call_later(
GIT_FETCH_TIMEOUT, self._check_process_active, # type: ignore GIT_ASYNC_TIMEOUT, self._check_process_active, # type: ignore
scmd) scmd, cmd)
try: try:
await scmd.run(timeout=0) await scmd.run(timeout=0)
except Exception: except Exception:
@ -1366,7 +1387,7 @@ class GitRepo:
if ret == 0: if ret == 0:
self.git_messages.clear() self.git_messages.clear()
return return
elif "loose object" in "\n".join(self.git_messages): elif fix_loose and "loose object" in "\n".join(self.git_messages):
# attempt to remove corrupt objects # attempt to remove corrupt objects
try: try:
await self.cmd_helper.run_cmd_with_response( await self.cmd_helper.run_cmd_with_response(
@ -1384,28 +1405,30 @@ class GitRepo:
out = output.decode().strip() out = output.decode().strip()
if out: if out:
self.git_messages.append(out) self.git_messages.append(out)
logging.debug( self.cmd_helper.notify_update_response(out)
f"Git Repo {self.alias}: Fetch/Pull Response: {out}") logging.debug(
f"Git Repo {self.alias}: {out}")
async def _check_process_active(self, async def _check_process_active(self,
scmd: shell_command.ShellCommand scmd: shell_command.ShellCommand,
cmd_name: str
) -> None: ) -> None:
ret = scmd.get_return_code() ret = scmd.get_return_code()
if ret is not None: if ret is not None:
logging.debug(f"Git Repo {self.alias}: Fetch/Pull returned") logging.debug(f"Git Repo {self.alias}: {cmd_name} returned")
return return
if self.fetch_input_recd: if self.fetch_input_recd:
# Received some input, reschedule timeout # Received some input, reschedule timeout
logging.debug( logging.debug(
f"Git Repo {self.alias}: Fetch/Pull active, rescheduling") f"Git Repo {self.alias}: {cmd_name} active, rescheduling")
ioloop = IOLoop.current() ioloop = IOLoop.current()
self.fetch_input_recd = False self.fetch_input_recd = False
self.fetch_timeout_handle = ioloop.call_later( self.fetch_timeout_handle = ioloop.call_later(
GIT_FETCH_TIMEOUT, self._check_process_active, # type: ignore GIT_ASYNC_TIMEOUT, self._check_process_active, # type: ignore
scmd) scmd, cmd_name)
else: else:
# Request has timed out with no input, terminate it # Request has timed out with no input, terminate it
logging.debug(f"Git Repo {self.alias}: Fetch/Pull timed out") logging.debug(f"Git Repo {self.alias}: {cmd_name} timed out")
# Cancel with SIGKILL # Cancel with SIGKILL
await scmd.cancel(2) await scmd.cancel(2)