def post_checkout_branch(branch, database, old_head_ref, new_head_ref): """\ Handle a switch in branches. Fun times ahead. """ if get_last_branch() == branch: logging.warning("Already on branch %s.", branch) return # Still a work in progress #check_branch_rename(branch, database) if not exists_database(database + branch): # The branched DB doesn't exist, create it from our current copy. # XXX: This has problems! Currently we only support branching from the current branch... # I think I can solve this by searching through all the branches, and finding the corresponding name # for old_ref. src_branch = get_last_branch() copy_database(database, database + branch) # Create the "base.sql" file for this branch - any changes to the branch will be relative to its starting # point - and NOT to production. create_base_sql(branch, database) patches = [patch for patch in list_applied_patches(src_branch, database) if patch not in list_merged_patches(src_branch, branch, database) and patch not in list_ignored_patches(src_branch, database)] add_merged_patches(patches, 'base.sql', src_branch, branch, database) # XXX: Use reflog here rather to get the last branch. rename_database(database, database + get_last_branch()) rename_database(database + branch, database) set_last_branch(branch) add_event_to_db(database, "branch-change", time.time())
def status(): branch = git.current_branch() print "# On branch %s, tracking database %s" % (branch, database) tracked = [db[len(database):] for db in list_databases() if db.startswith(database)] tracked[tracked.index("")] = branch print "# Currently tracking local branches: %s" % tracked print "# Patches applied:" print "# %s" % list_applied_patches(branch, database) print "# Patches not applied:" print "# %s" % [patch for patch in list_file_patches(branch, database) if patch not in list_applied_patches(branch, database) and patch not in list_ignored_patches(branch, database)] print "# Patches ignored:" print "# %s" % list_ignored_patches(branch, database) print "# Patches stashed:" print "# %s" % list_stashed_patches(branch, database) last_commit = git.last_commit_time() if fast_check_sqlchanges(database, last_commit): with amalgamated_sql(branch, database) as amalgamated_sql_file: sql_difference = calculate_difference(amalgamated_sql_file, database, [filters.filter_auto_increment, lambda input: filters.filter_renames(input, database, git.last_commit_time())]) if sql_difference: print "# Untracked SQL code since last commit at '%s'." % (last_commit) else: print "# SQL database clean." else: print "# SQL database clean."
def __create_sql(self): if self.branch == "master": self.tmpfile.write(open(production_sql(self.database)).read()) else: self.tmpfile.write(open(base_sql(self.branch, self.database)).read()) for patch in list_applied_patches(self.branch, self.database): with open(patch, "r") as infile: self.tmpfile.write(infile.read()) self.tmpfile.flush()
def post_merge(dst_branch, database, squash): merge_info = git.reflog(False)[0] res = re.search(r".*: merge (\w+):.*", merge_info) if res: src_branch = res.group(1) logging.info("Branch '%s' was merged with us, '%s'", src_branch, dst_branch) logging.debug("Amalgamating patches from branch '%s' into one SQL file, and importing.", src_branch) patches = [patch for patch in list_applied_patches(src_branch, database) if ( patch not in list_merged_patches(src_branch, dst_branch, database) and patch not in list_ignored_patches(src_branch, database) and patch not in list_patches_resulting_from_merge(dst_branch, src_branch, database))] if not patches: logging.info("No patches to apply.") return output_file = NamedTemporaryFile(suffix=".sql", delete=False) for patch in patches: with open(patch, "r") as patchfile: output_file.write(patchfile.read()) output_file.flush() try: apply_patch(database, output_file.name, True) if dst_branch == "master": base_dir = os.path.join("sql", "development", database) patch_filename = free_patch_name() else: base_dir = os.path.join(".git", "gitdb", "patches", dst_branch, database) patch_filename = free_patch_name() patch_name = os.path.join(base_dir, patch_filename) makedirs(base_dir) shutil.copy(output_file.name, patch_name) add_patch_to_db(dst_branch, database, patch_name) add_merged_patches(patches, patch_name, src_branch, dst_branch, database) if dst_branch == "master": # If we are on master, add the SQL file and amend the commit. git.add(patch_name) git.amend_commit() except MySqlException: # Rollback occured. logging.error("SQL patch did not apply cleanly. This means that while the files in branch %s will have been updated, the database %s is not. Please bring the database to a consistent state manually (you can use the SQL information stored in %s for help), then run:", dst_branch, database, output_file.name) logging.error("foobar") else: handle_pull(dst_branch, database)
def ignore_patches(): # Mark certain patches as to be ignored / unignored. branch = git.current_branch() patches = dict([(i, patch) for i, patch in enumerate(list_file_patches(branch, database)) if patch not in list_applied_patches(branch, database) and patch not in list_ignored_patches(branch, database)]) if not patches: print "No patches to be ignored." return pprint(patches) try: input = raw_input("Please enter a space separated list of patch #s to ignore: ") if input != "": to_ignore = [int(i) for i in input.split(" ")] for ignored in to_ignore: add_ignored_to_db(git.current_branch(), database, patches[ignored]) except: print "Invalid input." return add_event_to_db(database, "ignored patches, %s: ", time.time())
def handle_pull(branch, database): logging.info("New patches from pull:") if branch != "master": logging.warn("gitdb does not currently support remote tracking branches.") else: patches = [patch for patch in list_file_patches(branch, database) if patch not in list_applied_patches(branch, database) and patch not in list_ignored_patches(branch, database)] logging.info(patches) try: apply_patch(database, patches, True) for patch in patches: add_patch_to_db(branch, database, patch) except MySqlException: logging.error("Patches from pull failed to apply.") logging.error("Feel free to do whatever you like to the database to make it consistent. Then run git db merge.")
def filter_renames(input, database, start_datetime, use_branch_patches=False): logging.debug("Scanning for renames using mysql's binlog...") logging.debug("Starting from %s" % start_datetime) if not use_branch_patches: binlog = read_binlog(database, start_datetime) else: binlog = "" for patch in list_applied_patches(use_branch_patches, database): binlog += open(patch, "r").read() print binlog table_renames = [r"RENAME TABLE (\w*) TO (\w*)", r"ALTER TABLE (`\w+`|\w+).+?RENAME TO (`\w+`|\w+)" ] table_mappings = bidict() for pattern in table_renames: re_match = re.compile(pattern, flags=re.IGNORECASE|re.DOTALL) for statement in binlog: results = re_match.search(statement) if results: old_name, new_name = results.groups() old_name = old_name.replace("`", "") new_name = new_name.replace("`", "") logging.info("Detected rename of table from '%s' to '%s'" % (old_name, new_name)) # Check if old_name exists as a new_name / for double ++ renames. if old_name in table_mappings.values(): table_mappings[table_mappings[:old_name]] = new_name else: table_mappings[old_name] = new_name for old_name, new_name in table_mappings.items(): input = re.sub("DROP TABLE %s;" % old_name, "", input) input = re.sub("CREATE TABLE %s \(.+?\).+?;" % new_name, "RENAME TABLE %s to %s;" % (old_name, new_name), input, flags=re.DOTALL) column_mappings = bidict() data_changes_on_renamed = {} re_match = re.compile(r"ALTER TABLE (`\w*`|\w*).+?CHANGE (`\w*`) (`\w*`) ([a-zA-Z\(0-9\)_-]*) (.*) (AFTER (?:`\w*`|\w*)|FIRST)", re.IGNORECASE|re.DOTALL) for statement in binlog: results = re_match.search(statement) if results: table_name, old_name, new_name, data_info_1, data_info_2, data_info_3 = results.groups() table_name = table_name.replace("`", "") old_name = old_name.replace("`", "") new_name = new_name.replace("`", "") data_info = data_info_1 + " " + data_info_2 if old_name == new_name: data_changes_on_renamed[old_name] = data_info continue if table_name in table_mappings: table_name = table_mappings[table_name] logging.info("Detected rename of column from '%s' to '%s' on table '%s'" % (old_name, new_name, table_name)) if (table_name, old_name, data_info) in column_mappings.values(): column_mappings[column_mappings[:(table_name, old_name, data_info)]] = (table_name, new_name, data_info) else: column_mappings[(table_name, old_name)] = (table_name, new_name, data_info) for (table_name, old_column), (_, new_column, data_info) in column_mappings.items(): if new_column in data_changes_on_renamed: data_info = data_changes_on_renamed[new_column] if table_name in table_mappings or table_name in table_mappings.values(): # Column renamed on a renamed tabled, we don't need to worry about removing # any lines, since they would be incorporated into the table change, place us after the rename. new_sql = "ALTER TABLE %s CHANGE %s %s %s;" % (table_name, old_column, new_column, data_info) input += new_sql else: new_sql = "ALTER TABLE %s CHANGE %s %s %s;" % (table_name, old_column, new_column, data_info) # Verify both first: if re.search("ALTER TABLE %s DROP COLUMN %s; # was .*" % (table_name, old_column), input) and re.search("ALTER TABLE %s ADD COLUMN %s.*;" %(table_name, new_column), input): input = re.sub("ALTER TABLE %s DROP COLUMN %s; # was .*" % (table_name, old_column), "", input, flags=re.IGNORECASE) input = re.sub("ALTER TABLE %s ADD COLUMN %s.*;" %(table_name, new_column), new_sql, input, flags=re.IGNORECASE) return input