source: tailor/vcpx/repository/darcs/target.py @ 1618

Revision 1618, 14.5 KB checked in by lele@…, 5 years ago (diff)

Don't perform the post-commit-check if using look-for-adds=True with darcs

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: Tailor -- Darcs peculiarities when used as a target
3# :Creato:   lun 10 lug 2006 00:12:15 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
5# :Licenza:  GNU General Public License
6#
7
8"""
9This module contains the target specific bits of the darcs backend.
10"""
11
12__docformat__ = 'reStructuredText'
13
14import re
15
16from vcpx.shwrap import ExternalCommand, PIPE, STDOUT
17from vcpx.target import ChangesetReplayFailure, SynchronizableTargetWorkingDir, \
18                        PostCommitCheckFailure
19from vcpx.tzinfo import UTC
20
21
22MOTD = """\
23Tailorized equivalent of
24%s
25"""
26
27
28class DarcsTargetWorkingDir(SynchronizableTargetWorkingDir):
29    """
30    A target working directory under ``darcs``.
31    """
32
33    def importFirstRevision(self, source_repo, changeset, initial):
34        from os import walk, sep
35        from os.path import join
36        from vcpx.dualwd import IGNORED_METADIRS
37
38        if not self.repository.split_initial_import_level:
39            super(DarcsTargetWorkingDir, self).importFirstRevision(
40                source_repo, changeset, initial)
41        else:
42            cmd = self.repository.command("add", "--case-ok", "--quiet")
43            add = ExternalCommand(cwd=self.repository.basedir, command=cmd)
44            cmd = self.repository.command("add", "--case-ok", "--recursive",
45                                          "--quiet")
46            addrecurs = ExternalCommand(cwd=self.repository.basedir, command=cmd)
47            for root, dirs, files in walk(self.repository.basedir):
48                subtree = root[len(self.repository.basedir)+1:]
49                if subtree:
50                    log = "Import of subtree %s" % subtree
51                    level = len(subtree.split(sep))
52                else:
53                    log = "Import of first level"
54                    level = 0
55                for excd in IGNORED_METADIRS:
56                    if excd in dirs:
57                        dirs.remove(excd)
58                if level>self.repository.split_initial_import_level:
59                    while dirs:
60                        d = dirs.pop(0)
61                        addrecurs.execute(join(subtree, d))
62                    filenames = [join(subtree, f) for f in files]
63                    if filenames:
64                        add.execute(*filenames)
65                else:
66                    dirnames = [join(subtree, d) for d in dirs]
67                    if dirnames:
68                        add.execute(*dirnames)
69                    filenames = [join(subtree, f) for f in files]
70                    if filenames:
71                        add.execute(*filenames)
72                self._commit(changeset.date, "tailor", "Initial import",
73                             log, isinitialcommit=initial)
74
75            cmd = self.repository.command("tag", "--author", "tailor")
76            ExternalCommand(cwd=self.repository.basedir, command=cmd).execute(
77                "Initial import from %s" % source_repo.repository)
78
79    def _addPathnames(self, names):
80        """
81        Add some new filesystem objects.
82        """
83
84        cmd = self.repository.command("add", "--case-ok", "--not-recursive",
85                                      "--quiet")
86        ExternalCommand(cwd=self.repository.basedir, command=cmd).execute(names)
87
88    def _addSubtree(self, subdir):
89        """
90        Use the --recursive variant of ``darcs add`` to add a subtree.
91        """
92
93        cmd = self.repository.command("add", "--case-ok", "--recursive",
94                                      "--quiet")
95        add = ExternalCommand(cwd=self.repository.basedir, command=cmd,
96                              ok_status=(0,2))
97        output = add.execute(subdir, stdout=PIPE, stderr=STDOUT)[0]
98        if add.exit_status and add.exit_status!=2:
99            self.log.warning("%s returned status %d, saying %s",
100                             str(add), add.exit_status, output.read())
101
102    def _commit(self, date, author, patchname, changelog=None, entries=None,
103                tags = [], isinitialcommit = False):
104        """
105        Commit the changeset.
106        """
107
108        logmessage = []
109
110        logmessage.append(date.astimezone(UTC).strftime('%Y/%m/%d %H:%M:%S UTC'))
111        logmessage.append(author)
112        if patchname:
113            logmessage.append(patchname)
114        else:
115            # This is possibile also when REMOVE_FIRST_LOG_LINE is in
116            # effect and the changelog starts with newlines: discard
117            # those, otherwise darcs will complain about invalid patch
118            # name
119            if changelog and changelog.startswith('\n'):
120                while changelog.startswith('\n'):
121                    changelog = changelog[1:]
122        if changelog:
123            logmessage.append(changelog)
124
125        if not logmessage:
126            logmessage.append('Unnamed patch')
127
128        cmd = self.repository.command("record", "--all", "--pipe", "--ignore-times")
129        if not entries:
130            entries = ['.']
131
132        record = ExternalCommand(cwd=self.repository.basedir, command=cmd)
133        output = record.execute(input=self.repository.encode('\n'.join(logmessage)),
134                                stdout=PIPE, stderr=STDOUT)[0]
135
136        if record.exit_status:
137            raise ChangesetReplayFailure(
138                "%s returned status %d, saying: %s" % (str(record),
139                                                       record.exit_status,
140                                                       output.read()))
141
142    def _postCommitCheck(self):
143        # If we are using --look-for-adds on commit this is useless
144        if not self.repository.use_look_for_adds:
145            cmd = self.repository.command("whatsnew", "--summary", "--look-for-add")
146            whatsnew = ExternalCommand(cwd=self.repository.basedir, command=cmd, ok_status=(1,))
147            output = whatsnew.execute(stdout=PIPE, stderr=STDOUT)[0]
148            if not whatsnew.exit_status:
149                raise PostCommitCheckFailure(
150                    "Changes left in working dir after commit:\n%s" % output.read())
151
152    def _replayChangeset(self, changeset):
153        """
154        Instead of using the "darcs mv" command, manually add
155        the rename to the pending file: this is a dirty trick, that
156        allows darcs to handle the case when the source changeset
157        is something like::
158          $ bzr mv A B
159          $ touch A
160          $ bzr add A
161        where A is actually replaced, and old A is now B. Since by the
162        time the changeset gets replayed, the source has already replaced
163        A with its new content, darcs would move the *wrong* A to B...
164        """
165
166        from os.path import join, exists
167
168        # The "_darcs/patches/pending" file is basically a patch containing
169        # only the changes (hunks, adds...) not yet recorded by darcs: it does
170        # contain either a single change (that is, exactly one line), or a
171        # collection of changes, with opening and closing curl braces.
172        # Filenames must begin with "./", and eventual spaces replaced by '\32\'.
173        # Order is significant!
174
175        pending = join(self.repository.basedir, '_darcs', 'patches', 'pending')
176        if exists(pending):
177            p = open(pending).readlines()
178            if p[0] != '{\n':
179                p.insert(0, '{\n')
180                p.append('}\n')
181        else:
182            p = [ '{\n', '}\n' ]
183
184        entries = []
185
186        while changeset.entries:
187            e = changeset.entries.pop(0)
188            if e.action_kind == e.DELETED:
189                elide = False
190                for j,oe in enumerate(changeset.entries):
191                    if oe.action_kind == oe.ADDED and e.name == oe.name:
192                        self.log.debug('Collapsing a %s and a %s on %s, assuming '
193                                       'an upstream "replacement"',
194                                       e.action_kind, oe.action_kind, oe.name)
195                        del changeset.entries[j]
196                        elide = True
197                        break
198                if not elide:
199                    entries.append(e)
200            elif e.action_kind == e.ADDED:
201                elide = False
202                for j,oe in enumerate(changeset.entries):
203                    if oe.action_kind == oe.DELETED and e.name == oe.name:
204                        self.log.debug('Collapsing a %s and a %s on %s, assuming '
205                                       'an upstream "replacement"',
206                                       e.action_kind, oe.action_kind, oe.name)
207                        del changeset.entries[j]
208                        elide = True
209                        break
210                if not elide:
211                    entries.append(e)
212            else:
213                entries.append(e)
214
215        changed = False
216        for e in entries:
217            if e.action_kind == e.RENAMED:
218                self.log.debug('Mimicing "darcs mv %s %s"',
219                               e.old_name, e.name)
220                oname = e.old_name.replace(' ', '\\32\\')
221                nname = e.name.replace(' ', '\\32\\')
222                p.insert(-1, 'move ./%s ./%s\n' % (oname, nname))
223                changed = True
224            elif e.action_kind == e.ADDED:
225                self.log.debug('Mimicing "darcs add %s"', e.name)
226                name = e.name.replace(' ', '\\32\\')
227                if e.is_directory:
228                    p.insert(-1, 'adddir ./%s\n' % name)
229                else:
230                    p.insert(-1, 'addfile ./%s\n' % name)
231                changed = True
232            elif e.action_kind == e.DELETED:
233                self.log.debug('Mimicing "darcs rm %s"', e.name)
234                name = e.name.replace(' ', '\\32\\')
235                if e.is_directory:
236                    p.insert(-1, 'rmdir ./%s\n' % name)
237                else:
238                    p.insert(-1, 'rmfile ./%s\n' % name)
239                changed = True
240        if changed:
241            open(pending, 'w').writelines(p)
242        return True
243
244    def _prepareTargetRepository(self):
245        """
246        Create the base directory if it doesn't exist, and execute
247        ``darcs initialize`` if needed.
248        """
249
250        from os.path import join, exists
251
252        metadir = join(self.repository.basedir, '_darcs')
253
254        if not exists(metadir):
255            self.repository.create()
256
257        prefsdir = join(metadir, 'prefs')
258        prefsname = join(prefsdir, 'prefs')
259        boringname = join(prefsdir, 'boring')
260        if exists(prefsname):
261            for pref in open(prefsname, 'rU'):
262                if pref:
263                    pname, pvalue = pref.split(' ', 1)
264                    if pname == 'boringfile':
265                        boringname = join(self.repository.basedir, pvalue[:-1])
266
267        boring = open(boringname, 'rU')
268        ignored = boring.read().rstrip().split('\n')
269        boring.close()
270
271        # Build a list of compiled regular expressions, that will be
272        # used later to filter the entries.
273        self.__unwanted_entries = [re.compile(rx) for rx in ignored
274                                   if rx and not rx.startswith('#')]
275
276    def _prepareWorkingDirectory(self, source_repo):
277        """
278        Tweak the default settings of the repository.
279        """
280
281        from os.path import join
282
283        motd = open(join(self.repository.basedir, '_darcs/prefs/motd'), 'w')
284        motd.write(MOTD % str(source_repo))
285        motd.close()
286
287    def _adaptEntries(self, changeset):
288        """
289        Filter out boring files.
290        """
291
292        from copy import copy
293
294        adapted = SynchronizableTargetWorkingDir._adaptEntries(self, changeset)
295
296        # If there are no entries or no rules, there's nothing to do
297        if not adapted or not adapted.entries or not self.__unwanted_entries:
298            return adapted
299
300        entries = []
301        skipped = False
302        for e in adapted.entries:
303            skip = False
304            for rx in self.__unwanted_entries:
305                if rx.search(e.name):
306                    skip = True
307                    break
308            if skip:
309                self.log.info('Entry "%s" skipped per boring rules', e.name)
310                skipped = True
311            else:
312                entries.append(e)
313
314        # All entries are gone, don't commit this changeset
315        if not entries:
316            self.log.info('All entries ignored, skipping whole '
317                          'changeset "%s"', changeset.revision)
318            return None
319
320        if skipped:
321            adapted = copy(adapted)
322            adapted.entries = entries
323
324        return adapted
325
326    def _tag(self, tag, date, author):
327        """
328        Apply the given tag to the repository, unless it has already
329        been applied to the current state. (If it has been applied to
330        an earlier state, do apply it; the later tag overrides the
331        earlier one.
332        """
333        if tag not in self._currentTags():
334            cmd = self.repository.command("tag", "--author", "Unknown tagger")
335            ExternalCommand(cwd=self.repository.basedir, command=cmd).execute(tag)
336
337    def _currentTags(self):
338        """
339        Return a list of tags that refer to the repository's current
340        state.  Does not consider tags themselves to be part of the
341        state, so if the repo was tagged with T1 and then T2, then
342        both T1 and T2 are considered to refer to the current state,
343        even though 'darcs get --tag=T1' and 'darcs get --tag=T2'
344        would have different results (the latter creates a repo that
345        contains tag T2, but the former does not).
346
347        This function assumes that a tag depends on all patches that
348        precede it in the "darcs changes" list.  This assumption is
349        valid if tags only come into the repository via tailor; if the
350        user applies a tag by hand in the hybrid repository, or pulls
351        in a tag from another darcs repository, then the assumption
352        could be violated and mistagging could result.
353        """
354
355        from vcpx.repository.darcs.source import changesets_from_darcschanges_unsafe
356
357        cmd = self.repository.command("changes",
358                                      "--from-match", "not name ^TAG",
359                                      "--xml-output", "--reverse")
360        changes =  ExternalCommand(cwd=self.repository.basedir, command=cmd)
361        output = changes.execute(stdout=PIPE)[0]
362        if changes.exit_status:
363            raise ChangesetReplayFailure(
364                "%s returned status %d saying\n%s" %
365                (str(changes), changes.exit_status, output.read()))
366
367        tags = []
368        for cs in changesets_from_darcschanges_unsafe(output):
369            for tag in cs.tags:
370                if tag not in tags:
371                    tags.append(tag)
372        return tags
Note: See TracBrowser for help on using the repository browser.