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

Revision 1639, 14.8 KB checked in by lele@…, 5 years ago (diff)

Rename the pending file on darcs record failures

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