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

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

[aegis] adapt the changeset even if the target repository is empty

Adapt the changeset even if the target repository is empty since some
thing has to be done:

  • remove directory
  • remove deletion of files not in the repository
  • adapt renames
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
92        from copy import deepcopy
93        adapted = deepcopy(changeset)
94
95        #
96        # adapt the entries:
97        # * delete directory entries
98        # * delete entries with action_kind REMOVE not in the repository (DEL => )
99        # * change to ADD a rename of a file non in the repository (REN => ADD)
100        # * remove from the changeset entries whith 2 operation (REN + UPD => REN);
101        # * change the ADD action_kind for files already in the repository (ADD => UPD);
102        # * change the UPD action_kind for files *non* in the repository (UPD => ADD);
103        #
104        for e in adapted.entries[:]:
105            if e.is_directory:
106                adapted.entries.remove(e)
107                continue
108            if e.action_kind == e.DELETED and not project_files.count(e.name):
109                self.log.info("remove delete entry %s", e.name)
110                adapted.entries.remove(e)
111
112        renamed_file = []
113        for e in adapted.entries:
114            if e.action_kind == e.RENAMED:
115                renamed_file.append(e.name)
116
117        for e in adapted.entries[:]:
118            if renamed_file.count(e.name) and e.action_kind != e.RENAMED:
119                adapted.entries.remove(e)
120            if e.action_kind == e.RENAMED and not project_files.count(e.old_name):
121                e.action_kind = e.ADDED
122                e.old_name = None
123            if e.action_kind == e.ADDED and project_files.count(e.name):
124                e.action_kind = ChangesetEntry.UPDATED
125            elif e.action_kind == e.UPDATED and not project_files.count(e.name):
126                e.action_kind = e.ADDED
127
128        #
129        # Returns even if the changeset does not contain entries to
130        # give the opportunity to still register tags.
131        #
132        return SynchronizableTargetWorkingDir._adaptChangeset(self, adapted)
133
134    def _initializeWorkingDir(self):
135        #
136        # This method is called only by importFirstRevision
137        #
138        self.__new_file('.')
139
140    def _prepareWorkingDirectory(self, source_repo):
141        #
142        # Receive the first changeset from the source repository.
143        #
144        self.change_number = self.__new_change()
145        self.__develop_begin()
146        return True
147
148    def _tag(self, tag, author, date):
149        self.__delta_name(tag)
150
151    def _addSubtree(self, subdir):
152        #
153        # Aegis new_file command is recursive, there is no need to
154        # walk the directory tree.
155        #
156        pass
157
158    def _addEntries(self, entries):
159        for e in entries:
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            self.__remove_file(e.name)
173
174    def _removePathnames(self, names):
175        for name in names:
176            self.__remove_file(name)
177
178    def _renameEntries(self, entries):
179        for e in entries:
180            self.__move_file(e.old_name, e.name)
181
182    def _renamePathname(self, oldname, newname):
183        self.__move_file(oldname, newname)
184
185    #
186    # The following methods wraps change's related aegis commands.
187    #
188    def __new_change(self, title = "none", description = "none"):
189        change_attr_file = \
190            self.__change_attribute_file(brief_description = title,
191                                         description = description)
192        change_number_file = ReopenableNamedTemporaryFile('aegis', 'tailor')
193        cmd = self.repository.command("-new_change",
194                                      "-project", self.repository.module,
195                                      "-file", change_attr_file.name,
196                                      "-output", change_number_file.name)
197        new_change = ExternalCommand(cwd="/tmp", command=cmd)
198        output = new_change.execute(stdout = PIPE, stderr = STDOUT)[0]
199        if new_change.exit_status > 0:
200            raise ChangesetApplicationFailure(
201                "%s returned status %d, saying: %s" % (str(new_change),
202                                                       new_change.exit_status,
203                                                       output.read()))
204        fd = open(change_number_file.name, "rb")
205        change_number = fd.read()
206        fd.close()
207        return change_number.strip()
208
209    def __develop_begin(self):
210        cmd = self.repository.command("-develop_begin",
211                                      "-project", self.repository.module,
212                                      "-change", self.change_number,
213                                      "-directory", self.repository.basedir)
214        develop_begin = ExternalCommand(cwd="/tmp", command=cmd)
215        output = develop_begin.execute(stdout = PIPE, stderr = STDOUT)[0]
216        if develop_begin.exit_status > 0:
217            raise ChangesetApplicationFailure(
218                "%s returned status %d, saying: %s" %
219                (str(develop_begin), develop_begin.exit_status, output.read()))
220        self.log.info(output.read())
221
222    def __develop_end(self):
223        self.__finish()
224
225    def __integrate_begin(self):
226        cmd = self.repository.command("-integrate_begin",
227                                      "-project", self.repository.module,
228                                      "-change", self.change_number)
229        integrate_begin = ExternalCommand(cwd="/tmp", command=cmd)
230        output = integrate_begin.execute(stdout = PIPE, stderr = STDOUT)[0]
231        if integrate_begin.exit_status > 0:
232            raise ChangesetApplicationFailure(
233                "%s returned status %d, saying: %s" %
234                (str(integrate_begin), integrate_begin.exit_status,
235                 output.read()))
236
237    def __integrate_pass(self):
238        self.__finish()
239
240    def __finish(self):
241        cmd = self.repository.command("-finish",
242                                      "-project", self.repository.module,
243                                      "-change", self.change_number)
244        finish = ExternalCommand(cwd="/tmp", command=cmd)
245        output = finish.execute(stdout = PIPE, stderr = STDOUT)[0]
246        if finish.exit_status > 0:
247            raise ChangesetApplicationFailure(
248                "%s returned status %d, saying: %s" %
249                (str(finish), finish.exit_status, output.read()))
250
251    def __change_attribute(self, file):
252        cmd = self.repository.command ("-change_attr",
253                                       "-project", self.repository.module,
254                                       "-change", self.change_number)
255        change_attr = ExternalCommand (cwd="/tmp", command=cmd)
256        output = change_attr.execute ("-file", file, stdout = PIPE, stderr = STDOUT)[0]
257        if change_attr.exit_status > 0:
258            raise ChangesetApplicationFailure(
259                "%s returned status %d, saying: %s" %
260                (str(change_attr), change_attr.exit_status, output.read()))
261
262    def __delta_name (self, delta):
263        cmd = self.repository.command ("-delta_name",
264                                       "-project", self.repository.module)
265        delta_name = ExternalCommand (cwd="/tmp", command=cmd)
266        output = delta_name.execute (delta, stdout = PIPE, stderr = STDOUT)[0]
267        if delta_name.exit_status > 0:
268            raise ChangesetApplicationFailure(
269                "%s returned status %d, saying: %s" %
270                (str(delta_name), delta_name.exit_status, output.read()))
271
272    #
273    # File's related methods.
274    #
275    def __new_file(self, file_names, usage = None):
276        #
277        # Tailor try to add also the aegis own log file and it's forbidden.
278        #
279        if file_names == "./aegis.log":
280            return
281        if usage == "config":
282            cmd = self.repository.command("-new_file", "-keep", "-config",
283                                          "-not-logging",
284                                          "-project", self.repository.module,
285                                          "-change", self.change_number)
286        else:
287            cmd = self.repository.command("-new_file", "-keep", "-not-logging",
288                                          "-project", self.repository.module,
289                                          "-change", self.change_number)
290        new_file = ExternalCommand(cwd=self.repository.basedir,
291                                   command=cmd)
292        output = new_file.execute(file_names, stdout = PIPE, stderr = STDOUT)[0]
293        if new_file.exit_status > 0:
294            raise ChangesetApplicationFailure(
295                "%s returned status %d, saying: %s" %
296                (str(new_file), new_file.exit_status, output.read()))
297
298
299    def __copy_file(self, file_names):
300        cmd = self.repository.command("-copy", "-keep",
301                                      "-not-logging",
302                                      "-project", self.repository.module,
303                                      "-change", self.change_number)
304        copy_file = ExternalCommand(cwd=self.repository.basedir,
305                                    command=cmd)
306        output = copy_file.execute(file_names, stdout = PIPE, stderr = STDOUT)[0]
307        if copy_file.exit_status > 0:
308            raise ChangesetApplicationFailure(
309                "%s returned status %d, saying: %s" %
310                (str(copy_file), copy_file.exit_status, output.read()))
311
312    def __move_file(self, old_name, new_name):
313        #
314        # The aegis command to rename files does not have the -keep
315        # option to preserve the content of the file, do it manually.
316        #
317        fp = open(os.path.join(self.repository.basedir, new_name), 'rb')
318        content = fp.read()
319        fp.close()
320        cmd = self.repository.command("-move",
321                                      "-not-logging",
322                                      "-project", self.repository.module,
323                                      "-change", self.change_number)
324        move_file = ExternalCommand(cwd=self.repository.basedir,
325                                    command=cmd)
326        output = move_file.execute(old_name, new_name, stdout = PIPE, stderr = STDOUT)[0]
327        if move_file.exit_status > 0:
328            raise ChangesetApplicationFailure(
329                "%s returned status %d, saying: %s" %
330                (str(move_file), move_file.exit_status, output.read()))
331
332        #
333        # Restore the previously saved content of the renamed file.
334        #
335        fp = open(os.path.join(self.repository.basedir, new_name), 'wb')
336        fp.write(content)
337        fp.close()
338
339    def __remove_file(self, file_name):
340        cmd = self.repository.command("-remove",
341                                      "-not-logging",
342                                      "-project", self.repository.module,
343                                      "-change", self.change_number)
344        remove_file = ExternalCommand(cwd=self.repository.basedir,
345                                      command=cmd)
346        output = remove_file.execute(file_name, stdout = PIPE, stderr = STDOUT)[0]
347        if remove_file.exit_status > 0:
348            raise ChangesetApplicationFailure(
349                "%s returned status %d, saying: %s" %
350                (str(remove_file), remove_file.exit_status, output.read()))
351
352    def __change_attribute_file(self, *args, **kwargs):
353        """
354        Create a temporary file to modify change's attributes.
355        """
356        if kwargs['brief_description']:
357            brief_description = \
358                self.repository.normalize(kwargs['brief_description'])
359        else:
360            brief_description = 'none'
361        if kwargs['description']:
362            description = \
363                self.repository.normalize(kwargs['description'])
364        else:
365            description = "none"
366        attribute_file = ReopenableNamedTemporaryFile('aegis', 'tailor')
367        fd = open(attribute_file.name, 'wb')
368        fd.write("""
369            brief_description = "%s";
370            description = "%s";
371            cause = external_improvement;
372            test_exempt = true;
373            test_baseline_exempt = true;
374            regression_test_exempt = true;
375            """
376            % (brief_description, description))
377        fd.close()
378        return attribute_file
379
380    def __config_file(self, dir, name):
381        """
382        Prepare the basic configuration to make aegis work:
383        * define a successfull build command (exit 0)
384        * define the history command
385        * define the merge commands
386        """
387        c = open(os.path.join(dir, name), "wb", 0644)
388        c.write("""
389build_command = "exit 0";
390link_integration_directory = true;
391
392history_get_command = "aesvt -check-out -edit ${quote $edit} "
393    "-history ${quote $history} -f ${quote $output}";
394history_put_command = "aesvt -check-in -history ${quote $history} "
395    "-f ${quote $input}";
396history_query_command = "aesvt -query -history ${quote $history}";
397history_content_limitation = binary_capable;
398
399diff_command = "set +e; $diff $orig $i > $out; test $$? -le 1";
400merge_command =
401"(diff3 -e $i $orig $mr | sed -e '/^w$$/d' -e '/^q$$/d'; echo '1,$$p') "
402"| ed - $i > $out";
403patch_diff_command =
404"set +e; $diff -C0 -L $index -L $index $orig $i > $out; test $$? -le 1";
405
406shell_safe_filenames = false;
407""")
408        c.close()
Note: See TracBrowser for help on using the repository browser.