source: tailor/vcpx/changes.py @ 706

Revision 706, 6.1 KB checked in by R.Ghetta <birrachiara@…>, 8 years ago (diff)

created a MonotoneChangeset? to better handle monotone specific changeset informations.
The monotone-specific changeset uses only the revision field for equality comparison, and maintains a "linearized" ancestor field, containing the ancestor of current revision in the linearized (fake) revision chain.

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    ANONYMOUS_USER = "anonymous"
98    """Author name when it is not known"""
99
100    REFILL_MESSAGE = False
101    """Refill changelogs"""
102
103    def __init__(self, revision, date, author, log, entries=None, **other):
104        """
105        Initialize a new Changeset.
106        """
107
108        self.revision = revision
109        self.date = date
110        # Author name may be missing, to mean a check in made by an
111        # anonymous user.
112        self.author = author or self.ANONYMOUS_USER
113        self.setLog(log)
114        self.entries = entries or []
115        self.unidiff = None        # This is the unidiff of the whole changeset
116
117    def __eq__(self, other):
118        return (self.revision == other.revision and
119                self.date == other.date and
120                self.author == other.author)
121
122    def __ne__(self, other):
123        return (self.revision <> other.revision or
124                self.date <> other.date or
125                self.author <> other.author)
126
127    def setLog(self, log):
128        if self.REFILL_MESSAGE:
129            self.log = refill(log)
130        else:
131            self.log = log.strip()
132
133    def addEntry(self, entry, revision):
134        """
135        Facility to add an entry.
136        """
137
138        e = ChangesetEntry(entry)
139        e.new_revision = revision
140        self.entries.append(e)
141        return e
142
143    def __str__(self):
144        s = []
145        s.append('Revision: %s' % self.revision)
146        s.append('Date: %s' % str(self.date))
147        s.append('Author: %s' % self.author)
148        for ak in ['Modified', 'Removed', 'Renamed', 'Added']:
149            entries = getattr(self, ak.lower()+'Entries')()
150            if entries:
151                if ak == 'Renamed':
152                    entries = ['%s (from %s)' % (e.name, e.old_name)
153                               for e in entries]
154                else:
155                    entries = [e.name for e in entries]
156                s.append('%s: %s' % (ak, ','.join(entries)))
157        s.append('Log: %s' % self.log)
158        return '\n'.join(s)
159
160    def applyPatch(self, working_dir, patch_options="-p1"):
161        """
162        Apply the changeset using ``patch(1)`` to a given directory.
163        """
164
165        from shwrap import ExternalCommand
166        from source import ChangesetApplicationFailure
167
168        if self.unidiff:
169            cmd = ["patch"]
170            if patch_options:
171                if isinstance(patch_options, basestring):
172                    cmd.extend(patch_options.split(' '))
173                else:
174                    cmd.extend(patch_options)
175
176            patch = ExternalCommand(cwd=working_dir, command=cmd)
177            patch.execute(input=self.unidiff)
178
179            if patch.exit_status:
180                raise ChangesetApplicationFailure(
181                    "%s returned status %s" % (str(patch), patch.exit_status))
182
183    def addedEntries(self):
184        """
185        Facility to extract a list of added entries.
186        """
187
188        return [e for e in self.entries if e.action_kind == e.ADDED]
189
190    def modifiedEntries(self):
191        """
192        Facility to extract a list of modified entries.
193        """
194
195        return [e for e in self.entries if e.action_kind == e.UPDATED]
196
197    def removedEntries(self):
198        """
199        Facility to extract a list of deleted entries.
200        """
201
202        return [e for e in self.entries if e.action_kind == e.DELETED]
203
204    def renamedEntries(self):
205        """
206        Facility to extract a list of renamed entries.
207        """
208
209        return [e for e in self.entries if e.action_kind == e.RENAMED]
Note: See TracBrowser for help on using the repository browser.