source: mergebot/trunk/mergebot/SvnOps.py @ 16

Last change on this file since 16 was 16, checked in by retracile, 15 years ago

Mergebot: Codebase as released with permission from CommProve?, plus cleanups to remove traces of that environment.

File size: 4.7 KB
Line 
1#!/usr/bin/python
2"""
3Encapsulate logical Subversion operations so the various MergeBot actors can
4operate at a higher level of abstraction.
5"""
6
7import os
8import time
9import re
10
11def shell_quote(string):
12    """Given a string, escape the characters interpretted by the shell."""
13    for char in ["\\", "\"", "$"]:
14        string = string.replace(char, "\\%s" % (char, ))
15    return '"%s"' % (string, )
16
17def logcmd(cmd, logfile):
18    """Log the cmd string, then execute it, appending its stdout and stderr to
19    logfile."""
20    open(logfile, "a").write("%s: %s\n" % (time.asctime(), cmd))
21    return os.system("(%s) >>%s 2>&1" % (cmd, logfile))
22
23def get_rev_from_log(logentry):
24    """Given a log entry split out of svn log, return its revision number"""
25    return int(logentry.split()[0][1:])
26
27def get_branch_info(url, logfile):
28    """Given a subversion url and a logfile, return (start_revision,
29    end_revision) or None if it does not exist."""
30    svncmd = os.popen("svn log --stop-on-copy --non-interactive %s 2>>%s" % \
31        (url, logfile), "r")
32    branchlog = svncmd.read()
33    returnval = svncmd.close()
34    if returnval:
35        # This branch apparently doesn't exist
36        return None
37    logs = branchlog.split("-"*72 + "\n")
38    # If there have been no commits on the branch since it was created, there
39    # will only be one revision listed.... but the log will split into 3 parts.
40    endrev = get_rev_from_log(logs[1])
41    startrev = get_rev_from_log(logs[-2])
42    return (startrev, endrev)
43
44def create_branch(from_url, to_url, commit_message, logfile):
45    """Create a branch copying from_url to to_url.  Commit as mergebot, and use
46    the provided commit message."""
47    svncmd = \
48        "svn copy --username=mergebot --password=mergebot -m %s %s %s" \
49        % (shell_quote(commit_message), from_url, to_url)
50    return logcmd(svncmd, logfile)
51
52def delete_branch(url, commit_message, logfile):
53    """This will generate a new revision.  Return the revision number, or -1 on
54    failure.
55    Assumes that the url exists.  You should call get_branch_info() to
56    determine that first"""
57    svncmd = "svn rm --no-auth-cache --username=mergebot --password=mergebot " \
58        "-m %s %s 2>>%s" % (shell_quote(commit_message), url, logfile)
59    return _svn_new_rev_command(svncmd)
60
61def checkout(from_url, workingdir, logfile):
62    """Checkout from the given url into workingdir"""
63    return os.system("svn checkout %s %s >>%s 2>&1" % (from_url, workingdir,
64        logfile))
65
66def merge(from_url, workingdir, revision_range, logfile):
67    """Returns a list (status, filename) tuples"""
68    # There are a couple of different 'Skipped' messages.
69    skipped_regex = re.compile("Skipped.* '(.*)'", re.M)
70    start_rev, end_rev = revision_range
71    pipe = os.popen("cd %s && svn merge --revision %s:%s %s . 2>>%s" % \
72        (workingdir, start_rev, end_rev, from_url, logfile))
73    output = pipe.readlines()
74    # FIXME: check pipe.close for errors
75    results = []
76    for line in output:
77        if line.startswith("Skipped"):
78            # This kind of conflict requires special handling.
79            filename = skipped_regex.findall(line)[0]
80            status = "C"
81        else:
82            assert line[4] == ' ', "Unexpected output from svn merge " \
83                "operation; the 5th character should always be a space." \
84                "  Output was %r." % line
85            filename = line[5:-1] # (strip trailing newline)
86            status = line[:4].rstrip()
87        results.append((status, filename))
88    return results
89
90def conflicts_from_merge_results(results):
91    "Given the output from merge, return a list of files that had conflicts."
92    conflicts = [filename for status, filename in results if 'C' in status]
93    return conflicts
94
95def commit(workingdir, commit_message, logfile):
96    """Returns newly committed revision number, or None if there was nothing to
97    commit.  -1 on error."""
98    svncmd = "cd %s && svn commit --no-auth-cache --username=mergebot " \
99        "--password=mergebot -m %s 2>>%s" % (workingdir,
100            shell_quote(commit_message), logfile)
101    return _svn_new_rev_command(svncmd)
102
103def _svn_new_rev_command(svncmd):
104    """Given an svn command that results in a new revision, return the revision
105    number, or -1 on error."""
106    pipe = os.popen(svncmd)
107    output = pipe.read()
108    retval = pipe.close()
109    if retval:
110        new_revision = -1
111    else:
112        new_revisions = re.compile("Committed revision ([0-9]+)\\.",
113            re.M).findall(output)
114        if new_revisions:
115            new_revision = new_revisions[0]
116        else:
117            new_revision = None
118    return new_revision
119
120# vim:foldcolumn=4 foldmethod=indent
121# vim:tabstop=4 shiftwidth=4 softtabstop=4 expandtab
Note: See TracBrowser for help on using the repository browser.