def generate(): ''' Generate patch for specific branch. ''' repo = Repo('.') if repo.is_dirty: abort('Working tree is dirty. Working tree must be clean to perform this operation.') if confirm('Use master as base?'): for branch in repo.branches: if branch.name == 'master': master = branch else: branch, master = prompt_branch_select(repo, 'Select base branch') name, selected_branch = prompt_branch_select(repo) while exists(path(name)): name = prompt('Directory already exists: %s.\nWhat should I call this patch?'%path(name)) os.mkdir(path(name)) rawpath = join(path(name), 'raw.diff') local('git diff --binary %s %s > %s'%(selected_branch.commit, master.commit, rawpath)) with open(rawpath, 'r') as raw: changed = raw.read() config = RawConfigParser() config.add_section('target') config.add_section('iteration') config.set('target', 'branch', master.name) config.set('target', 'commit', master.commit) config.set('iteration', 'branch', selected_branch.name) config.set('iteration', 'commit', selected_branch.commit) print("Generated %s patch directory."%name) diffs = Diff.list_from_string(repo, changed) def filter(diff): def match(pattern): if fnmatch.fnmatch(diff.b_path, pattern): return pattern matches = [match for match in map(match, patterns) if not match is None] if not bool(matches): return diff ignore = os.path.join(os.getcwd(), '.diffignore') if os.path.exists(ignore): patterns = open(ignore, 'r').read().split('\n') diffs = [diff for diff in map(filter, diffs) if not diff is None] binary_ext = ['.png', '.gif', '.jpg', '.jpeg', '.flv', '.swf','.zip', '.gz', '.rar', '.fla'] # TODO: change this to use git generated binary marker binaries = [diff for diff in diffs if splitext(diff.b_path)[1] in binary_ext] text = set(diffs) - set(binaries) config.add_section('patch') config.set('patch', 'binary', int(len(binaries) > 0)) config.set('patch', 'text', int(len(text) > 0)) print 'Generated patch config file' if text: text_patch = join(path(name), 'text.patch') with open(text_patch, 'w') as pf: for diff in text: pf.write('%s\n\n'%diff.diff) print 'Generated %s text patch'%name else: text_patch = None if binaries: binary_list = join(path(name), 'binary.changes') binary_base = join(path(name), 'binaries') os.mkdir(binary_base) with open(binary_list, 'w') as pf: for binary in binaries: def mkdirs(path): path = join(binary_base, path) try: os.makedirs(path) except: pass return path def extractfile(commit, path): dirs, file = os.path.split(path) dirs = mkdirs('%s/%s'%(commit, dirs)) with settings(show('stdout'), warn_only = True): if commit == 'target': commit_id = master.commit else: commit_id = selected_branch.commit get_file(commit_id, path, join(binary_base, commit, path)) if binary.a_commit is None: # new file is being created extractfile('iteration', binary.b_path) elif binary.b_commit is None: # file is being removed extractfile('target', binary.a_path) else: # file is being updated or renamed extractfile('target', binary.a_path) extractfile('iteration', binary.b_path) if binary.new_file: pf.write('new %s\n'%binary.b_path) elif binary.deleted_file: pf.write('rm %s\n'%binary.b_path) elif binary.renamed: pf.write('mv %s %s\n'%(binary.a_path, binary.b_path)) else: pf.write('up %s\n'%binary.b_path) print 'Generated %s'%binary_list else: binary_list = None with open(join(path(name), 'patch.cfg'), 'wb') as configfile: config.write(configfile) os.mkdir(join(path(name), 'db')) source = join(path(name), 'db', 'source.sql') target = join(path(name), 'db', 'target.sql') get_file(selected_branch.commit, join('db', 'last'), source) get_file(master.commit, join('db', 'last'), target) with open(join(path(name), 'db', 'source.sql'), 'r') as sfp: with open(join(path(name), 'db', 'target.sql'), 'r') as tfp: dbchanged = md5sum(sfp) != md5sum(tfp) if dbchanged and confirm('Generate database patch?'): dbpatch.generate(join(path(name), 'db'), source, target) else: shutil.rmtree(join(path(name), 'db'), ignore_errors=True) config.set('patch', 'schema', 0) config.set('patch', 'data', 0) with open(join(path(name), 'patch.cfg'), 'wb') as configfile: config.write(configfile) return name, path(name)