Ignore:
Timestamp:
Jun 8, 2009 3:07:47 AM (15 years ago)
Author:
retracile
Message:

Mergebot: redesigned implementation. Still has rough edges.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • mergebot/trunk/mergebot/RebranchActor.py

    r16 r17  
    11#!/usr/bin/env python
    22"""
    3 Syntax: RebranchActor.py ticketnum component version requestor
    4 
    53Rebranch a branch from its trunk, pulling in the changes made on the branch if
    64possible.
    75"""
    86
     7import shutil
     8import time
    99import os
    10 import sys
    11 import time
    12 import trac.env
    1310
    14 import SvnOps
    15 from WorkQueue import MergeBotActor, VersionToDir
    16 from TrackerTools import GetRepositoryPublicUrl, GetRepositoryLocalUrl, Task, \
    17     GetWorkDir, GetLogFile
     11from mergebot import SvnOps
     12from mergebot.Actor import Actor
    1813
    19 def rebranch_action(trac_env, ticketnum, component, version, requestor):
     14class RebranchActor(Actor):
     15    """Rebranches a ticket from its baseline.
    2016    """
    21     To rebranch from the baseline, you have to do these steps:
    22         delete the branch.
    23         recreate the branch.
    24         merge in the changes from the deleted branch.
    25         if there are no conflicts, commit those changes to the branch.
    26     """
    27     # FIXME: This desparately needs to be refactored.
    28     # Do as much work as we can before affecting the repository so we have as
    29     # little cleanup as possible and the fewest cases where we leave a broken
    30     # branch.
    31     task_obj = Task(trac_env, ticketnum)
    32     summary = task_obj.GetSummary()
     17    def execute(self):
     18        """
     19        To rebranch from the baseline, you have to do these steps:
     20            delete the branch.
     21            recreate the branch.
     22            merge in the changes from the deleted branch.
     23            if there are no conflicts, commit those changes to the branch.
     24        """
     25        # FIXME: This desparately needs to be refactored.
     26        # Do as much work as we can before affecting the repository so we have
     27        # as little cleanup as possible and the fewest cases where we leave a
     28        # broken branch.
    3329
    34     # We need to improve the logging of the rebranch stuff.
    35     logfile = GetLogFile(trac_env, ticketnum)
    36     open(logfile, "a").write("%s rebranching ticket %s\n" % (time.asctime(),
    37         ticketnum))
     30        results = {}
    3831
    39     baserepositoryurl = str(GetRepositoryLocalUrl(trac_env))
    40     branchurl = os.path.join(baserepositoryurl, component, "branches",
    41         "ticket-%s" % ticketnum)
    42     baselineurl = os.path.join(baserepositoryurl, component,
    43         VersionToDir(version))
    44     publicbranchurl = os.path.join(GetRepositoryPublicUrl(trac_env), component,
    45         "branches", "ticket-%s" % ticketnum)
     32        # We need to improve the logging of the rebranch stuff.
     33        logfile = self.logfilename()
     34        open(logfile, "a").write("%s rebranching ticket %s\n" %
     35            (time.asctime(), self.ticket))
    4636
    47     # Determine the revision range for the branch: startrev:endrev
    48     branchinfo = SvnOps.get_branch_info(branchurl, logfile)
    49     if not branchinfo:
    50         open(logfile, "a").write(
    51             "Unable to get %s branch revision information.\n" % (branchurl))
    52         return "rebranchfailed", task_obj
    53     startrev, endrev = branchinfo
     37        # Make sure the various urls we require do exist
     38        if not SvnOps.get_branch_info(self.local_url(), logfile):
     39            comment = 'Component %s does not exist in the repository.' \
     40                % self.component
     41            return results, comment, False
     42        if not SvnOps.get_branch_info(self.local_url() + '/branches', logfile):
     43            comment = 'No directory in which to create branches for ' \
     44                'component %s in the repository.' % self.component
     45            return results, comment, False
     46        if not SvnOps.get_branch_info(self.baseline_local_url(), logfile):
     47            comment = 'Version %s for component %s does not exist in the ' \
     48                'repository.' % (self.version, self.component)
     49            return results, comment, False
    5450
    55     workingcopy = GetWorkDir(trac_env, ticketnum, __name__)
     51        rev_info = SvnOps.get_branch_info(self.branch_local_url(), logfile)
     52        if not rev_info:
     53            comment = \
     54                'Branch for ticket %s does not exist in the repository.' % \
     55                (self.ticket)
     56            return results, comment, False
     57        startrev, endrev = rev_info
    5658
    57     svnmergecommand = "svn merge -r %s:%s %s@%s" % (startrev, endrev,
    58         branchurl, startrev)
    59     publicsvnmergecommand = "svn merge -r %s:%s %s@%s" % (startrev, endrev,
    60         publicbranchurl, startrev)
    61     # This is used in the ticket comments.
    62     instructioncomment = "\n".join([
    63             "You will need to fix this manually by creating the branch and "
    64                 "then doing the merge with this command:",
    65             "{{{",
    66             publicsvnmergecommand,
    67             "}}}",
    68     ])
    69     # These are used in the commit messages.
    70     rmmessage = "\n".join([
    71         "Ticket #%s: %s" % (ticketnum, summary),
    72         "    Remove the branch to rebranch from %s for %s." % \
    73             (version, requestor),
    74     ])
    75     copymessage = "\n".join([
    76         "Ticket #%s: %s" % (ticketnum, summary),
    77         "    Recreate branch from %s for %s." % (version, requestor),
    78         "[log:%s/branches/ticket-%s@%s Previous log]." % \
    79             (component, ticketnum, endrev),
    80     ])
    81     # This is a list of commands.  Each of these must complete successfully, in
    82     # this order, to complete the rebranch.  On failure, the given comment
    83     # needs to be added to the ticket log.
    84     commanderrors = (
    85         (lambda x: SvnOps.logcmd("rm -rf \"%s\"" % (workingcopy), x),
    86             "Rebranch internal error: Unable to cleanup work area."),
    87         (lambda x: SvnOps.delete_branch(branchurl, rmmessage, x) == -1,
    88             "Rebranch internal error: Unable to delete the old branch."),
    89         (lambda x: SvnOps.create_branch(baselineurl, branchurl, copymessage, x),
    90             "Rebranch internal error: Unable to recreate the branch.  %s" % \
    91                 (instructioncomment, )),
    92         (lambda x: SvnOps.checkout(branchurl, workingcopy, x),
    93             "Rebranch internal error: Unable to get working copy.  %s" % \
    94                 (instructioncomment, )),
    95     )
    96     for cmd, errorcomment in commanderrors:
    97         retval = cmd(logfile)
    98         if retval:
    99             task_obj.AddComment(errorcomment)
    100             os.system("rm -rf \"%s\"" % (workingcopy, ))
    101             return "rebranchfailed", task_obj
     59        workingcopy = self.work_dir
     60        if os.path.exists(workingcopy):
     61            shutil.rmtree(workingcopy)
    10262
    103     # On success, we're in the same state as if we had just branched.
    104     status = "branched"
    105     # Go ahead and try to do the merge.  If we got lucky and there are no
    106     # conflicts, commit the changes.
    107     # Put a full update on the ticket.
    108     results = SvnOps.merge("%s@%s" % (branchurl, startrev), workingcopy,
    109         (startrev, endrev), logfile)
    110     conflicts = SvnOps.conflicts_from_merge_results(results)
    111     if conflicts:
    112         ticketmessage = "\n".join([
    113             "There were conflicts on rebranching.",
    114             "Files in conflict:",
    115             "{{{",
    116             "\n".join(conflicts),
    117             "}}}",
    118             "You will need to resolve the conflicts manually.",
    119             "To do so, update a working copy to the branch, "
    120                 "and run this merge command:",
    121             "{{{",
    122             publicsvnmergecommand,
    123             "}}}",
    124             "Once you have resolved the conflicts, "
    125                 "commit your work to the branch.",
    126         ])
    127     else: # No conflicts, do the commit.
    128         mergemessage = "\n".join([
    129             "Ticket #%s: %s" % (ticketnum, summary),
    130             "    Merge in changes from old branch for %s." % (requestor),
    131             "    %s" % (svnmergecommand),
    132         ])
    133         newrev = SvnOps.commit(workingcopy, mergemessage, logfile)
    134         if newrev == None:
    135             ticketmessage = "\n".join([
    136                 "Rebranched from %s for %s." % (version, requestor),
    137                 "There were no changes to commit to the branch.",
    138                 "You will need to update your working copy.",
    139             ])
    140         elif newrev < 0:
    141             ticketmessage = "\n".join([
    142                 "Rebranch internal error:  Unable to commit merged changes.",
    143                 "You will need to fix this manually by doing the merge with "
    144                     "this command:",
     63        svnmergecommand = "svn merge -r %s:%s %s@%s" % (startrev, endrev,
     64            self.branch_local_url(), startrev)
     65        publicsvnmergecommand = "svn merge -r %s:%s %s@%s" % (startrev, endrev,
     66            self.branch_public_url(), startrev)
     67        # This is used in the ticket comments when there are conflicts.
     68        instructioncomment = "\n".join([
     69                "You will need to fix this manually by creating the branch and "
     70                    "then doing the merge with this command:",
    14571                "{{{",
    14672                publicsvnmergecommand,
    14773                "}}}",
     74        ])
     75        # These are used in the commit messages.
     76        rmmessage = "\n".join([
     77            "Ticket #%s: %s" % (self.ticket, self.summary),
     78            "    Remove the branch to rebranch from %s for %s." % \
     79                (self.version, self.requestor),
     80        ])
     81        copymessage = "\n".join([
     82            "Ticket #%s: %s" % (self.ticket, self.summary),
     83            "    Recreate branch from %s for %s." % (self.version,
     84                                                     self.requestor),
     85            "[log:%s/branches/ticket-%s@%s Previous log]." % \
     86                (self.component, self.ticket, endrev),
     87        ])
     88        # This is a list of commands.  Each of these must complete
     89        # successfully, in this order, to complete the rebranch.  On failure,
     90        # the given comment needs to be added to the ticket log.
     91        commanderrors = (
     92            (lambda x: SvnOps.delete_branch(self.branch_local_url(), rmmessage,
     93                                            x) == -1,
     94                "Rebranch internal error: Unable to delete the old branch."),
     95            (lambda x: SvnOps.create_branch(self.baseline_local_url(),
     96                    self.branch_local_url(), copymessage, x),
     97                "Rebranch internal error: Unable to recreate the branch.  %s" \
     98                    % (instructioncomment, )),
     99            (lambda x: SvnOps.checkout(self.branch_local_url(), workingcopy, x),
     100                "Rebranch internal error: Unable to get working copy.  %s" % \
     101                    (instructioncomment, )),
     102        )
     103        for cmd, error_comment in commanderrors:
     104            retval = cmd(logfile)
     105            if retval:
     106                if os.path.exists(workingcopy):
     107                    shutil.rmtree(workingcopy)
     108                results['mergebotstate'] = 'rebranchfailed'
     109                return results, error_comment, False
     110
     111        # On success, we're in the same state as if we had just branched.
     112        results['mergebotstate'] = 'branched'
     113        success = True
     114
     115        # Go ahead and try to do the merge.  If we got lucky and there are no
     116        # conflicts, commit the changes.
     117        # Put a full update on the ticket.
     118        merge_results = SvnOps.merge("%s@%s" % (self.branch_local_url(),
     119            startrev), workingcopy, (startrev, endrev), logfile)
     120        conflicts = SvnOps.conflicts_from_merge_results(merge_results)
     121        if conflicts:
     122            ticketmessage = "\n".join([
     123                "There were conflicts on rebranching.",
     124                "Files in conflict:",
     125                "{{{",
     126                "\n".join(conflicts),
     127                "}}}",
     128                "You will need to resolve the conflicts manually.",
     129                "To do so, update a working copy to the branch, "
     130                    "and run this merge command:",
     131                "{{{",
     132                publicsvnmergecommand,
     133                "}}}",
     134                "Once you have resolved the conflicts, "
     135                    "commit your work to the branch.",
     136            ])
     137        else: # No conflicts, do the commit.
     138            mergemessage = "\n".join([
     139                "Ticket #%s: %s" % (self.ticket, self.summary),
     140                "    Merge in changes from old branch for %s." % self.requestor,
     141                "    %s" % svnmergecommand,
    148142            ])
    149             status = "rebranchfailed"
    150         else:
    151             ticketmessage = "\n".join([
    152                 "Rebranched from %s for %s." % (version, requestor),
    153                 "There were no conflicts, so the changes were automatically "
    154                     "merged and committed to the branch.",
    155                 "You will need to update your working copy.",
    156             ])
     143            newrev = SvnOps.commit(workingcopy, mergemessage, logfile)
     144            if newrev == None:
     145                ticketmessage = "\n".join([
     146                    "Rebranched from %s for %s." % (self.version,
     147                                                    self.requestor),
     148                    "There were no changes to commit to the branch.",
     149                    "You will need to update your working copy.",
     150                ])
     151            elif newrev < 0:
     152                ticketmessage = "\n".join([
     153                    "Rebranch internal error:  Unable to commit merged "
     154                        "changes.",
     155                    "You will need to fix this manually by doing the merge "
     156                        "with this command:",
     157                    "{{{",
     158                    publicsvnmergecommand,
     159                    "}}}",
     160                ])
     161                results['mergebotstate'] = 'rebranchfailed'
     162                success = False
     163            else:
     164                ticketmessage = "\n".join([
     165                    "Rebranched from %s for %s." % (self.version,
     166                                                    self.requestor),
     167                    "There were no conflicts, so the changes were "
     168                        "automatically merged and committed to the branch.",
     169                    "You will need to update your working copy.",
     170                ])
    157171
    158     task_obj.AddComment( ticketmessage )
    159     os.system("rm -rf \"%s\"" % (workingcopy, ))
    160     return status, task_obj
    161 
    162 class RebranchActor(MergeBotActor):
    163     "Actor wrapper for rebranch_action"
    164     def __init__(self, trac_env):
    165         MergeBotActor.__init__(self, trac_env, "rebranch", rebranch_action)
    166 
    167 def main():
    168     tracdir = sys.argv[1]
    169     trac_env = trac.env.open_environment(tracdir)
    170     rebranchingActor = RebranchActor(trac_env)
    171     rebranchingActor.AddTask(sys.argv[2:])
    172     rebranchingActor.Run()
    173 
    174 if __name__ == "__main__":
    175     main()
     172        if os.path.exists(workingcopy):
     173            shutil.rmtree(workingcopy)
     174        return results, ticketmessage, success
    176175
    177176# vim:foldcolumn=4 foldmethod=indent
Note: See TracChangeset for help on using the changeset viewer.