def cmd_diff(config, localnames=None): """ calls SCM diff for all SCM entries in config, relative to path :returns: List of dict {element: ConfigElement, diff: diffstring} :raises MultiProjectException: on plenty of errors """ class DiffRetriever(): def __init__(self, element, path): self.element = element self.path = path def do_work(self): return {'diff': self.element.get_diff(self.path)} path = config.get_base_path() elements = config.get_config_elements() work = DistributedWork(len(elements)) elements = select_elements(config, localnames) for element in elements: if element.is_vcs_element(): work.add_thread(DiffRetriever(element, path)) outputs = work.run() return outputs
def cmd_status(config, localnames=None, untracked=False): """ calls SCM status for all SCM entries in config, relative to path :returns: List of dict {element: ConfigElement, diff: diffstring} :param untracked: also show files not added to the SCM :raises MultiProjectException: on plenty of errors """ class StatusRetriever(): def __init__(self, element, path, untracked): self.element = element self.path = path self.untracked = untracked def do_work(self): path_spec = self.element.get_path_spec() scmtype = path_spec.get_scmtype() status = self.element.get_status(self.path, self.untracked) # align other scm output to svn columns = -1 if scmtype == "git": columns = 3 elif scmtype == "hg": columns = 2 elif scmtype == "bzr": columns = 4 if columns > -1 and status is not None: status_aligned = '' for line in status.splitlines(): status_aligned = "%s%s%s\n" % (status_aligned, line[:columns].ljust(8), line[columns:]) status = status_aligned return {'status': status} path = config.get_base_path() # call SCM info in separate threads elements = config.get_config_elements() work = DistributedWork(len(elements)) elements = select_elements(config, localnames) for element in elements: if element.is_vcs_element(): work.add_thread(StatusRetriever(element, path, untracked)) outputs = work.run() return outputs
def test_distributed_work(self): work = DistributedWork(3) thing1 = FooThing(FooThing(FooThing(None)), result={'done': True}) thing2 = FooThing(FooThing(FooThing(None)), result={'done': True}) thing3 = FooThing(FooThing(FooThing(None)), result={'done': True}) self.assertEqual(3, len(work.outputs)) work.add_thread(thing1) self.assertEqual(1, len(work.threads)) work.add_thread(thing2) self.assertEqual(2, len(work.threads)) work.add_thread(thing3) self.assertEqual(3, len(work.threads)) self.assertEqual(thing1.done, False) self.assertEqual(thing2.done, False) self.assertEqual(thing3.done, False) output = work.run() self.assertEqual(False, 'error' in output[0], output) self.assertEqual(False, 'error' in output[1], output) self.assertEqual(False, 'error' in output[2], output)
def cmd_info(config, localnames=None): """This function compares what should be (config_file) with what is (directories) and returns a list of dictionary giving each local path and all the state information about it available. """ class InfoRetriever(): def __init__(self, element, path): self.element = element self.path = path def do_work(self): localname = "" scm = None uri = "" curr_uri = None exists = False version = "" # what is given in config file modified = "" actualversion = "" # revision number of version specversion = "" # actual revision number localname = self.element.get_local_name() path = self.element.get_path() or localname if localname is None or localname == "": raise MultiProjectException( "Missing local-name in element: %s" % self.element) abs_path = normabspath(path, self.path) if (os.path.exists(abs_path)): exists = True if self.element.is_vcs_element(): if not exists: path_spec = self.element.get_path_spec() version = path_spec.get_version() else: path_spec = self.element.get_versioned_path_spec() version = path_spec.get_version() curr_uri = path_spec.get_curr_uri() status = self.element.get_status(self.path) if (status is not None and status.strip() != ''): modified = True specversion = path_spec.get_revision() if (version is not None and version.strip() != '' and (specversion is None or specversion.strip() == '')): specversion = '"%s"' % version actualversion = path_spec.get_current_revision() scm = path_spec.get_scmtype() uri = path_spec.get_uri() return { 'scm': scm, 'exists': exists, 'localname': localname, 'path': path, 'uri': uri, 'curr_uri': curr_uri, 'version': version, 'specversion': specversion, 'actualversion': actualversion, 'modified': modified, 'properties': self.element.get_properties() } path = config.get_base_path() # call SCM info in separate threads elements = config.get_config_elements() work = DistributedWork(len(elements)) elements = select_elements(config, localnames) for element in elements: if element.get_properties( ) is None or not 'setup-file' in element.get_properties(): work.add_thread(InfoRetriever(element, path)) outputs = work.run() return outputs
def cmd_install_or_update(config, backup_path=None, mode='abort', robust=False, localnames=None, num_threads=1, verbose=False): """ performs many things, generally attempting to make the local filesystem look like what the config specifies, pulling from remote sources the most recent changes. The command may have stdin user interaction (TODO abstract) :param backup_path: if and where to backup trees before deleting them :param robust: proceed to next element even when one element fails :returns: True on Success :raises MultiProjectException: on plenty of errors """ success = True if not os.path.exists(config.get_base_path()): os.mkdir(config.get_base_path()) # Prepare install operation check filesystem and ask user preparation_reports = [] elements = select_elements(config, localnames) for tree_el in elements: abs_backup_path = None if backup_path is not None: abs_backup_path = os.path.join(config.get_base_path(), backup_path) try: preparation_report = tree_el.prepare_install( backup_path=abs_backup_path, arg_mode=mode, robust=robust) if preparation_report is not None: if preparation_report.abort: raise MultiProjectException( "Aborting install because of %s" % preparation_report.error) if not preparation_report.skip: preparation_reports.append(preparation_report) else: if preparation_report.error is not None: print("Skipping install of %s because: %s" % ( preparation_report.config_element.get_local_name(), preparation_report.error)) except MultiProjectException as exc: fail_str = "Failed to install tree '%s'\n %s" % ( tree_el.get_path(), exc) if robust: success = False print("Continuing despite %s" % fail_str) else: raise MultiProjectException(fail_str) class Installer(): def __init__(self, report): self.element = report.config_element self.report = report def do_work(self): self.element.install(checkout=self.report.checkout, backup=self.report.backup, backup_path=self.report.backup_path, verbose=self.report.verbose) return {} work = DistributedWork(len(preparation_reports), num_threads, silent=False) for report in preparation_reports: report.verbose = verbose thread = Installer(report) work.add_thread(thread) try: work.run() except MultiProjectException as exc: print("Exception caught during install: %s" % exc) success = False if not robust: raise exc return success
def cmd_info(config, localnames=None): """This function compares what should be (config_file) with what is (directories) and returns a list of dictionary giving each local path and all the state information about it available. """ class InfoRetriever(): def __init__(self, element, path): self.element = element self.path = path def do_work(self): localname = "" scm = None uri = "" curr_uri = None exists = False version = "" # what is given in config file modified = "" actualversion = "" # revision number of version specversion = "" # actual revision number localname = self.element.get_local_name() path = self.element.get_path() or localname if localname is None or localname == "": raise MultiProjectException("Missing local-name in element: %s" % self.element) if (os.path.exists(normabspath(path, self.path))): exists = True if self.element.is_vcs_element(): if not exists: path_spec = self.element.get_path_spec() version = path_spec.get_version() else: path_spec = self.element.get_versioned_path_spec() version = path_spec.get_version() curr_uri = path_spec.get_curr_uri() status = self.element.get_status(self.path) if (status is not None and status.strip() != ''): modified = True specversion = path_spec.get_revision() if (version is not None and version.strip() != '' and (specversion is None or specversion.strip() == '')): specversion = '"%s"' % version actualversion = path_spec.get_current_revision() scm = path_spec.get_scmtype() uri = path_spec.get_uri() return {'scm': scm, 'exists': exists, 'localname': localname, 'path': path, 'uri': uri, 'curr_uri': curr_uri, 'version': version, 'specversion': specversion, 'actualversion': actualversion, 'modified': modified, 'properties': self.element.get_properties()} path = config.get_base_path() # call SCM info in separate threads elements = config.get_config_elements() work = DistributedWork(len(elements)) elements = select_elements(config, localnames) for element in elements: if element.get_properties() is None or not 'setup-file' in element.get_properties(): work.add_thread(InfoRetriever(element, path)) outputs = work.run() return outputs
def cmd_install_or_update( config, backup_path=None, mode='abort', robust=False, localnames=None, num_threads=1, verbose=False): """ performs many things, generally attempting to make the local filesystem look like what the config specifies, pulling from remote sources the most recent changes. The command may have stdin user interaction (TODO abstract) :param backup_path: if and where to backup trees before deleting them :param robust: proceed to next element even when one element fails :returns: True on Success :raises MultiProjectException: on plenty of errors """ success = True if not os.path.exists(config.get_base_path()): os.mkdir(config.get_base_path()) # Prepare install operation check filesystem and ask user preparation_reports = [] elements = select_elements(config, localnames) for tree_el in elements: abs_backup_path = None if backup_path is not None: abs_backup_path = os.path.join(config.get_base_path(), backup_path) try: preparation_report = tree_el.prepare_install(backup_path=abs_backup_path, arg_mode=mode, robust=robust) if preparation_report is not None: if preparation_report.abort: raise MultiProjectException("Aborting install because of %s" % preparation_report.error) if not preparation_report.skip: preparation_reports.append(preparation_report) else: if preparation_report.error is not None: print("Skipping install of %s because: %s" % (preparation_report.config_element.get_local_name(), preparation_report.error)) except MultiProjectException as exc: fail_str = "Failed to install tree '%s'\n %s" % (tree_el.get_path(), exc) if robust: success = False print("Continuing despite %s" % fail_str) else: raise MultiProjectException(fail_str) class Installer(): def __init__(self, report): self.element = report.config_element self.report = report def do_work(self): self.element.install(checkout=self.report.checkout, backup=self.report.backup, backup_path=self.report.backup_path, inplace=self.report.inplace, verbose=self.report.verbose) return {} work = DistributedWork(len(preparation_reports), num_threads, silent=False) for report in preparation_reports: report.verbose = verbose thread = Installer(report) work.add_thread(thread) try: work.run() except MultiProjectException as exc: print ("Exception caught during install: %s" % exc) success = False if not robust: raise return success