git_deploy: validation improvements

Add a check for shallow repos and move validation logic to the
GitRepo class.  Additionally report the real number of commits behind.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2023-07-09 08:06:55 -04:00
parent 51e3568aac
commit 01141fa10e
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
1 changed files with 67 additions and 27 deletions

View File

@ -47,6 +47,7 @@ class GitDeploy(AppDeploy):
async def initialize(self) -> Dict[str, Any]:
storage = await super().initialize()
await self.repo.restore_state(storage)
self._is_valid = self.repo.is_valid()
if not self.needs_refresh():
self.repo.log_repo_info()
return storage
@ -61,16 +62,10 @@ class GitDeploy(AppDeploy):
self._is_valid = False
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")
if self.server.is_debug_enabled():
self._is_valid = True
self.log_info(
"Repo debug enabled, overriding validity checks")
else:
self.log_info("Updates on repo disabled")
self._is_valid = self.repo.is_valid()
if not self._is_valid:
self.log_info("Repo validation check failed, updates disabled")
else:
self._is_valid = True
self.log_info("Validity check for git repo passed")
self._save_state()
@ -103,6 +98,11 @@ class GitDeploy(AppDeploy):
self.notify_status("Attempting Repo Recovery...")
dep_info = await self._collect_dependency_info()
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()
if self.channel != Channel.DEV:
if self.repo.upstream_commit != "?":
@ -287,6 +287,7 @@ class GitRepo:
self.fetch_timeout_handle: Optional[asyncio.Handle] = None
self.fetch_input_recd: bool = False
self.channel = channel
self.is_shallow = False
async def restore_state(self, storage: Dict[str, Any]) -> None:
self.valid_git_repo: bool = storage.get('repo_valid', False)
@ -310,6 +311,7 @@ class GitRepo:
self.head_detached: bool = storage.get('head_detached', False)
self.git_messages: List[str] = storage.get('git_messages', [])
self.commits_behind: List[Dict[str, Any]] = storage.get('commits_behind', [])
self.commits_behind_count: int = storage.get('cbh_count', 0)
self.diverged: bool = storage.get("diverged", False)
self.repo_corrupt: bool = storage.get('corrupt', False)
def_rbs = self.capture_state_for_rollback()
@ -317,6 +319,8 @@ class GitRepo:
self.rollback_branch: str = storage.get('rollback_branch', def_rbs["branch"])
rbv = storage.get('rollback_version', self.current_version)
self.rollback_version = GitVersion(str(rbv))
if not await self._detect_git_dir():
self.valid_git_repo = False
if self.valid_git_repo:
await self.set_current_instance()
self._check_warnings()
@ -341,6 +345,7 @@ class GitRepo:
'head_detached': self.head_detached,
'git_messages': self.git_messages,
'commits_behind': self.commits_behind,
'cbh_count': self.commits_behind_count,
'diverged': self.diverged,
'corrupt': self.repo_corrupt
}
@ -402,8 +407,8 @@ class GitRepo:
# Get Commits Behind
self.commits_behind = []
cbh = await self.get_commits_behind()
if cbh:
if self.commits_behind_count > 0:
cbh = await self.get_commits_behind()
tagged_commits = await self.get_tagged_commits()
debug_msg = '\n'.join([f"{k}: {v}" for k, v in tagged_commits.items()])
logging.debug(f"Git Repo {self.alias}: Tagged Commits\n{debug_msg}")
@ -431,16 +436,7 @@ class GitRepo:
async def _check_repo_status(self) -> bool:
async with self.git_operation_lock:
self.valid_git_repo = False
if self.git_folder_path.is_file():
# Submodules have a file that contain the path to
# the .git folder
eventloop = self.server.get_event_loop()
data = await eventloop.run_in_thread(self.git_folder_path.read_text)
ident, _, gitdir = data.partition(":")
if ident.strip() != "gitdir" or not gitdir.strip():
return False
self.git_folder_path = pathlib.Path(gitdir).expanduser().resolve()
if not self.git_folder_path.is_dir():
if not await self._detect_git_dir():
logging.info(
f"Git Repo {self.alias}: path '{self.src_path}'"
" is not a valid git repo")
@ -469,6 +465,21 @@ class GitRepo:
await self.set_current_instance()
return True
async def _detect_git_dir(self) -> bool:
if self.git_folder_path.is_file():
# Submodules have a file that contain the path to
# the .git folder
eventloop = self.server.get_event_loop()
data = await eventloop.run_in_thread(self.git_folder_path.read_text)
ident, _, gitdir = data.partition(":")
if ident.strip() != "gitdir" or not gitdir.strip():
return False
self.git_folder_path = pathlib.Path(gitdir).expanduser().resolve()
if self.git_folder_path.is_dir():
self.is_shallow = self.git_folder_path.joinpath("shallow").is_file()
return True
return False
async def _find_current_branch(self) -> None:
# Populate list of current branches
blist = await self.list_branches()
@ -559,6 +570,7 @@ class GitRepo:
return moved
async def _get_upstream_version(self) -> GitVersion:
self.commits_behind_count = 0
if self.channel == Channel.DEV:
self.upstream_commit = await self.rev_parse(
f"{self.git_remote}/{self.git_branch}"
@ -581,6 +593,9 @@ class GitRepo:
upstream_ver_str = tag
break
self.upstream_commit = upstream_commit
if self.upstream_commit != "?":
rl_args = f"HEAD..{self.upstream_commit} --count"
self.commits_behind_count = int(await self.rev_list(rl_args))
return GitVersion(upstream_ver_str)
async def _set_versions(
@ -612,8 +627,7 @@ class GitRepo:
count = await self.rev_list(f"{self.upstream_commit} --count")
else:
log_msg += "\nRemote has diverged, approximating dev count. "
count = await self.rev_list(f"{self.upstream_commit}..HEAD --count")
count = str(int(count) + current_version.dev_count)
count = str(self.commits_behind_count + current_version.dev_count)
upstream_version = GitVersion(f"{tag}-{count}-inferred")
log_msg += f"Falling back to inferred version: {upstream_version}"
logging.info(log_msg)
@ -675,7 +689,8 @@ class GitRepo:
f"Rollback Version: {self.rollback_version}\n"
f"Is Dirty: {self.current_version.dirty}\n"
f"Is Detached: {self.head_detached}\n"
f"Commits Behind: {len(self.commits_behind)}\n"
f"Is Shallow: {self.is_shallow}\n"
f"Commits Behind Count: {self.commits_behind_count}\n"
f"Diverged: {self.diverged}"
f"{warnings}"
)
@ -699,6 +714,8 @@ class GitRepo:
self.repo_warnings.append("Detached HEAD detected")
if self.diverged:
self.repo_warnings.append("Repo has diverged from remote")
if self.is_dirty():
self.repo_warnings.append("Repo has modified files (dirty)")
if len(self.managing_instances) > 1:
instances = "\n".join([f" {ins}" for ins in self.managing_instances])
self.repo_warnings.append(
@ -711,9 +728,6 @@ class GitRepo:
ro_msg += "\n".join(self.repo_warnings)
self.server.add_log_rollover_item(f"umgr_{self.alias}_warn", ro_msg, log=False)
def check_is_valid(self):
return not self.head_detached and not self.diverged
def _verify_repo(self, check_remote: bool = False) -> None:
if not self.valid_git_repo:
raise self.server.error(
@ -1010,6 +1024,7 @@ class GitRepo:
'is_dirty': self.current_version.dirty,
'detached': self.head_detached,
'commits_behind': self.commits_behind,
'commits_behind_count': self.commits_behind_count,
'git_messages': self.git_messages,
'full_version_string': self.current_version.full_version,
'pristine': not self.current_version.dirty,
@ -1029,6 +1044,31 @@ class GitRepo:
def is_current(self) -> bool:
return self.current_commit == self.upstream_commit
def is_submodule_or_worktree(self):
return (
self.src_path.joinpath(".git").is_file() and
self.git_folder_path.parent.name in ("modules", "worktrees")
)
def is_valid(self) -> bool:
return (
not self.is_damaged() and
not self.has_recoverable_errors()
)
def is_damaged(self) -> bool:
# A damaged repo requires a clone to recover
return not self.valid_git_repo or self.repo_corrupt
def has_recoverable_errors(self) -> bool:
# These errors should be recoverable using a git reset
detached_err = False if self.server.is_debug_enabled() else self.head_detached
return (
self.diverged or
self.is_dirty() or
detached_err
)
async def _check_lock_file_exists(self, remove: bool = False) -> bool:
lock_path = self.git_folder_path.joinpath("index.lock")
if lock_path.is_file():