def run(self): """ Future Functionality: Given a filename or a reference, determine what actually needs to be resolved and then attempt to automatically resolve the issue. The current items that will be attempted to address include (in order of how they would be addressed): - filename standards => attempt to rename file to meet standards - divergent chains => append to end of alter chain and rename file - abandoned alters => append to end of alter chain and rename file - missing up/down => create the missing pair Current Functionality: Given a filename, resolve a conflict that is happening by moving the file to the end of the build chain. First however, verify that this alter hasn't been run in the DB. If so, require the force option to continue. If the alter is at the end of the db history then we can also warn and, if given the force option, can undo the commit before resolving (should issue message about what has happened). """ (options, args) = self.parser.parse_args() if len(args) == 0: raise ArgsError("You must provide a filename or reference", self.parser.format_help()) # search for file/ref self.file = self.ref = args[0] if self._file_exists(): self.type = 'file' elif self._ref_exists(): self.type = 'ref' else: raise MissingRefError("Filename or reference not found for '%s'" % self.file) # We can now assume that self.ref and self.file are set properly # TODO: implement ability to determine issue and resolve (if possible) automatically # determine what the issue is: # check = CheckCommand() # if not check.check_filename(self.file): # # rename file # pass sys.stdout.write("Resolving\n") self._relocate_sub_chain() sys.stdout.write("\nRe-Checking...\n") sys.argv = [sys.argv[0]] CheckCommand(self.context).run()
def run(self): (options, args) = self.parser.parse_args() # validate static_alter_dir set if flag used if options.write_to_file: options.include_rev_query = True if self.config.get('static_alter_dir') is None: raise Exception( 'static_alter_dir must be set in config.json to' '\nuse -w/--write-to-file flag') self._setup_static_alter_dir() refs = args nodes = ChainUtil.build_chain() ref_nodes = [] if len(refs) == 0: # entire chain refs = self._get_node_ids(nodes) refs.reverse() # validate valid refs for ref in refs: node = self._find_ref(ref, nodes) if node is False: raise MissingRefError("Ref '%s' could not be found" % ref, self.parser.format_help()) else: ref_nodes.append(node) # gen SQL for each ref if options.write_to_file: # gen SQL for each ref, and save to individual files. for node in ref_nodes: sql = self.gen_sql_for_reflist([node], options) if options.down_alter: filename = node.down_filename() else: filename = node.filename fobj = open( os.path.join(self.config['static_alter_dir'], filename), 'w') fobj.write(sql) fobj.close() print os.path.join(self.config['static_alter_dir'], filename) else: # gen SQL for refs in one call sql = self.gen_sql_for_reflist(ref_nodes, options) sys.stdout.write(sql)
def check_abandoned_alters(self, chain): """ Check for files that do not exist within the current alter chain. """ tail = chain chain_files = [] while tail is not None: chain_files.append(tail.filename) tail = tail.backref up_alter = re.compile('-up.sql') for file in self.files: if up_alter.search(file) is not None: if file not in chain_files: # @jmurray - how can scenario be encountered? raise MissingRefError( "File not found within build-chain '%s'" % file)
def run(self): """ Update the DB by checking the current state (stored in the DB itself) and and bringing the DB up to date from the current list of alters. Returns nothing, but updated DB (via alters) and updated revision number in DB table Now that we have the history of what has _been_ run and the alter chain of all alters, we can determine what _needs_ to be run. First we will go up the history until it diverges with the chain. Then we'll run all of the "undos" for any history items that still exist and then run the list from where we left off. """ (options, args) = self.parser.parse_args() CheckCommand(self.context).run(inline=True) # get history history = self.db.get_commit_history() history = sorted(history, key=lambda h: h[0]) history_alters = [h[1] for h in history] # get current alter chain tail = ChainUtil.build_chain() alter_list = [tail] if None in alter_list: alter_list.remove(None) while tail is not None and tail.backref is not None: tail = tail.backref alter_list.append(tail) # find (and remove) synced alters from alter_list (so they are not run again) common_history = 0 for (_id, alter_id, datetime) in history: if len(alter_list) == 0: break # don't count alters for other env's in common-history alter = alter_list.pop() while not self.should_run(alter): alter = alter_list.pop() if alter.id == alter_id: common_history += 1 else: alter_list.append(alter) break # undo alters that are not in sync with alter chain if options.undo is true. if options.undo: uncommon_history = history[common_history:] if len(uncommon_history) > 0: # clean up alters from here uncommon_history.reverse() for (_id, alter_id, datetime) in uncommon_history: alters = [a for a in alter_list if a.id == alter_id] if len(alters) > 1: msg = "Multiple alters found for a single id (%s)" % a.id if not options.force: raise MultipleDownAltersError(msg) else: sys.stderr.write(msg + "\n") elif len(alters) == 0: raise MissingDownAlterError("Missing down alter %s" % alter_id) alter = alters[0] self.db.run_down(alter) if alter.id in history_alters: history_alters.remove(alter.id) # Ensure that if a target ref was specified that one was found in # in the list of alters to run (up) if len(alter_list) and len(args) and args[0] not in [a.id for a in alter_list]: raise MissingRefError('revision (%s) not found in alters that would be run' % args[0]) # Do alters that are in the alter chain and have not # been run yet max_ = int(options.N or len(alter_list)) i = 0 while not len(alter_list) == 0: if i == max_: break alter = alter_list.pop() if len(args) > 0: target_rev = args[0] if target_rev == alter.id: i = (max_ - 1) i += 1 if alter.id not in history_alters and self.should_run(alter): self.db.run_up(alter=alter, force=options.force, verbose=options.verbose) elif not self.should_run(alter): # possible to get a skipped alter in the event that it wasn't removed # in the common-history code (aka, running new alters) pass else: sys.stderr.write("Warning: alter " + str(alter.id) + " has already been " \ "run. Skipping\n") sys.stdout.write("Updated\n")
def __build_and_validate_linked_list(cls, nodes): """ Build a linked list and validate it's correctness given an array of SimpleNodes contain soft/weak references to each other Returns tail of list (since it's processed backwards) :rtype : SimpleNode """ # check if we're working with no nodes, return None if so # don't error/exit because and empty list is not necessarily invalid if len(nodes) == 0: return None heads = [] backrefs = {} for node in nodes: if node.backref is not None: # Check for duplicate refs backnodes = [n for n in nodes if n.id == node.backref] if len(backnodes) == 1: node.backref = backnodes[0] elif len(backnodes) > 1: msg = "\n".join([ "Duplicate refs found in %s\n" % b.filename for b in backnodes ]) raise DuplicateRefsError(msg) else: raise MissingRefError( "Backref points to non-existent alter: %s\n" % node.filename) # catalog backrefs (for divergence checking) if node.backref.id not in backrefs: backrefs[node.backref.id] = [] backrefs[node.backref.id].append(node) else: heads.append(node) # check backref catalog for duplicates for (_, _nodes) in backrefs.iteritems(): if len(_nodes) > 1: msg = [] msg.append( "Divergent Branch:" "\nThis means that we have found alters that share a common parent. To fix" "\nthis you can run the 'resolve' command. When merging changes from your" "\nfeature-branch, ensure that you resolve your files that are in conflict" "\n(not existing files that were previously in a good state)." "\n") msg += [ "\tDuplicate backref found (divergent branch): %s\n" % node.filename for node in _nodes ] msg = "\n".join(msg) raise DuplicateRefsError(msg) # check head(s) if len(heads) > 1: msg = ["More than one head found:" ] + [" %s" % head.filename for head in heads] msg = "\n".join(msg) raise HeadError(msg) elif len(heads) == 0: raise HeadError("No head found") # check tail(s) tails = [] for node in nodes: if node.backref is None: continue children = [n for n in nodes if n.backref == node] if len(children) == 0: tails.append(node) if len(tails) > 1: msg = "\n".join([ "Duplicate backref found in %s\n" % tail.filename for tail in tails ]) raise DuplicateRefsError(msg) elif len(tails) == 0 and (not len(nodes) == 1): raise CircularRefError( "something strange is happening... no last alter found (circular references!!)" ) if len(nodes) == 1: return heads[0] else: return tails[0]
def run(self): """ Analogous to what the up_command definition does, but in reverse. """ (options, args) = self.parser.parse_args() CheckCommand(self.context).run(inline=True) # check validity of options (can't really do this in OptionParser AFAIK) if len(args) == 0 and options.N is None: raise OptionsError("must specify either argument or number of down-alters to run", self.parser.format_help()) # get current history history = self.db.get_commit_history() history = sorted(history, key=lambda h: h[0], reverse=True) # get current alter chain tail = ChainUtil.build_chain() alter_list = [tail] if None in alter_list: alter_list.remove(None) while tail is not None and tail.backref is not None: tail = tail.backref alter_list.append(tail) # parse the args given run_type, target_rev = self.parse_args(args) # collect the down-alters that we need to run depending on the command line # options and arguments that were given down_alters_to_run = [] max_history_len = int(options.N or len(history)) i = 0 for (id, alter_id, alter_time) in history: if i == max_history_len: break if run_type == 'base': if i == (max_history_len - 1): break elif run_type == 'all': pass else: if target_rev == alter_id: i = (max_history_len - 1) i += 1 alters = [a for a in alter_list if a.id == alter_id] if len(alters) > 0: alter = alters[0] down_alters_to_run.append(alter) else: # error depending on the force and verbose flags (missing alter to run) if options.force: sys.stderr.write("Warning: missing alter: %s\n" % alter_id) self.db.remove_commit(ref=alter_id) else: raise MissingDownAlterError("missing alter: %s\n" % alter_id) # ensure that if a target_revision was specified that one was found in # in the list of alters to run (down) if (run_type == 'revision' and target_rev not in [a.id for a in down_alters_to_run]): raise MissingRefError('revision (%s) not found in alters that would be run' % target_rev) # run all the down-alters that we have collected for alter_to_run in down_alters_to_run: self.db.run_down(alter=alter_to_run, force=options.force, verbose=options.verbose) sys.stdout.write("Downgraded\n")