/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.64.128 by Ian Clatworthy
fix encoding issue in bzr_exporter (Teemu Likonen)
1
# -*- coding: utf-8 -*-
2
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
3
# Copyright (C) 2008 Canonical Ltd
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
#
0.79.10 by Ian Clatworthy
documentation clean-ups
19
# Original Copyright (c) 2008 Adeodato Simó
20
# Original License: MIT (See exporters/bzr-fast-export.LICENSE)
21
#
0.64.57 by Ian Clatworthy
integrate dato's bzr-fast-export
22
# vim: fileencoding=utf-8
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
23
24
"""Core engine for the fast-export command."""
0.64.57 by Ian Clatworthy
integrate dato's bzr-fast-export
25
0.79.7 by Ian Clatworthy
trivial bzr_exporter clean-ups
26
# TODO: if a new_git_branch below gets merged repeatedly, the tip of the branch
0.64.57 by Ian Clatworthy
integrate dato's bzr-fast-export
27
# is not updated (because the parent of commit is already merged, so we don't
28
# set new_git_branch to the previously used name)
29
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
30
from email.Utils import parseaddr
0.64.173 by Ian Clatworthy
add -r option to fast-export
31
import sys, time
0.64.57 by Ian Clatworthy
integrate dato's bzr-fast-export
32
33
import bzrlib.branch
34
import bzrlib.revision
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
35
from bzrlib import (
36
    builtins,
37
    errors as bazErrors,
0.64.237 by Ian Clatworthy
implicitly rename children on export when directory renamed
38
    osutils,
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
39
    progress,
40
    trace,
41
    )
0.79.4 by Ian Clatworthy
use note and warning APIs
42
0.123.8 by Jelmer Vernooij
Use modes for FileModifyCommand.
43
from bzrlib.plugins.fastimport import helpers, marks_file
0.79.2 by Ian Clatworthy
extend & use marks_file API
44
0.123.8 by Jelmer Vernooij
Use modes for FileModifyCommand.
45
from fastimport import commands
0.64.282 by Jelmer Vernooij
Fix output stream to stdout for bzr fast-export.
46
from fastimport.helpers import binary_stream
47
48
49
def _get_output_stream(destination):
50
    if destination is None or destination == '-':
51
        return binary_stream(sys.stdout)
52
    elif destination.endswith('gz'):
53
        import gzip
54
        return gzip.open(destination, 'wb')
55
    else:
56
        return open(destination, 'wb')
57
0.64.57 by Ian Clatworthy
integrate dato's bzr-fast-export
58
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
59
class BzrFastExporter(object):
0.68.1 by Pieter de Bie
Classify bzr-fast-export
60
0.64.222 by Ian Clatworthy
Support an explicit output destination for bzr fast-export
61
    def __init__(self, source, destination, git_branch=None, checkpoint=-1,
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
62
        import_marks_file=None, export_marks_file=None, revision=None,
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
63
        verbose=False, plain_format=False):
64
        """Export branch data in fast import format.
65
66
        :param plain_format: if True, 'classic' fast-import format is
67
          used without any extended features; if False, the generated
68
          data is richer and includes information like multiple
69
          authors, revision properties, etc.
70
        """
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
71
        self.source = source
0.64.282 by Jelmer Vernooij
Fix output stream to stdout for bzr fast-export.
72
        self.outf = _get_output_stream(destination)
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
73
        self.git_branch = git_branch
74
        self.checkpoint = checkpoint
75
        self.import_marks_file = import_marks_file
76
        self.export_marks_file = export_marks_file
0.64.173 by Ian Clatworthy
add -r option to fast-export
77
        self.revision = revision
78
        self.excluded_revisions = set()
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
79
        self.plain_format = plain_format
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
80
        self._multi_author_api_available = hasattr(bzrlib.revision.Revision,
81
            'get_apparent_authors')
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
82
        self.properties_to_exclude = ['authors', 'author']
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
83
84
        # Progress reporting stuff
85
        self.verbose = verbose
86
        if verbose:
87
            self.progress_every = 100
88
        else:
89
            self.progress_every = 1000
90
        self._start_time = time.time()
0.64.230 by Ian Clatworthy
Fix ghost handling and improve progress tracking in fast-export
91
        self._commit_total = 0
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
92
93
        # Load the marks and initialise things accordingly
0.68.1 by Pieter de Bie
Classify bzr-fast-export
94
        self.revid_to_mark = {}
95
        self.branch_names = {}
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
96
        if self.import_marks_file:
0.79.2 by Ian Clatworthy
extend & use marks_file API
97
            marks_info = marks_file.import_marks(self.import_marks_file)
98
            if marks_info is not None:
0.64.134 by Ian Clatworthy
fix marks importing in fast-export
99
                self.revid_to_mark = dict((r, m) for m, r in
100
                    marks_info[0].items())
0.79.2 by Ian Clatworthy
extend & use marks_file API
101
                self.branch_names = marks_info[1]
0.64.173 by Ian Clatworthy
add -r option to fast-export
102
 
103
    def interesting_history(self):
104
        if self.revision:
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
105
            rev1, rev2 = builtins._get_revision_range(self.revision,
106
                self.branch, "fast-export")
0.64.173 by Ian Clatworthy
add -r option to fast-export
107
            start_rev_id = rev1.rev_id
108
            end_rev_id = rev2.rev_id
109
        else:
110
            start_rev_id = None
111
            end_rev_id = None
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
112
        self.note("Calculating the revisions to include ...")
0.100.1 by Ian Clatworthy
Stop fast-export from exceeding the maximum recursion depth
113
        view_revisions = reversed([rev_id for rev_id, _, _, _ in
114
            self.branch.iter_merge_sorted_revisions(end_rev_id, start_rev_id)])
0.64.173 by Ian Clatworthy
add -r option to fast-export
115
        # If a starting point was given, we need to later check that we don't
116
        # start emitting revisions from before that point. Collect the
117
        # revisions to exclude now ...
118
        if start_rev_id is not None:
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
119
            self.note("Calculating the revisions to exclude ...")
0.100.1 by Ian Clatworthy
Stop fast-export from exceeding the maximum recursion depth
120
            self.excluded_revisions = set([rev_id for rev_id, _, _, _ in
121
                self.branch.iter_merge_sorted_revisions(start_rev_id)])
0.64.230 by Ian Clatworthy
Fix ghost handling and improve progress tracking in fast-export
122
        return list(view_revisions)
0.64.173 by Ian Clatworthy
add -r option to fast-export
123
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
124
    def run(self):
125
        # Open the source
126
        self.branch = bzrlib.branch.Branch.open_containing(self.source)[0]
127
128
        # Export the data
0.68.1 by Pieter de Bie
Classify bzr-fast-export
129
        self.branch.repository.lock_read()
130
        try:
0.100.1 by Ian Clatworthy
Stop fast-export from exceeding the maximum recursion depth
131
            interesting = self.interesting_history()
0.102.15 by Ian Clatworthy
add revision count to 'Starting export ...' message
132
            self._commit_total = len(interesting)
133
            self.note("Starting export of %d revisions ..." %
134
                self._commit_total)
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
135
            if not self.plain_format:
136
                self.emit_features()
0.100.1 by Ian Clatworthy
Stop fast-export from exceeding the maximum recursion depth
137
            for revid in interesting:
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
138
                self.emit_commit(revid, self.git_branch)
139
            if self.branch.supports_tags():
140
                self.emit_tags()
0.68.1 by Pieter de Bie
Classify bzr-fast-export
141
        finally:
142
            self.branch.repository.unlock()
143
0.79.1 by Ian Clatworthy
turn bzr-fast-export into a fast-export command
144
        # Save the marks if requested
0.79.2 by Ian Clatworthy
extend & use marks_file API
145
        self._save_marks()
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
146
        self.dump_stats()
147
148
    def note(self, msg, *args):
149
        """Output a note but timestamp it."""
150
        msg = "%s %s" % (self._time_of_day(), msg)
151
        trace.note(msg, *args)
152
153
    def warning(self, msg, *args):
154
        """Output a warning but timestamp it."""
155
        msg = "%s WARNING: %s" % (self._time_of_day(), msg)
156
        trace.warning(msg, *args)
157
158
    def _time_of_day(self):
159
        """Time of day as a string."""
160
        # Note: this is a separate method so tests can patch in a fixed value
161
        return time.strftime("%H:%M:%S")
162
163
    def report_progress(self, commit_count, details=''):
164
        if commit_count and commit_count % self.progress_every == 0:
0.64.230 by Ian Clatworthy
Fix ghost handling and improve progress tracking in fast-export
165
            if self._commit_total:
166
                counts = "%d/%d" % (commit_count, self._commit_total)
167
            else:
168
                counts = "%d" % (commit_count,)
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
169
            minutes = (time.time() - self._start_time) / 60
170
            rate = commit_count * 1.0 / minutes
171
            if rate > 10:
172
                rate_str = "at %.0f/minute " % rate
173
            else:
174
                rate_str = "at %.1f/minute " % rate
175
            self.note("%s commits exported %s%s" % (counts, rate_str, details))
176
177
    def dump_stats(self):
178
        time_required = progress.str_tdelta(time.time() - self._start_time)
179
        rc = len(self.revid_to_mark)
180
        self.note("Exported %d %s in %s",
181
            rc, helpers.single_plural(rc, "revision", "revisions"),
182
            time_required)
0.79.2 by Ian Clatworthy
extend & use marks_file API
183
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
184
    def print_cmd(self, cmd):
185
        self.outf.write("%r\n" % cmd)
186
0.79.2 by Ian Clatworthy
extend & use marks_file API
187
    def _save_marks(self):
188
        if self.export_marks_file:
0.64.134 by Ian Clatworthy
fix marks importing in fast-export
189
            revision_ids = dict((m, r) for r, m in self.revid_to_mark.items())
0.79.2 by Ian Clatworthy
extend & use marks_file API
190
            marks_file.export_marks(self.export_marks_file, revision_ids,
191
                self.branch_names)
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
192
 
0.68.10 by Pieter de Bie
bzr-fast-export: Don't rename empty directories
193
    def is_empty_dir(self, tree, path):
194
        path_id = tree.path2id(path)
0.100.1 by Ian Clatworthy
Stop fast-export from exceeding the maximum recursion depth
195
        if path_id is None:
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
196
            self.warning("Skipping empty_dir detection - no file_id for %s" %
197
                (path,))
0.68.10 by Pieter de Bie
bzr-fast-export: Don't rename empty directories
198
            return False
199
200
        # Continue if path is not a directory
201
        if tree.kind(path_id) != 'directory':
202
            return False
203
204
        # Use treewalk to find the contents of our directory
205
        contents = list(tree.walkdirs(prefix=path))[0]
206
        if len(contents[1]) == 0:
207
            return True
208
        else:
209
            return False
210
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
211
    def emit_features(self):
0.102.5 by Ian Clatworthy
Define feature names in one place
212
        for feature in sorted(commands.FEATURE_NAMES):
213
            self.print_cmd(commands.FeatureCommand(feature))
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
214
0.68.1 by Pieter de Bie
Classify bzr-fast-export
215
    def emit_commit(self, revid, git_branch):
0.64.173 by Ian Clatworthy
add -r option to fast-export
216
        if revid in self.revid_to_mark or revid in self.excluded_revisions:
0.68.4 by Pieter de Bie
bzr-fast-export.py: Add support for ghost commits
217
            return
218
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
219
        # Get the Revision object
0.68.4 by Pieter de Bie
bzr-fast-export.py: Add support for ghost commits
220
        try:
221
            revobj = self.branch.repository.get_revision(revid)
222
        except bazErrors.NoSuchRevision:
223
            # This is a ghost revision. Mark it as not found and next!
224
            self.revid_to_mark[revid] = -1
225
            return
0.64.82 by Ian Clatworthy
Merge Pieter de Bie's export-fixes branch
226
 
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
227
        # Get the primary parent
0.100.1 by Ian Clatworthy
Stop fast-export from exceeding the maximum recursion depth
228
        # TODO: Consider the excluded revisions when deciding the parents.
229
        # Currently, a commit with parents that are excluded ought to be
230
        # triggering the git_branch calculation below (and it is not).
231
        # IGC 20090824
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
232
        ncommits = len(self.revid_to_mark)
0.100.1 by Ian Clatworthy
Stop fast-export from exceeding the maximum recursion depth
233
        nparents = len(revobj.parent_ids)
0.68.4 by Pieter de Bie
bzr-fast-export.py: Add support for ghost commits
234
        if nparents == 0:
0.79.9 by Ian Clatworthy
fix branch of first commit to not be refs/heads/tmp
235
            if ncommits:
236
                # This is a parentless commit but it's not the first one
237
                # output. We need to create a new temporary branch for it
238
                # otherwise git-fast-import will assume the previous commit
239
                # was this one's parent
240
                git_branch = self._next_tmp_branch_name()
0.68.4 by Pieter de Bie
bzr-fast-export.py: Add support for ghost commits
241
            parent = bzrlib.revision.NULL_REVISION
242
        else:
243
            parent = revobj.parent_ids[0]
244
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
245
        # Print the commit
246
        git_ref = 'refs/heads/%s' % (git_branch,)
0.98.1 by Gonéri Le Bouder
add the missing ":" since revid_to_mark are "committish"
247
        mark = ncommits + 1
0.64.221 by Ian Clatworthy
backout git-bzr fix as it was breaking fast-export
248
        self.revid_to_mark[revid] = mark
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
249
        file_cmds = self._get_filecommands(parent, revid)
250
        self.print_cmd(self._get_commit_command(git_ref, mark, revobj,
251
            file_cmds))
252
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
253
        # Report progress and checkpoint if it's time for that
254
        self.report_progress(ncommits)
255
        if (self.checkpoint > 0 and ncommits
256
            and ncommits % self.checkpoint == 0):
257
            self.note("Exported %i commits - adding checkpoint to output"
258
                % ncommits)
259
            self._save_marks()
260
            self.print_cmd(commands.CheckpointCommand())
261
0.102.16 by Ian Clatworthy
tweak author formatting to use same smart rule as used for committer
262
    def _get_name_email(self, user):
263
        if user.find('<') == -1:
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
264
            # If the email isn't inside <>, we need to use it as the name
265
            # in order for things to round-trip correctly.
266
            # (note: parseaddr('a@b.com') => name:'', email: 'a@b.com')
0.102.16 by Ian Clatworthy
tweak author formatting to use same smart rule as used for committer
267
            name = user
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
268
            email = ''
269
        else:
0.102.16 by Ian Clatworthy
tweak author formatting to use same smart rule as used for committer
270
            name, email = parseaddr(user)
271
        return name, email
272
273
    def _get_commit_command(self, git_ref, mark, revobj, file_cmds):
274
        # Get the committer and author info
275
        committer = revobj.committer
276
        name, email = self._get_name_email(committer)
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
277
        committer_info = (name, email, revobj.timestamp, revobj.timezone)
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
278
        if self._multi_author_api_available:
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
279
            more_authors = revobj.get_apparent_authors()
280
            author = more_authors.pop(0)
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
281
        else:
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
282
            more_authors = []
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
283
            author = revobj.get_apparent_author()
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
284
        if more_authors:
0.102.16 by Ian Clatworthy
tweak author formatting to use same smart rule as used for committer
285
            name, email = self._get_name_email(author)
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
286
            author_info = (name, email, revobj.timestamp, revobj.timezone)
287
            more_author_info = []
288
            for a in more_authors:
0.102.16 by Ian Clatworthy
tweak author formatting to use same smart rule as used for committer
289
                name, email = self._get_name_email(a)
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
290
                more_author_info.append(
291
                    (name, email, revobj.timestamp, revobj.timezone))
292
        elif author != committer:
0.102.16 by Ian Clatworthy
tweak author formatting to use same smart rule as used for committer
293
            name, email = self._get_name_email(author)
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
294
            author_info = (name, email, revobj.timestamp, revobj.timezone)
295
            more_author_info = None
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
296
        else:
297
            author_info = None
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
298
            more_author_info = None
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
299
300
        # Get the parents in terms of marks
301
        non_ghost_parents = []
0.68.4 by Pieter de Bie
bzr-fast-export.py: Add support for ghost commits
302
        for p in revobj.parent_ids:
0.64.173 by Ian Clatworthy
add -r option to fast-export
303
            if p in self.excluded_revisions:
304
                continue
0.64.230 by Ian Clatworthy
Fix ghost handling and improve progress tracking in fast-export
305
            try:
306
                parent_mark = self.revid_to_mark[p]
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
307
                non_ghost_parents.append(":%s" % parent_mark)
0.64.230 by Ian Clatworthy
Fix ghost handling and improve progress tracking in fast-export
308
            except KeyError:
309
                # ghost - ignore
310
                continue
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
311
        if non_ghost_parents:
312
            from_ = non_ghost_parents[0]
313
            merges = non_ghost_parents[1:]
314
        else:
315
            from_ = None
316
            merges = None
317
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
318
        # Filter the revision properties. Some metadata (like the
319
        # author information) is already exposed in other ways so
320
        # don't repeat it here.
321
        if self.plain_format:
322
            properties = None
323
        else:
324
            properties = revobj.properties
325
            for prop in self.properties_to_exclude:
326
                try:
327
                    del properties[prop]
328
                except KeyError:
329
                    pass
330
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
331
        # Build and return the result
332
        return commands.CommitCommand(git_ref, mark, author_info,
0.102.3 by Ian Clatworthy
First cut at exporting additional metadata via 'features'
333
            committer_info, revobj.message, from_, merges, iter(file_cmds),
334
            more_authors=more_author_info, properties=properties)
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
335
336
    def _get_revision_trees(self, parent, revision_id):
0.68.6 by Pieter de Bie
bzr-fast-export.py: Skip over broken commits.
337
        try:
338
            tree_old = self.branch.repository.revision_tree(parent)
339
        except bazErrors.UnexpectedInventoryFormat:
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
340
            self.warning("Parent is malformed - diffing against previous parent")
0.68.6 by Pieter de Bie
bzr-fast-export.py: Skip over broken commits.
341
            # We can't find the old parent. Let's diff against his parent
342
            pp = self.branch.repository.get_revision(parent)
343
            tree_old = self.branch.repository.revision_tree(pp.parent_ids[0])
344
        tree_new = None
345
        try:
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
346
            tree_new = self.branch.repository.revision_tree(revision_id)
0.68.6 by Pieter de Bie
bzr-fast-export.py: Skip over broken commits.
347
        except bazErrors.UnexpectedInventoryFormat:
348
            # We can't really do anything anymore
0.87.1 by David Reitter
fix bug #348038 (call to warning() with two arguments) and handle malformed revisions gracefully by not generating any output
349
            self.warning("Revision %s is malformed - skipping" % revision_id)
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
350
        return tree_old, tree_new
0.68.1 by Pieter de Bie
Classify bzr-fast-export
351
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
352
    def _get_filecommands(self, parent, revision_id):
353
        """Get the list of FileCommands for the changes between two revisions."""
354
        tree_old, tree_new = self._get_revision_trees(parent, revision_id)
0.64.166 by Ian Clatworthy
graceful handling of faulty revisions (David Reitter)
355
        if not(tree_old and tree_new):
356
            # Something is wrong with this revision - ignore the filecommands
0.87.1 by David Reitter
fix bug #348038 (call to warning() with two arguments) and handle malformed revisions gracefully by not generating any output
357
            return []
0.68.1 by Pieter de Bie
Classify bzr-fast-export
358
0.64.166 by Ian Clatworthy
graceful handling of faulty revisions (David Reitter)
359
        changes = tree_new.changes_from(tree_old)
360
361
        # Make "modified" have 3-tuples, as added does
362
        my_modified = [ x[0:3] for x in changes.modified ]
363
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
364
        # The potential interaction between renames and deletes is messy.
365
        # Handle it here ...
366
        file_cmds, rd_modifies, renamed = self._process_renames_and_deletes(
367
            changes.renamed, changes.removed, revision_id, tree_old)
0.64.166 by Ian Clatworthy
graceful handling of faulty revisions (David Reitter)
368
369
        # Map kind changes to a delete followed by an add
370
        for path, id_, kind1, kind2 in changes.kind_changed:
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
371
            path = self._adjust_path_for_renames(path, renamed, revision_id)
0.64.174 by Ian Clatworthy
fix rename adjustment & kind change logic in fast-export
372
            # IGC: I don't understand why a delete is needed here.
373
            # In fact, it seems harmful? If you uncomment this line,
374
            # please file a bug explaining why you needed to.
375
            #file_cmds.append(commands.FileDeleteCommand(path))
0.64.166 by Ian Clatworthy
graceful handling of faulty revisions (David Reitter)
376
            my_modified.append((path, id_, kind2))
377
378
        # Record modifications
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
379
        for path, id_, kind in changes.added + my_modified + rd_modifies:
0.64.166 by Ian Clatworthy
graceful handling of faulty revisions (David Reitter)
380
            if kind == 'file':
381
                text = tree_new.get_file_text(id_)
0.123.8 by Jelmer Vernooij
Use modes for FileModifyCommand.
382
                file_cmds.append(commands.FileModifyCommand(path,
383
                    helpers.kind_to_mode('file', tree_new.is_executable(id_)),
384
                    None, text))
0.64.166 by Ian Clatworthy
graceful handling of faulty revisions (David Reitter)
385
            elif kind == 'symlink':
0.123.8 by Jelmer Vernooij
Use modes for FileModifyCommand.
386
                file_cmds.append(commands.FileModifyCommand(path,
387
                    helpers.kind_to_mode('symlink', False),
388
                    None, tree_new.get_symlink_target(id_)))
0.102.14 by Ian Clatworthy
export and import empty directories
389
            elif kind == 'directory':
0.105.1 by John Whitley
Don't emit directory info when plain format is specified.
390
                if not self.plain_format:
0.123.8 by Jelmer Vernooij
Use modes for FileModifyCommand.
391
                    file_cmds.append(commands.FileModifyCommand(path,
392
                        helpers.kind_to_mode('directory', False),
393
                        None, None))
0.64.166 by Ian Clatworthy
graceful handling of faulty revisions (David Reitter)
394
            else:
0.102.14 by Ian Clatworthy
export and import empty directories
395
                self.warning("cannot export '%s' of kind %s yet - ignoring" %
396
                    (path, kind))
0.64.166 by Ian Clatworthy
graceful handling of faulty revisions (David Reitter)
397
        return file_cmds
398
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
399
    def _process_renames_and_deletes(self, renames, deletes,
400
        revision_id, tree_old):
401
        file_cmds = []
402
        modifies = []
403
        renamed = []
404
405
        # See https://bugs.edge.launchpad.net/bzr-fastimport/+bug/268933.
406
        # In a nutshell, there are several nasty cases:
407
        #
408
        # 1) bzr rm a; bzr mv b a; bzr commit
409
        # 2) bzr mv x/y z; bzr rm x; commmit
410
        #
411
        # The first must come out with the delete first like this:
412
        #
413
        # D a
414
        # R b a
415
        #
416
        # The second case must come out with the rename first like this:
417
        #
418
        # R x/y z
419
        # D x
420
        #
421
        # So outputting all deletes first or all renames first won't work.
422
        # Instead, we need to make multiple passes over the various lists to
423
        # get the ordering right.
424
0.64.237 by Ian Clatworthy
implicitly rename children on export when directory renamed
425
        must_be_renamed = {}
426
        old_to_new = {}
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
427
        deleted_paths = set([p for p, _, _ in deletes])
428
        for (oldpath, newpath, id_, kind,
429
                text_modified, meta_modified) in renames:
0.106.2 by Harry Hirsch
Don't emit directory info for renames operations when using plain format
430
            emit = kind != 'directory' or not self.plain_format
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
431
            if newpath in deleted_paths:
0.106.2 by Harry Hirsch
Don't emit directory info for renames operations when using plain format
432
                if emit:
433
                    file_cmds.append(commands.FileDeleteCommand(newpath))
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
434
                deleted_paths.remove(newpath)
435
            if (self.is_empty_dir(tree_old, oldpath)):
436
                self.note("Skipping empty dir %s in rev %s" % (oldpath,
437
                    revision_id))
438
                continue
439
            #oldpath = self._adjust_path_for_renames(oldpath, renamed,
440
            #    revision_id)
441
            renamed.append([oldpath, newpath])
0.64.237 by Ian Clatworthy
implicitly rename children on export when directory renamed
442
            old_to_new[oldpath] = newpath
0.106.2 by Harry Hirsch
Don't emit directory info for renames operations when using plain format
443
            if emit:
444
                file_cmds.append(commands.FileRenameCommand(oldpath, newpath))
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
445
            if text_modified or meta_modified:
446
                modifies.append((newpath, id_, kind))
447
0.64.237 by Ian Clatworthy
implicitly rename children on export when directory renamed
448
            # Renaming a directory implies all children must be renamed.
449
            # Note: changes_from() doesn't handle this
450
            if kind == 'directory':
451
                for p, e in tree_old.inventory.iter_entries_by_dir(from_dir=id_):
0.106.2 by Harry Hirsch
Don't emit directory info for renames operations when using plain format
452
                    if e.kind == 'directory' and self.plain_format:
453
                        continue
0.64.237 by Ian Clatworthy
implicitly rename children on export when directory renamed
454
                    old_child_path = osutils.pathjoin(oldpath, p)
455
                    new_child_path = osutils.pathjoin(newpath, p)
456
                    must_be_renamed[old_child_path] = new_child_path
457
458
        # Add children not already renamed
459
        if must_be_renamed:
460
            renamed_already = set(old_to_new.keys())
461
            still_to_be_renamed = set(must_be_renamed.keys()) - renamed_already
462
            for old_child_path in sorted(still_to_be_renamed):
463
                new_child_path = must_be_renamed[old_child_path]
464
                if self.verbose:
465
                    self.note("implicitly renaming %s => %s" % (old_child_path,
466
                        new_child_path))
467
                file_cmds.append(commands.FileRenameCommand(old_child_path,
468
                    new_child_path))
469
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
470
        # Record remaining deletes
471
        for path, id_, kind in deletes:
472
            if path not in deleted_paths:
473
                continue
0.106.2 by Harry Hirsch
Don't emit directory info for renames operations when using plain format
474
            if kind == 'directory' and self.plain_format:
475
                continue
0.64.178 by Ian Clatworthy
improve fast-export's handling of rename+delete combinations
476
            #path = self._adjust_path_for_renames(path, renamed, revision_id)
477
            file_cmds.append(commands.FileDeleteCommand(path))
478
        return file_cmds, modifies, renamed
479
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
480
    def _adjust_path_for_renames(self, path, renamed, revision_id):
0.64.174 by Ian Clatworthy
fix rename adjustment & kind change logic in fast-export
481
        # If a previous rename is found, we should adjust the path
482
        for old, new in renamed:
483
            if path == old:
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
484
                self.note("Changing path %s given rename to %s in revision %s"
485
                    % (path, new, revision_id))
0.64.174 by Ian Clatworthy
fix rename adjustment & kind change logic in fast-export
486
                path = new
487
            elif path.startswith(old + '/'):
0.64.176 by Ian Clatworthy
faster export of revision range & improved diagnostics in fast-export
488
                self.note(
489
                    "Adjusting path %s given rename of %s to %s in revision %s"
490
                    % (path, old, new, revision_id))
0.64.174 by Ian Clatworthy
fix rename adjustment & kind change logic in fast-export
491
                path = path.replace(old + "/", new + "/")
492
        return path
493
0.68.1 by Pieter de Bie
Classify bzr-fast-export
494
    def emit_tags(self):
495
        for tag, revid in self.branch.tags.get_tag_dict().items():
496
            try:
497
                mark = self.revid_to_mark[revid]
498
            except KeyError:
0.79.4 by Ian Clatworthy
use note and warning APIs
499
                self.warning('not creating tag %r pointing to non-existent '
500
                    'revision %s' % (tag, revid))
0.68.1 by Pieter de Bie
Classify bzr-fast-export
501
            else:
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
502
                git_ref = 'refs/tags/%s' % tag
0.64.133 by Ian Clatworthy
Fix str + int concat in bzr-fast-export (Stéphane Raimbault)
503
                self.print_cmd(commands.ResetCommand(git_ref, ":" + str(mark)))
0.68.1 by Pieter de Bie
Classify bzr-fast-export
504
0.79.9 by Ian Clatworthy
fix branch of first commit to not be refs/heads/tmp
505
    def _next_tmp_branch_name(self):
0.79.6 by Ian Clatworthy
refactor bzr_exporter to use Command objects
506
        """Return a unique branch name. The name will start with "tmp"."""
0.64.57 by Ian Clatworthy
integrate dato's bzr-fast-export
507
        prefix = 'tmp'
0.68.1 by Pieter de Bie
Classify bzr-fast-export
508
        if prefix not in self.branch_names:
509
            self.branch_names[prefix] = 0
510
        else:
511
            self.branch_names[prefix] += 1
512
            prefix = '%s.%d' % (prefix, self.branch_names[prefix])
513
        return prefix