source: tailor/vcpx/changes.py @ 1627

Revision 1627, 7.8 KB checked in by <walter.franzini@…>, 5 years ago (diff)

print is_directory flag

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
14from vcpx import TailorBug
15
16class ChangesetEntry(object):
17    """
18    Represent a changed entry in a Changeset.
19
20    For our scope, this simply means an entry ``name``, the original
21    ``old_revision``, the ``new_revision`` after this change, an
22    ``action_kind`` to denote the kind of change, and finally a ``status``
23    to indicate possible conflicts.
24    """
25
26    ADDED = 'ADD'
27    DELETED = 'DEL'
28    UPDATED = 'UPD'
29    RENAMED = 'REN'
30
31    APPLIED = 'APPLIED'
32    CONFLICT = 'CONFLICT'
33
34    def __init__(self, name):
35        self.name = name
36        self.old_name = None
37        self.old_revision = None
38        self.new_revision = None
39        self.action_kind = None
40        self.status = None
41        self.unidiff = None # This is the unidiff of this particular entry
42        self.is_directory = False # This usually makes sense only on ADDs and DELs
43
44    def __str__(self):
45        s = self.name + '(' + self.action_kind
46        if self.action_kind == self.ADDED:
47            if self.new_revision:
48                s += ' at ' + self.new_revision
49        elif self.action_kind == self.UPDATED:
50            if self.new_revision:
51                s += ' to ' + self.new_revision
52        elif self.action_kind == self.DELETED:
53            if self.new_revision:
54                s += ' at ' + self.new_revision
55        elif self.action_kind == self.RENAMED:
56            s += ' from ' + self.old_name
57        else:
58            s += '??'
59        if self.is_directory:
60            s += ', DIR'
61        s += ')'
62        if isinstance(s, unicode):
63            s = s.encode('ascii', 'replace')
64        return s
65
66    def __eq__(self, other):
67        return (self.name == other.name and
68                self.old_name == other.old_name and
69                self.old_revision == other.old_revision and
70                self.new_revision == other.new_revision and
71                self.action_kind == other.action_kind)
72
73    def __ne__(self, other):
74        return (self.name != other.name or
75                self.old_name != other.old_name or
76                self.old_revision != other.old_revision or
77                self.new_revision != other.new_revision or
78                self.action_kind != other.action_kind)
79
80from textwrap import TextWrapper
81from re import compile, MULTILINE
82
83itemize_re = compile('^[ ]*[-*] ', MULTILINE)
84
85def refill(msg):
86    """
87    Refill a changelog message.
88
89    Normalize the message reducing multiple spaces and newlines to single
90    spaces, recognizing common form of ``bullet lists``, that is paragraphs
91    starting with either a dash "-" or an asterisk "*".
92    """
93
94    wrapper = TextWrapper()
95    res = []
96    items = itemize_re.split(msg.strip())
97
98    if len(items)>1:
99        # Remove possible first empty split, when the message immediately
100        # starts with a bullet
101        if not items[0]:
102            del items[0]
103
104        if len(items)>1:
105            wrapper.initial_indent = '- '
106            wrapper.subsequent_indent = ' '*2
107
108    for item in items:
109        if item:
110            words = filter(None, item.strip().replace('\n', ' ').split(' '))
111            normalized = ' '.join(words)
112            res.append(wrapper.fill(normalized))
113
114    return '\n\n'.join(res)
115
116
117class Changeset(object):
118    """
119    Represent a single upstream Changeset.
120
121    This is a container of each file affected by this revision of the tree.
122    """
123
124    ANONYMOUS_USER = "anonymous"
125    """Author name when it is not known"""
126
127    REFILL_MESSAGE = False
128    """Refill changelogs"""
129
130    def _get_date(self):
131        try:
132            return self.__date
133        except AttributeError, e:
134            # handle state-file Changesets created with previous versions of tailor
135            from vcpx.tzinfo import UTC
136            self.__date = self.__dict__['date'].replace(tzinfo=UTC)
137            return self.__date
138
139    def _set_date(self, date):
140        if date and date.tzinfo is None:
141            raise TailorBug("Changeset dates must have a timezone!")
142        self.__date = date
143
144    # date has to be a property because some backends (eg. monotone)
145    # update it after the constructor
146    date = property(_get_date, _set_date)
147
148    def __init__(self, revision, date, author, log, entries=None, **other):
149        """
150        Initialize a new Changeset.
151        """
152
153        self.revision = revision
154        self.date = date
155        # Author name may be missing, to mean a check in made by an
156        # anonymous user.
157        self.author = author or self.ANONYMOUS_USER
158        self.setLog(log)
159        self.entries = entries or []
160        self.unidiff = None        # This is the unidiff of the whole changeset
161        self.tags = other.get('tags', None)
162
163    # Don't take into account the entries, to compare changesets, because they
164    # may be loaded after changeset application: the not-yet-applied changeset
165    # will be different from the same-but-just-applied one.
166
167    def __eq__(self, other):
168        return (self.revision == other.revision and
169                self.date == other.date and
170                self.author == other.author)
171
172    def __ne__(self, other):
173        return (self.revision <> other.revision or
174                self.date <> other.date or
175                self.author <> other.author)
176
177    def setLog(self, log):
178        if self.REFILL_MESSAGE:
179            self.log = refill(log)
180        else:
181            self.log = log
182
183    def addEntry(self, entry, revision, before=None):
184        """
185        Facility to add an entry, eventually before another one.
186        """
187
188        e = ChangesetEntry(entry)
189        e.new_revision = revision
190        if before is None:
191            self.entries.append(e)
192        else:
193            self.entries.insert(self.entries.index(before), e)
194        return e
195
196    def __str__(self):
197        s = []
198        s.append('Revision: %s' % self.revision)
199        s.append('Date: %s' % str(self.date))
200        s.append('Author: %s' % self.author)
201        s.append('Entries: %s' % ', '.join([str(x) for x in self.entries]))
202        s.append('Log: %s' % self.log)
203        s = '\n'.join(s)
204        if isinstance(s, unicode):
205            s = s.encode('ascii', 'replace')
206        return s
207
208    def applyPatch(self, working_dir, patch_options="-p1"):
209        """
210        Apply the changeset using ``patch(1)`` to a given directory.
211        """
212
213        from shwrap import ExternalCommand
214        from source import ChangesetApplicationFailure
215
216        if self.unidiff:
217            cmd = ["patch"]
218            if patch_options:
219                if isinstance(patch_options, basestring):
220                    cmd.extend(patch_options.split(' '))
221                else:
222                    cmd.extend(patch_options)
223
224            patch = ExternalCommand(cwd=working_dir, command=cmd)
225            patch.execute(input=self.unidiff)
226
227            if patch.exit_status:
228                raise ChangesetApplicationFailure(
229                    "%s returned status %s" % (str(patch), patch.exit_status))
230
231    def addedEntries(self):
232        """
233        Facility to extract a list of added entries.
234        """
235
236        return [e for e in self.entries if e.action_kind == e.ADDED]
237
238    def modifiedEntries(self):
239        """
240        Facility to extract a list of modified entries.
241        """
242
243        return [e for e in self.entries if e.action_kind == e.UPDATED]
244
245    def removedEntries(self):
246        """
247        Facility to extract a list of deleted entries.
248        """
249
250        return [e for e in self.entries if e.action_kind == e.DELETED]
251
252    def renamedEntries(self):
253        """
254        Facility to extract a list of renamed entries.
255        """
256
257        return [e for e in self.entries if e.action_kind == e.RENAMED]
Note: See TracBrowser for help on using the repository browser.