source: tailor/vcpx/changes.py @ 393

Revision 393, 5.6 KB checked in by lele@…, 8 years ago (diff)

Transition to a Python 2.4 subprocess compatible way of executing external tools
The shwrap module now makes use of the new subprocess module available
with Python 2.4; under older snakes it uses an almost compatible module,
_process.py. This does not bring in any new functionality, it should just
make it easier to port the tool under other OS, and hopefully use a less
hackish way of doing the task.

BEWARE: even if I did several round trips over the various backends, there
may still be bugs in the way I translated the external commands.

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Changesets
3# :Creato:   ven 11 giu 2004 15:31:18 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
5# :Licenza:  GNU General Public License
6#
7
8"""
9Changesets are an object representation of a set of changes to some files.
10"""
11
12__docformat__ = 'reStructuredText'
13
14class ChangesetEntry(object):
15    """
16    Represent a changed entry in a Changeset.
17
18    For our scope, this simply means an entry ``name``, the original
19    ``old_revision``, the ``new_revision`` after this change, an
20    ``action_kind`` to denote the kind of change, and finally a ``status``
21    to indicate possible conflicts.
22    """
23   
24    ADDED = 'ADD'
25    DELETED = 'DEL'
26    UPDATED = 'UPD'
27    RENAMED = 'REN'
28
29    APPLIED = 'APPLIED'
30    CONFLICT = 'CONFLICT'
31   
32    def __init__(self, name):
33        self.name = name
34        self.old_name = None
35        self.old_revision = None
36        self.new_revision = None
37        self.action_kind = None
38        self.status = None
39        self.unidiff = None # This is the unidiff of this particular entry
40
41    def __str__(self):
42        if self.action_kind == self.ADDED:
43            return '%s (new at %s)' % (self.name, self.new_revision)
44        elif self.action_kind == self.DELETED:
45            return '%s (deleted)' % self.name
46        elif self.action_kind == self.UPDATED:
47            return "%s (update to %s)" % (self.name,
48                                          self.new_revision)
49        else:
50            return '%s (rename from %s)' % (self.name, self.old_name)
51
52
53from textwrap import TextWrapper
54from re import compile, MULTILINE
55   
56itemize_re = compile('^[ ]*[-*] ', MULTILINE)
57
58def refill(msg):
59    """
60    Refill a changelog message.
61
62    Normalize the message reducing multiple spaces and newlines to single
63    spaces, recognizing common form of ``bullet lists``, that is paragraphs
64    starting with either a dash "-" or an asterisk "*".
65    """
66   
67    wrapper = TextWrapper()
68    res = []
69    items = itemize_re.split(msg.strip())
70   
71    if len(items)>1:
72        # Remove possible first empty split, when the message immediately
73        # starts with a bullet
74        if not items[0]:
75            del items[0]
76           
77        if len(items)>1:
78            wrapper.initial_indent = '- '
79            wrapper.subsequent_indent = ' '*2
80               
81    for item in items:
82        if item:
83            words = filter(None, item.strip().replace('\n', ' ').split(' '))
84            normalized = ' '.join(words)
85            res.append(wrapper.fill(normalized))
86
87    return '\n\n'.join(res)
88
89
90class Changeset(object):
91    """
92    Represent a single upstream Changeset.
93
94    This is a container of each file affected by this revision of the tree.
95    """
96
97    REFILL_MESSAGE = True
98    """Refill changelogs"""
99   
100    def __init__(self, revision, date, author, log, entries=None, **other):
101        """
102        Initialize a new Changeset.
103        """
104       
105        self.revision = revision
106        self.date = date
107        self.author = author
108        if self.REFILL_MESSAGE:
109            self.log = refill(log)
110        else:
111            self.log = log
112        self.entries = entries or []
113        self.unidiff = None        # This is the unidiff of the whole changeset
114
115    def addEntry(self, entry, revision):
116        """
117        Facility to add an entry.
118        """
119
120        e = ChangesetEntry(entry)
121        e.new_revision = revision
122        self.entries.append(e)
123        return e
124   
125    def __str__(self):
126        s = []
127        s.append('Revision: %s' % self.revision)
128        s.append('Date: %s' % str(self.date))
129        s.append('Author: %s' % self.author)
130        for ak in ['Modified', 'Removed', 'Renamed', 'Added']:
131            entries = getattr(self, ak.lower()+'Entries')()
132            if entries:
133                if ak == 'Renamed':
134                    entries = ['%s (from %s)' % (e.name, e.old_name)
135                               for e in entries]
136                else:
137                    entries = [e.name for e in entries]
138                s.append('%s: %s' % (ak, ','.join(entries)))
139        s.append('Log: %s' % self.log)
140        return '\n'.join(s)
141
142    def applyPatch(self, working_dir, patch_options="-p1"):
143        """
144        Apply the changeset using ``patch(1)`` to a given directory.
145        """
146       
147        from shwrap import ExternalCommand
148        from source import ChangesetApplicationFailure
149
150        if self.unidiff:
151            cmd = ["patch"]
152            if patch_options:
153                if isinstance(patch_options, basestring):
154                    cmd.extend(patch_options.split(' '))
155                else:
156                    cmd.extend(patch_options)
157
158            patch = ExternalCommand(cwd=working_dir, command=cmd)
159            patch.execute(input=self.unidiff)
160           
161            if patch.exit_status:
162                raise ChangesetApplicationFailure(
163                    "%s returned status %s" % (str(patch), patch.exit_status))
164       
165    def addedEntries(self):
166        """
167        Facility to extract a list of added entries.
168        """
169       
170        return [e for e in self.entries if e.action_kind == e.ADDED]
171
172    def modifiedEntries(self):
173        """
174        Facility to extract a list of modified entries.
175        """
176
177        return [e for e in self.entries if e.action_kind == e.UPDATED]
178
179    def removedEntries(self):
180        """
181        Facility to extract a list of deleted entries.
182        """
183
184        return [e for e in self.entries if e.action_kind == e.DELETED]
185
186    def renamedEntries(self):
187        """
188        Facility to extract a list of renamed entries.
189        """
190
191        return [e for e in self.entries if e.action_kind == e.RENAMED]
Note: See TracBrowser for help on using the repository browser.