#!/usr/bin/python # initial_merge.py - perform initial merge after dist-git migration """\ Performs a 'git merge' of all git branches with the same content (i.e. with the same package spec files, patch files, etc.), regardless of their history. This is useful after Fedora's dist-cvs to dist-git migration, as often different branches have different histories but the same content on the filesystem. After these initial merges of identical trees, future merges between the branches will be a lot easier: Easier to follow in the dependency graph, and easier to perform without conflicts. """ # TODO: Use more pyfedpkg.* functions instead of direct calls to git. import argparse import sys import os import subprocess __all__ = [ 'get_parser', 'handle_path', 'handle_curdir' ] global global_dry_run global_dry_run = False # global_dry_run = True def run_cmd_out(cmd, dry_run=None, shell=False): if dry_run==None: dry_run = global_dry_run if dry_run: print 'RUN-', cmd else: print 'RUN:', cmd if dry_run: return (0,'','') proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=shell) stdout, stderr = proc.communicate() return (proc.returncode,stdout,stderr) def run_cmd(cmd, dry_run=None, shell=False): retcode, stdout, stderr = run_cmd_out(cmd, dry_run=dry_run, shell=shell) sys.stdout.write(stdout) sys.stdout.write(stderr) return retcode def cmp_Branch(a, b): if a.sha == b.sha: return cmp(a.localbranch,b.localbranch) else: return cmp(a.sha,b.sha) class Branch: def __init__(self, string): self.sha, self.origbranch = string.split() a = self.origbranch.split('/') assert(a[0] == 'origin') assert(a[-1] == 'master') self.localbranch = a[1] def __repr__(self): return "%(sha)s %(origbranch)s" % self.__dict__ def switch_branch(branch): ret = run_cmd(['fedpkg', 'switch-branch', branch], dry_run=False) assert(ret == 0) def do_initial_merge(into, to_merge): print print "######## Merging", [ x.localbranch for x in to_merge ], \ "into", into.localbranch, "########" switch_branch(into.localbranch) ret = run_cmd(['git', 'merge', '-m', '"Initial peudo merge for dist-git setup"', '-s', 'ours'] + [ x.origbranch for x in to_merge]) assert(ret == 0) for t in to_merge: switch_branch(t.localbranch) ret = run_cmd(['git', 'merge', into.localbranch]) assert(ret == 0) class Consumer: def __reset(self): self.branch_list = [] def __init__(self): self.__reset() def __git_merge(self): head = self.branch_list[-1] others = self.branch_list[:-1] do_initial_merge(head, others) def __flush(self): if len(self.branch_list) < 2: return # print "Flush", self.branch_list self.__git_merge() self.__reset() def eat(self, item): if self.branch_list: last = self.branch_list[-1] if last.sha == item.sha: # print " ", "=", item self.branch_list.extend([item]) else: # print " ", "?", item self.__flush() self.branch_list = [item] else: self.branch_list = [item] def finish(self): self.__flush() def handle_curdir(): git_branch_cmd = """for branch in $(git branch -r | grep -v '/HEAD'); do echo "$(git rev-parse "$branch^{tree}") $branch"; done""" retcode, stdout, stderr = run_cmd_out(git_branch_cmd, dry_run=False, shell=True) assert(retcode == 0) assert(stderr == "") # print "RESULT:" # sys.stdout.write(stdout) a = stdout.splitlines() a = [ Branch(s) for s in a ] a.sort(cmp_Branch) # print "SORTED:" for x in a: print " ", x # print "ITERATING:" n = 0 c = Consumer() while n < len(a): c.eat(a[n]) n = n + 1 c.finish() print print "FINISHED." def handle_path(path=None): if not path: path = os.getcwd() oldcwd = os.getcwd() os.chdir(path) print "########", "Entering", path, "########" handle_curdir() print "Leaving", os.getcwd() os.chdir(oldcwd) class Action(argparse.Action): def __init__(self, *args, **kwargs): super(Action, self).__init__(*args, **kwargs) self.default_value = ['.'] def __call__(self, parser, namespace, values, option_string=None): if values: setattr(namespace, self.dest, values) else: setattr(namespace, self.dest, self.default) def fedpkg_command(args): global_dry_run = args.dry_run if global_dry_run: print "NOTE: Running in dry-run mode, i.e. we will not actually perform any merges." for repo in args.repos: handle_path(repo) _module_doc = __doc__ def get_parser(subparsers): sp = subparsers.add_parser('initial-merge', help = 'git merge to join branches with identical trees', formatter_class=argparse.RawDescriptionHelpFormatter, description = _module_doc) sp.add_argument('-n', '--dry-run', dest='dry_run', action='store_const', const=True, default=False, help = ('Whether to actually perform the merges. '+ 'Local tracking branches wil be created anyway.')) sp.add_argument('repos', metavar='repo-path', nargs='*', default=['.'], action=Action, help = 'Path to a repo to initial-merge') sp.set_defaults(command = fedpkg_command) return sp