git_deploy: refactor initialization

Use git-branch to determine current the current branch and
head state instead of git-status.  The former is a porcelain
command with guaranteed output.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2023-07-07 12:18:28 -04:00
parent 3f1e9e397e
commit 270c78d8af
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
1 changed files with 77 additions and 69 deletions

View File

@ -58,7 +58,7 @@ class GitDeploy(AppDeploy):
async def _update_repo_state(self, need_fetch: bool = True) -> None:
self._is_valid = False
await self.repo.initialize(need_fetch=need_fetch)
await self.repo.refresh_repo_state(need_fetch=need_fetch)
self.log_info(f"Channel: {self.channel}")
if not self.repo.check_is_valid():
self.log_info("Repo validation check failed")
@ -336,7 +336,7 @@ class GitRepo:
'corrupt': self.repo_corrupt
}
async def initialize(self, need_fetch: bool = True) -> None:
async def refresh_repo_state(self, need_fetch: bool = True) -> None:
if self.init_evt is not None:
# No need to initialize multiple requests
await self.init_evt.wait()
@ -346,12 +346,9 @@ class GitRepo:
self.init_evt = asyncio.Event()
self.git_messages.clear()
try:
await self.update_repo_status()
await self._check_repo_status()
self._verify_repo()
if not self.head_detached:
# lookup remote via git config
self.git_remote = await self.get_config_item(
f"branch.{self.git_branch}.remote")
await self._find_current_branch()
# Fetch the upstream url. If the repo has been moved,
# set the new url
@ -377,17 +374,6 @@ class GitRepo:
await self.fetch()
self.diverged = await self.check_diverged()
# Populate list of current branches
blist = await self.list_branches()
self.branches = []
for branch in blist:
branch = branch.strip()
if branch[0] == "*":
branch = branch[2:]
if branch[0] == "(":
continue
self.branches.append(branch)
# Parse GitHub Owner from URL
owner_match = re.match(r"https?://[^/]+/([^/]+)", self.upstream_url)
self.git_owner = "?"
@ -433,6 +419,78 @@ class GitRepo:
self.init_evt.set()
self.init_evt = None
async def _check_repo_status(self) -> bool:
async with self.git_operation_lock:
self.valid_git_repo = False
if not self.git_path.joinpath(".git").exists():
logging.info(
f"Git Repo {self.alias}: path '{self.git_path}'"
" is not a valid git repo")
return False
await self._wait_for_lock_release()
retries = 3
while retries:
self.git_messages.clear()
try:
cmd = "status --porcelain -b"
resp: Optional[str] = await self._run_git_cmd(cmd, retries=1)
except Exception:
retries -= 1
resp = None
# Attempt to recover from "loose object" error
if retries and self.repo_corrupt:
if not await self._repair_loose_objects():
# Since we are unable to recover, immediately
# return
return False
else:
break
if resp is None:
return False
self.valid_git_repo = True
return True
async def _find_current_branch(self) -> None:
# Populate list of current branches
blist = await self.list_branches()
current_branch = ""
self.branches = []
for branch in blist:
branch = branch.strip()
if not branch:
continue
if branch[0] == "*":
branch = branch[2:].strip()
current_branch = branch
if branch[0] == "(":
continue
self.branches.append(branch)
if current_branch.startswith("(HEAD detached"):
self.head_detached = True
ref_name = current_branch.split()[-1][:-1]
remote_list = (await self.remote("")).splitlines()
for remote in remote_list:
remote = remote.strip()
if not remote:
continue
if ref_name.startswith(remote):
self.git_branch = ref_name[len(remote)+1:]
self.git_remote = remote
break
else:
if self.git_remote == "?":
msg = "Resolve by manually checking out a branch via SSH."
else:
prev = f"{self.git_remote}/{self.git_branch}"
msg = f"Defaulting to previously tracked {prev}."
logging.info(f"Git Repo {self.alias}: {current_branch} {msg}")
else:
self.head_detached = False
self.git_branch = current_branch
self.git_remote = await self.get_config_item(
f"branch.{self.git_branch}.remote"
)
async def _check_moved_origin(self) -> bool:
detected_origin = self.upstream_url.lower().strip()
if not detected_origin.endswith(".git"):
@ -557,56 +615,6 @@ class GitRepo:
raise self.server.error(
f"Git Repo {self.alias}: Initialization failure")
async def update_repo_status(self) -> bool:
async with self.git_operation_lock:
self.valid_git_repo = False
if not self.git_path.joinpath(".git").exists():
logging.info(
f"Git Repo {self.alias}: path '{self.git_path}'"
" is not a valid git repo")
return False
await self._wait_for_lock_release()
retries = 3
while retries:
self.git_messages.clear()
try:
resp: Optional[str] = await self._run_git_cmd(
"status -u no", retries=1)
except Exception:
retries -= 1
resp = None
# Attempt to recover from "loose object" error
if retries and self.repo_corrupt:
if not await self._repair_loose_objects():
# Since we are unable to recover, immediately
# return
return False
else:
break
if resp is None:
return False
resp = resp.strip().split('\n', 1)[0]
self.head_detached = resp.startswith("HEAD detached")
branch_info = resp.split()[-1]
if self.head_detached:
bparts = branch_info.split("/", 1)
if len(bparts) == 2:
self.git_remote, self.git_branch = bparts
else:
if self.git_remote == "?":
msg = "Resolve by manually checking out" \
" a branch via SSH."
else:
msg = "Defaulting to previously tracked " \
f"{self.git_remote}/{self.git_branch}."
logging.info(
f"Git Repo {self.alias}: HEAD detached on untracked "
f"commit {branch_info}. {msg}")
else:
self.git_branch = branch_info
self.valid_git_repo = True
return True
async def check_diverged(self) -> bool:
self._verify_repo(check_remote=True)
if self.head_detached:
@ -726,7 +734,7 @@ class GitRepo:
async def list_branches(self) -> List[str]:
self._verify_repo()
async with self.git_operation_lock:
resp = await self._run_git_cmd("branch --list")
resp = await self._run_git_cmd("branch --list --no-color")
return resp.strip().split("\n")
async def remote(self, command: str) -> str: