"""ExternalStatus Class to store status and state information about repositories and create a string representation. """ from __future__ import absolute_import from __future__ import unicode_literals from __future__ import print_function from .global_constants import EMPTY_STR from .utils import printlog, indent_string from .global_constants import VERBOSITY_VERBOSE, VERBOSITY_DUMP class ExternalStatus(object): """Class to represent the status of a given source repository or tree. Individual repositories determine their own status in the Repository objects. This object is just resposible for storing the information and passing it up to a higher level for reporting or global decisions. There are two states of concern: * If the repository is in-sync with the externals description file. * If the repostiory working copy is clean and there are no pending transactions (e.g. add, remove, rename, untracked files). """ DEFAULT = '-' UNKNOWN = '?' EMPTY = 'e' MODEL_MODIFIED = 's' # a.k.a. out-of-sync DIRTY = 'M' STATUS_OK = ' ' STATUS_ERROR = '!' # source types OPTIONAL = 'o' STANDALONE = 's' MANAGED = ' ' def __init__(self): self.sync_state = self.DEFAULT self.clean_state = self.DEFAULT self.source_type = self.DEFAULT self.path = EMPTY_STR self.current_version = EMPTY_STR self.expected_version = EMPTY_STR self.status_output = EMPTY_STR def log_status_message(self, verbosity): """Write status message to the screen and log file """ self._default_status_message() if verbosity >= VERBOSITY_VERBOSE: self._verbose_status_message() if verbosity >= VERBOSITY_DUMP: self._dump_status_message() def _default_status_message(self): """Return the default terse status message string """ msg = '{sync}{clean}{src_type} {path}'.format( sync=self.sync_state, clean=self.clean_state, src_type=self.source_type, path=self.path) printlog(msg) def _verbose_status_message(self): """Return the verbose status message string """ clean_str = self.DEFAULT if self.clean_state == self.STATUS_OK: clean_str = 'clean sandbox' elif self.clean_state == self.DIRTY: clean_str = 'modified sandbox' sync_str = 'on {0}'.format(self.current_version) if self.sync_state != self.STATUS_OK: sync_str = '{current} --> {expected}'.format( current=self.current_version, expected=self.expected_version) msg = ' {clean}, {sync}'.format(clean=clean_str, sync=sync_str) printlog(msg) def _dump_status_message(self): """Return the dump status message string """ msg = indent_string(self.status_output, 12) printlog(msg) def safe_to_update(self): """Report if it is safe to update a repository. Safe is defined as: * If a repository is empty, it is safe to update. * If a repository exists and has a clean working copy state with no pending transactions. """ safe_to_update = False repo_exists = self.exists() if not repo_exists: safe_to_update = True else: # If the repo exists, it must be in ok or modified # sync_state. Any other sync_state at this point # represents a logic error that should have been handled # before now! sync_safe = ((self.sync_state == ExternalStatus.STATUS_OK) or (self.sync_state == ExternalStatus.MODEL_MODIFIED)) if sync_safe: # The clean_state must be STATUS_OK to update. Otherwise we # are dirty or there was a missed error previously. if self.clean_state == ExternalStatus.STATUS_OK: safe_to_update = True return safe_to_update def exists(self): """Determine if the repo exists. This is indicated by: * sync_state is not EMPTY * if the sync_state is empty, then the valid states for clean_state are default, empty or unknown. Anything else and there was probably an internal logic error. NOTE(bja, 2017-10) For the moment we are considering a sync_state of default or unknown to require user intervention, but we may want to relax this convention. This is probably a result of a network error or internal logic error but more testing is needed. """ is_empty = (self.sync_state == ExternalStatus.EMPTY) clean_valid = ((self.clean_state == ExternalStatus.DEFAULT) or (self.clean_state == ExternalStatus.EMPTY) or (self.clean_state == ExternalStatus.UNKNOWN)) if is_empty and clean_valid: exists = False else: exists = True return exists def check_safe_to_update_repos(tree_status): """Check if *ALL* repositories are in a safe state to update. We don't want to do a partial update of the repositories then die, leaving the model in an inconsistent state. Note: if there is an update to do, the repositories will by definiation be out of synce with the externals description, so we can't use that as criteria for updating. """ safe_to_update = True for comp in tree_status: stat = tree_status[comp] safe_to_update &= stat.safe_to_update() return safe_to_update