source: tailor/vcpx/repository/aegis/target.py @ 1584

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

aegis backend simulate the (missing) aemv -keep command
The aegis -move command does not provide the -keep option to preserve
the content of the newly created file, so the target backend simulate
that behavior.

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Aegis details
3# :Creato:   sab 24 mag 2008 15:44:00 CEST
4# :Autore:   Walter Franzini <walter.franzini@gmail.com>
5# :Licenza:  GNU General Public License
6#
7
8"""
9This module contains the target specific bits of the Aegis backend.
10"""
11
12__docformat__ = 'reStructuredText'
13
14import os
15import os.path
16import re
17import shutil
18
19from vcpx.changes import ChangesetEntry
20from vcpx.shwrap import ExternalCommand, ReopenableNamedTemporaryFile, PIPE, STDOUT
21from vcpx.source import ChangesetApplicationFailure
22from vcpx.target import SynchronizableTargetWorkingDir
23
24
25class AegisTargetWorkingDir(SynchronizableTargetWorkingDir):
26    """
27    A target working directory under ``Aegis``.
28    """
29
30    change_number = "not-a-number"
31
32    def _commit(self, date, author, patchname, changelog=None, entries=None,
33                tags = [], isinitialcommit = False):
34        """
35        Commit the changeset.
36        """
37
38        #
39        # The invocation for the initialcommit does not receive entries.
40        #
41        if isinitialcommit:
42            self.__new_file("aegis.conf", "config")
43            self.__config_file(self.repository.basedir, "aegis.conf")
44        elif not entries:
45            #
46            # Return successfully even if the changeset does not
47            # contain entries just in case it's a tag changeset.
48            #
49            return True
50
51
52        change_attribute_file = \
53            self.__change_attribute_file(brief_description=patchname,
54                                         description=changelog)
55        self.__change_attribute(change_attribute_file.name)
56        self.__develop_end()
57        #
58        # If the change cannot be closed, e.g. because a file is
59        # modified in a change not yet integrated, then we should stop here.
60        #
61        # return False
62        #
63        # Next step only if develop_end => awaiting_integration
64        #
65        self.__integrate_begin()
66        self.__finish()
67
68    def _prepareTargetRepository(self):
69        #
70        # Aegis refuse to use an already existing directory as the
71        # development directory of a change.
72        #
73        if os.path.exists(self.repository.basedir):
74            shutil.rmtree(self.repository.basedir)
75
76    def _prepareToReplayChangeset(self, changeset):
77        """
78        Runs aegis -New_Change -dir target.basedir
79        """
80
81        self._prepareTargetRepository()
82        self.change_number = self.__new_change(changeset.revision)
83        self.__develop_begin()
84        #
85        # This function MUST return
86        #
87        return True
88
89    def _adaptChangeset(self, changeset):
90        project_files = self.repository.project_file_list_get()
91        if not project_files:
92            return SynchronizableTargetWorkingDir._adaptChangeset(self, changeset)
93
94        from copy import deepcopy
95        adapted = deepcopy(changeset)
96
97        #
98        # adapt the entries:
99        # * delete entries with action_kind REMOVE not in the repository (DEL => )
100        # * change to ADD a rename of a file non in the repository (REN => ADD)
101        # * remove from the changeset entries whith 2 operation (REN + UPD => REN);
102        # * change the ADD action_kind for files already in the repository (ADD => UPD);
103        # * change the UPD action_kind for files *non* in the repository (UPD => ADD);
104        #
105        for e in adapted.entries:
106            if e.action_kind == 'DEL' and project_files.count(e.name) == 0:
107                self.log.info("remove delete entry %s", e.name)
108                adapted.entries.remove(e)
109
110        renamed_file = []
111        for e in adapted.entries:
112            if e.action_kind == ChangesetEntry.RENAMED:
113                renamed_file.append(e.name)
114
115        for e in adapted.entries:
116            if renamed_file.count(e.name) > 0 and e.action_kind != ChangesetEntry.RENAMED:
117                adapted.entries.remove(e)
118            if e.action_kind == ChangesetEntry.RENAMED and project_files.count(e.old_name) == 0:
119                e.action_kind = ChangesetEntry.ADDED
120                e.old_name = None
121            if e.action_kind == ChangesetEntry.ADDED and project_files.count(e.name) > 0:
122                e.action_kind = ChangesetEntry.UPDATED
123            elif e.action_kind == ChangesetEntry.UPDATED and project_files.count(e.name) == 0:
124                e.action_kind = ChangesetEntry.ADDED
125
126        #
127        # Returns even if the changeset does not contain entries to
128        # give the opportunity to still register tags.
129        #
130        return SynchronizableTargetWorkingDir._adaptChangeset(self, adapted)
131
132    def _initializeWorkingDir(self):
133        #
134        # This method is called only by importFirstRevision
135        #
136        self.__new_file('.')
137
138    def _prepareWorkingDirectory(self, source_repo):
139        #
140        # Receive the first changeset from the source repository.
141        #
142        self.change_number = self.__new_change()
143        self.__develop_begin()
144        return True
145
146    def _tag(self, tag, author, date):
147        self.__delta_name(tag)
148
149    def _addSubtree(self, subdir):
150        #
151        # Aegis new_file command is recursive, there is no need to
152        # walk the directory tree.
153        #
154        pass
155
156    def _addEntries(self, entries):
157        for e in entries:
158            if e.is_directory:
159                continue
160            self.__new_file(e.name)
161
162    def _addPathnames(self, names):
163        for name in names:
164            self.__new_file(name)
165
166    def _editPathnames(self, names):
167        for name in names:
168            self.__copy_file(name)
169
170    def _removeEntries(self, entries):
171        for e in entries:
172            if e.is_directory:
173                continue
174            self.__remove_file(e.name)
175
176    def _removePathnames(self, names):
177        for name in names:
178            self.__remove_file(name)
179
180    def _renameEntries(self, entries):
181        for e in entries:
182            if e.is_directory:
183                continue
184            self.__move_file(e.old_name, e.name)
185
186    def _renamePathname(self, oldname, newname):
187        self.__move_file(oldname, newname)
188
189    #
190    # The following methods wraps change's related aegis commands.
191    #
192    def __new_change(self, title = "none", description = "none"):
193        change_attr_file = \
194            self.__change_attribute_file(brief_description = title,
195                                         description = description)
196        change_number_file = ReopenableNamedTemporaryFile('aegis', 'tailor')
197        cmd = self.repository.command("-new_change",
198                                      "-project", self.repository.module,
199                                      "-file", change_attr_file.name,
200                                      "-output", change_number_file.name)
201        new_change = ExternalCommand(cwd="/tmp", command=cmd)
202        output = new_change.execute(stdout = PIPE, stderr = STDOUT)[0]
203        if new_change.exit_status > 0:
204            raise ChangesetApplicationFailure(
205                "%s returned status %d, saying: %s" % (str(new_change),
206                                                       new_change.exit_status,
207                                                       output.read()))
208        fd = open(change_number_file.name, "rb")
209        change_number = fd.read()
210        fd.close()
211        return change_number.strip()
212
213    def __develop_begin(self):
214        cmd = self.repository.command("-develop_begin",
215                                      "-project", self.repository.module,
216                                      "-change", self.change_number,
217                                      "-directory", self.repository.basedir)
218        develop_begin = ExternalCommand(cwd="/tmp", command=cmd)
219        output = develop_begin.execute(stdout = PIPE, stderr = STDOUT)[0]
220        if develop_begin.exit_status > 0:
221            raise ChangesetApplicationFailure(
222                "%s returned status %d, saying: %s" %
223                (str(develop_begin), develop_begin.exit_status, output.read()))
224        self.log.info(output.read())
225
226    def __develop_end(self):
227        self.__finish()
228
229    def __integrate_begin(self):
230        cmd = self.repository.command("-integrate_begin",
231                                      "-project", self.repository.module,
232                                      "-change", self.change_number)
233        integrate_begin = ExternalCommand(cwd="/tmp", command=cmd)
234        output = integrate_begin.execute(stdout = PIPE, stderr = STDOUT)[0]
235        if integrate_begin.exit_status > 0:
236            raise ChangesetApplicationFailure(
237                "%s returned status %d, saying: %s" %
238                (str(integrate_begin), integrate_begin.exit_status,
239                 output.read()))
240
241    def __integrate_pass(self):
242        self.__finish()
243
244    def __finish(self):
245        cmd = self.repository.command("-finish",
246                                      "-project", self.repository.module,
247                                      "-change", self.change_number)
248        finish = ExternalCommand(cwd="/tmp", command=cmd)
249        output = finish.execute(stdout = PIPE, stderr = STDOUT)[0]
250        if finish.exit_status > 0:
251            raise ChangesetApplicationFailure(
252                "%s returned status %d, saying: %s" %
253                (str(finish), finish.exit_status, output.read()))
254
255    def __change_attribute(self, file):
256        cmd = self.repository.command ("-change_attr",
257                                       "-project", self.repository.module,
258                                       "-change", self.change_number)
259        change_attr = ExternalCommand (cwd="/tmp", command=cmd)
260        output = change_attr.execute ("-file", file, stdout = PIPE, stderr = STDOUT)[0]
261        if change_attr.exit_status > 0:
262            raise ChangesetApplicationFailure(
263                "%s returned status %d, saying: %s" %
264                (str(change_attr), change_attr.exit_status, output.read()))
265
266    def __delta_name (self, delta):
267        cmd = self.repository.command ("-delta_name",
268                                       "-project", self.repository.module)
269        delta_name = ExternalCommand (cwd="/tmp", command=cmd)
270        output = delta_name.execute (delta, stdout = PIPE, stderr = STDOUT)[0]
271        if delta_name.exit_status > 0:
272            raise ChangesetApplicationFailure(
273                "%s returned status %d, saying: %s" %
274                (str(delta_name), delta_name.exit_status, output.read()))
275
276    #
277    # File's related methods.
278    #
279    def __new_file(self, file_names, usage = None):
280        #
281        # Tailor try to add also the aegis own log file and it's forbidden.
282        #
283        if file_names == "./aegis.log":
284            return
285        if usage == "config":
286            cmd = self.repository.command("-new_file", "-keep", "-config",
287                                          "-not-logging",
288                                          "-project", self.repository.module,
289                                          "-change", self.change_number)
290        else:
291            cmd = self.repository.command("-new_file", "-keep", "-not-logging",
292                                          "-project", self.repository.module,
293                                          "-change", self.change_number)
294        new_file = ExternalCommand(cwd=self.repository.basedir,
295                                   command=cmd)
296        output = new_file.execute(file_names, stdout = PIPE, stderr = STDOUT)[0]
297        if new_file.exit_status > 0:
298            raise ChangesetApplicationFailure(
299                "%s returned status %d, saying: %s" %
300                (str(new_file), new_file.exit_status, output.read()))
301
302
303    def __copy_file(self, file_names):
304        cmd = self.repository.command("-copy", "-keep",
305                                      "-not-logging",
306                                      "-project", self.repository.module,
307                                      "-change", self.change_number)
308        copy_file = ExternalCommand(cwd=self.repository.basedir,
309                                    command=cmd)
310        output = copy_file.execute(file_names, stdout = PIPE, stderr = STDOUT)[0]
311        if copy_file.exit_status > 0:
312            raise ChangesetApplicationFailure(
313                "%s returned status %d, saying: %s" %
314                (str(copy_file), copy_file.exit_status, output.read()))
315
316    def __move_file(self, old_name, new_name):
317        #
318        # The aegis command to rename files does not have the -keep
319        # option to preserve the content of the file, do it manually.
320        #
321        fp = open(os.path.join(self.repository.basedir, new_name), 'rb')
322        content = fp.read()
323        fp.close()
324        cmd = self.repository.command("-move",
325                                      "-not-logging",
326                                      "-project", self.repository.module,
327                                      "-change", self.change_number)
328        move_file = ExternalCommand(cwd=self.repository.basedir,
329                                    command=cmd)
330        output = move_file.execute(old_name, new_name, stdout = PIPE, stderr = STDOUT)[0]
331        if move_file.exit_status > 0:
332            raise ChangesetApplicationFailure(
333                "%s returned status %d, saying: %s" %
334                (str(move_file), move_file.exit_status, output.read()))
335
336        #
337        # Restore the previously saved content of the renamed file.
338        #
339        fp = open(os.path.join(self.repository.basedir, new_name), 'wb')
340        fp.write(content)
341        fp.close()
342
343    def __remove_file(self, file_name):
344        cmd = self.repository.command("-remove",
345                                      "-not-logging",
346                                      "-project", self.repository.module,
347                                      "-change", self.change_number)
348        remove_file = ExternalCommand(cwd=self.repository.basedir,
349                                      command=cmd)
350        output = remove_file.execute(file_name, stdout = PIPE, stderr = STDOUT)[0]
351        if remove_file.exit_status > 0:
352            raise ChangesetApplicationFailure(
353                "%s returned status %d, saying: %s" %
354                (str(remove_file), remove_file.exit_status, output.read()))
355
356    def __change_attribute_file(self, *args, **kwargs):
357        """
358        Create a temporary file to modify change's attributes.
359        """
360        if kwargs['brief_description']:
361            brief_description = \
362                self.repository.normalize(kwargs['brief_description'])
363        else:
364            brief_description = 'none'
365        if kwargs['description']:
366            description = \
367                self.repository.normalize(kwargs['description'])
368        else:
369            description = "none"
370        attribute_file = ReopenableNamedTemporaryFile('aegis', 'tailor')
371        fd = open(attribute_file.name, 'wb')
372        fd.write("""
373            brief_description = "%s";
374            description = "%s";
375            cause = external_improvement;
376            test_exempt = true;
377            test_baseline_exempt = true;
378            regression_test_exempt = true;
379            """
380            % (brief_description, description))
381        fd.close()
382        return attribute_file
383
384    def __config_file(self, dir, name):
385        """
386        Prepare the basic configuration to make aegis work:
387        * define a successfull build command (exit 0)
388        * define the history command
389        * define the merge commands
390        """
391        c = open(os.path.join(dir, name), "wb", 0644)
392        c.write("""
393build_command = "exit 0";
394link_integration_directory = true;
395
396history_get_command = "aesvt -check-out -edit ${quote $edit} "
397    "-history ${quote $history} -f ${quote $output}";
398history_put_command = "aesvt -check-in -history ${quote $history} "
399    "-f ${quote $input}";
400history_query_command = "aesvt -query -history ${quote $history}";
401history_content_limitation = binary_capable;
402
403diff_command = "set +e; $diff $orig $i > $out; test $$? -le 1";
404merge_command =
405"(diff3 -e $i $orig $mr | sed -e '/^w$$/d' -e '/^q$$/d'; echo '1,$$p') "
406"| ed - $i > $out";
407patch_diff_command =
408"set +e; $diff -C0 -L $index -L $index $orig $i > $out; test $$? -le 1";
409
410shell_safe_filenames = false;
411""")
412        c.close()
Note: See TracBrowser for help on using the repository browser.