#!/usr/bin/env python """ Rebranch a branch from its trunk, pulling in the changes made on the branch if possible. """ import shutil import time import os from mergebot import SvnOps from mergebot.Actor import Actor class RebranchActor(Actor): """Rebranches a ticket from its baseline. """ def execute(self): """ To rebranch from the baseline, you have to do these steps: delete the branch. recreate the branch. merge in the changes from the deleted branch. if there are no conflicts, commit those changes to the branch. """ # FIXME: This desparately needs to be refactored. # Do as much work as we can before affecting the repository so we have # as little cleanup as possible and the fewest cases where we leave a # broken branch. results = {} # We need to improve the logging of the rebranch stuff. logfile = self.logfilename() open(logfile, "a").write("%s rebranching ticket %s\n" % (time.asctime(), self.ticket)) # Make sure the various urls we require do exist if not SvnOps.get_branch_info(self.local_url(), logfile): comment = 'Component %s does not exist in the repository.' \ % self.component return results, comment, False if not SvnOps.get_branch_info(self.local_url() + '/branches', logfile): comment = 'No directory in which to create branches for ' \ 'component %s in the repository.' % self.component return results, comment, False if not SvnOps.get_branch_info(self.baseline_local_url(), logfile): comment = 'Version %s for component %s does not exist in the ' \ 'repository.' % (self.version, self.component) return results, comment, False rev_info = SvnOps.get_branch_info(self.branch_local_url(), logfile) if not rev_info: comment = \ 'Branch for ticket %s does not exist in the repository.' % \ (self.ticket) return results, comment, False startrev, endrev = rev_info workingcopy = self.work_dir if os.path.exists(workingcopy): shutil.rmtree(workingcopy) svnmergecommand = "svn merge -r %s:%s %s@%s" % (startrev, endrev, self.branch_local_url(), startrev) publicsvnmergecommand = "svn merge -r %s:%s %s@%s" % (startrev, endrev, self.branch_public_url(), startrev) # This is used in the ticket comments when there are conflicts. instructioncomment = "\n".join([ "You will need to fix this manually by creating the branch and " "then doing the merge with this command:", "{{{", publicsvnmergecommand, "}}}", ]) # These are used in the commit messages. rmmessage = "\n".join([ "Ticket #%s: %s" % (self.ticket, self.summary), " Remove the branch to rebranch from %s for %s." % \ (self.version, self.requestor), ]) copymessage = "\n".join([ "Ticket #%s: %s" % (self.ticket, self.summary), " Recreate branch from %s for %s." % (self.version, self.requestor), "[log:%s/branches/ticket-%s@%s Previous log]." % \ (self.component, self.ticket, endrev), ]) # This is a list of commands. Each of these must complete # successfully, in this order, to complete the rebranch. On failure, # the given comment needs to be added to the ticket log. commanderrors = ( (lambda x: SvnOps.delete_branch(self.branch_local_url(), rmmessage, x) == -1, "Rebranch internal error: Unable to delete the old branch."), (lambda x: SvnOps.create_branch(self.baseline_local_url(), self.branch_local_url(), copymessage, x), "Rebranch internal error: Unable to recreate the branch. %s" \ % (instructioncomment, )), (lambda x: SvnOps.checkout(self.branch_local_url(), workingcopy, x), "Rebranch internal error: Unable to get working copy. %s" % \ (instructioncomment, )), ) for cmd, error_comment in commanderrors: retval = cmd(logfile) if retval: if os.path.exists(workingcopy): shutil.rmtree(workingcopy) results['mergebotstate'] = 'rebranchfailed' return results, error_comment, False # On success, we're in the same state as if we had just branched. results['mergebotstate'] = 'branched' success = True # Go ahead and try to do the merge. If we got lucky and there are no # conflicts, commit the changes. # Put a full update on the ticket. merge_results = SvnOps.merge("%s@%s" % (self.branch_local_url(), startrev), workingcopy, (startrev, endrev), logfile) conflicts = SvnOps.conflicts_from_merge_results(merge_results) if conflicts: ticketmessage = "\n".join([ "There were conflicts on rebranching.", "Files in conflict:", "{{{", "\n".join(conflicts), "}}}", "You will need to resolve the conflicts manually.", "To do so, update a working copy to the branch, " "and run this merge command:", "{{{", publicsvnmergecommand, "}}}", "Once you have resolved the conflicts, " "commit your work to the branch.", ]) else: # No conflicts, do the commit. mergemessage = "\n".join([ "Ticket #%s: %s" % (self.ticket, self.summary), " Merge in changes from old branch for %s." % self.requestor, " %s" % svnmergecommand, ]) newrev = SvnOps.commit(workingcopy, mergemessage, logfile) if newrev == None: ticketmessage = "\n".join([ "Rebranched from %s for %s." % (self.version, self.requestor), "There were no changes to commit to the branch.", "You will need to update your working copy.", ]) elif newrev < 0: ticketmessage = "\n".join([ "Rebranch internal error: Unable to commit merged " "changes.", "You will need to fix this manually by doing the merge " "with this command:", "{{{", publicsvnmergecommand, "}}}", ]) results['mergebotstate'] = 'rebranchfailed' success = False else: ticketmessage = "\n".join([ "Rebranched from %s for %s." % (self.version, self.requestor), "There were no conflicts, so the changes were " "automatically merged and committed to the branch.", "You will need to update your working copy.", ]) if os.path.exists(workingcopy): shutil.rmtree(workingcopy) return results, ticketmessage, success # vim:foldcolumn=4 foldmethod=indent # vim:tabstop=4 shiftwidth=4 softtabstop=4 expandtab