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],
is_complete: bool = False
) -> None:
if self.cur_update_app is None:
return
resp = resp.strip()
if isinstance(resp, bytes):
resp = resp.decode()
@ -667,16 +669,12 @@ class GitUpdater(BaseUpdater):
f"Repo validation checks failed:\n{msgs}")
if self.debug:
self.is_valid = True
if not self.repo.is_dirty():
await self.repo.backup_repo()
self._log_info(
"Repo debug enabled, overriding validity checks")
else:
self._log_info("Updates on repo disabled")
else:
self.is_valid = True
if not self.repo.is_dirty():
await self.repo.backup_repo()
self._log_info("Validity check for git repo passed")
async def update(self) -> None:
@ -863,13 +861,8 @@ class GitUpdater(BaseUpdater):
npm_mtime = self._get_file_mtime(self.npm_pkg_json)
if hard:
self._notify_status("Restoring repo from backup...")
if os.path.exists(self.repo_path):
shutil.rmtree(self.repo_path)
os.mkdir(self.repo_path)
await self.repo.restore_repo()
await self.repo.clone()
await self._update_repo_state()
await self._pull_repo()
else:
self._notify_status("Resetting Git Repo...")
await self.repo.reset()
@ -894,8 +887,8 @@ class GitUpdater(BaseUpdater):
return status
GIT_FETCH_TIMEOUT = 300.
GIT_FETCH_ENV_VARS = {
GIT_ASYNC_TIMEOUT = 300.
GIT_ENV_VARS = {
'GIT_HTTP_LOW_SPEED_LIMIT': "1000",
'GIT_HTTP_LOW_SPEED_TIME ': "20"
}
@ -1201,6 +1194,26 @@ class GitRepo:
async with self.git_operation_lock:
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]]:
self._verify_repo()
if self.is_current():
@ -1339,14 +1352,22 @@ class GitRepo:
return
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
# gets delayed we do not want to terminate it while the command
# is processing.
await self._wait_for_lock_release()
env = os.environ.copy()
env.update(GIT_FETCH_ENV_VARS)
git_cmd = f"git -C {self.git_path} {cmd}"
env.update(GIT_ENV_VARS)
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(
git_cmd, callback=self._handle_process_output,
env=env)
@ -1355,8 +1376,8 @@ class GitRepo:
ioloop = IOLoop.current()
self.fetch_input_recd = False
self.fetch_timeout_handle = ioloop.call_later(
GIT_FETCH_TIMEOUT, self._check_process_active, # type: ignore
scmd)
GIT_ASYNC_TIMEOUT, self._check_process_active, # type: ignore
scmd, cmd)
try:
await scmd.run(timeout=0)
except Exception:
@ -1366,7 +1387,7 @@ class GitRepo:
if ret == 0:
self.git_messages.clear()
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
try:
await self.cmd_helper.run_cmd_with_response(
@ -1384,28 +1405,30 @@ class GitRepo:
out = output.decode().strip()
if out:
self.git_messages.append(out)
logging.debug(
f"Git Repo {self.alias}: Fetch/Pull Response: {out}")
self.cmd_helper.notify_update_response(out)
logging.debug(
f"Git Repo {self.alias}: {out}")
async def _check_process_active(self,
scmd: shell_command.ShellCommand
scmd: shell_command.ShellCommand,
cmd_name: str
) -> None:
ret = scmd.get_return_code()
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
if self.fetch_input_recd:
# Received some input, reschedule timeout
logging.debug(
f"Git Repo {self.alias}: Fetch/Pull active, rescheduling")
f"Git Repo {self.alias}: {cmd_name} active, rescheduling")
ioloop = IOLoop.current()
self.fetch_input_recd = False
self.fetch_timeout_handle = ioloop.call_later(
GIT_FETCH_TIMEOUT, self._check_process_active, # type: ignore
scmd)
GIT_ASYNC_TIMEOUT, self._check_process_active, # type: ignore
scmd, cmd_name)
else:
# 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
await scmd.cancel(2)