~bzr-svn/bzr-svn/trunk

2592 by Jelmer Vernooij
Move push code to a separate file.
1
# Copyright (C) 2006-2009 Jelmer Vernooij <jelmer@samba.org>
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
2632 by Jelmer Vernooij
Change license back to GPLv2+.
5
# the Free Software Foundation; either version 2 of the License, or
2592 by Jelmer Vernooij
Move push code to a separate file.
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
"""Pushing to Subversion repositories."""
17
2761 by Menno Smits
Provide support for Python 2.4.
18
try:
19
    from collections import defaultdict
20
except ImportError:
21
    from bzrlib.plugins.svn.pycompat import defaultdict
2743 by Jelmer Vernooij
Return both foreign revid and mapping.
22
2747 by Jelmer Vernooij
Move push to push module.
23
import subvertpy
2700 by Jelmer Vernooij
Reformat imports.
24
from subvertpy import (
25
    ERR_FS_TXN_OUT_OF_DATE,
26
    SubversionException,
27
    properties,
28
    )
29
2592 by Jelmer Vernooij
Move push code to a separate file.
30
from bzrlib import (
31
    ui,
32
    urlutils,
33
    )
34
from bzrlib.errors import (
2747 by Jelmer Vernooij
Move push to push module.
35
    AlreadyBranchError,
2592 by Jelmer Vernooij
Move push code to a separate file.
36
    BzrError,
37
    DivergedBranches,
38
    NoSuchRevision,
39
    )
40
from bzrlib.repository import (
41
    InterRepository,
42
    )
43
from bzrlib.revision import (
44
    NULL_REVISION,
45
    )
46
from bzrlib.trace import (
47
    mutter,
48
    )
49
50
from bzrlib.plugins.svn.commit import (
51
    SvnCommitBuilder,
52
    )
53
from bzrlib.plugins.svn.config import (
54
    BranchConfig,
55
    )
56
from bzrlib.plugins.svn.errors import (
57
    ChangesRootLHSHistory, 
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
58
    MissingPrefix,
2592 by Jelmer Vernooij
Move push code to a separate file.
59
    convert_svn_error,
60
    )
3169 by Jelmer Vernooij
Fix import.
61
from bzrlib.plugins.svn.mapping import (
62
    SVN_REVPROP_BZR_SKIP,
63
    )
2592 by Jelmer Vernooij
Move push code to a separate file.
64
from bzrlib.plugins.svn.repository import (
65
    SvnRepositoryFormat, 
66
    SvnRepository,
67
    )
68
from bzrlib.plugins.svn.transport import (
3040 by Jelmer Vernooij
Support creating prefix.
69
    check_dirs_exist,
3039 by Jelmer Vernooij
Move create_branch_prefix to transport.
70
    create_branch_prefix,
2592 by Jelmer Vernooij
Move push code to a separate file.
71
    url_join_unescaped_path,
72
    )
73
2746 by Jelmer Vernooij
Fix push.
74
def find_push_base_revision(source, target, stop_revision):
2744 by Jelmer Vernooij
use remembered base revisions.
75
    start_revid = stop_revision
76
    for revid in source.iter_reverse_revision_history(stop_revision):
2746 by Jelmer Vernooij
Fix push.
77
        if target.has_revision(revid):
2744 by Jelmer Vernooij
use remembered base revisions.
78
            break
79
        start_revid = revid
80
    return start_revid
81
2592 by Jelmer Vernooij
Move push code to a separate file.
82
83
def replay_delta(builder, old_trees, new_tree):
84
    """Replays a delta to a commit builder.
85
86
    :param builder: The commit builder.
87
    :param old_tree: Original tree on top of which the delta should be applied
88
    :param new_tree: New tree that should be committed
89
    """
90
    for path, ie in new_tree.inventory.iter_entries():
91
        builder.record_entry_contents(ie.copy(), 
92
            [old_tree.inventory for old_tree in old_trees], 
93
            path, new_tree, None)
94
    builder.finish_inventory()
95
96
2741 by Jelmer Vernooij
return result foreign revid in a couple more cases.
97
def push_revision_tree(graph, target_repo, branch_path, config, source_repo, 
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
98
                       base_revid, revision_id, rev, 
99
                       base_foreign_revid, base_mapping,
100
                       push_metadata=True,
2592 by Jelmer Vernooij
Move push code to a separate file.
101
                       append_revisions_only=True,
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
102
                       override_svn_revprops=None):
2592 by Jelmer Vernooij
Move push code to a separate file.
103
    """Push a revision tree into a target repository.
104
105
    :param graph: Repository graph.
106
    :param target_repo: Target repository.
107
    :param branch_path: Branch path.
108
    :param config: Branch configuration.
109
    :param source_repo: Source repository.
110
    :param base_revid: Base revision id.
111
    :param revision_id: Revision id to push.
112
    :param rev: Revision object of revision to push.
113
    :param push_metadata: Whether to push metadata.
114
    :param append_revisions_only: Append revisions only.
115
    :return: Revision id of newly created revision.
116
    """
117
    assert rev.revision_id in (None, revision_id)
118
    old_tree = source_repo.revision_tree(revision_id)
119
    if rev.parent_ids:
120
        base_tree = source_repo.revision_tree(rev.parent_ids[0])
121
    else:
122
        base_tree = source_repo.revision_tree(NULL_REVISION)
123
124
    if push_metadata:
125
        base_revids = rev.parent_ids
126
    else:
127
        base_revids = [base_revid]
128
129
    try:
130
        opt_signature = source_repo.get_signature_text(rev.revision_id)
131
    except NoSuchRevision:
132
        opt_signature = None
133
134
    builder = SvnCommitBuilder(target_repo, branch_path, base_revids,
135
                               config, rev.timestamp,
136
                               rev.timezone, rev.committer, rev.properties, 
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
137
                               revision_id, base_foreign_revid, base_mapping,
2592 by Jelmer Vernooij
Move push code to a separate file.
138
                               base_tree.inventory,
139
                               push_metadata=push_metadata,
140
                               graph=graph, opt_signature=opt_signature,
141
                               texts=source_repo.texts,
142
                               append_revisions_only=append_revisions_only,
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
143
                               override_svn_revprops=override_svn_revprops)
2592 by Jelmer Vernooij
Move push code to a separate file.
144
    parent_trees = [base_tree]
145
    for p in rev.parent_ids[1:]:
146
        try:
147
            parent_trees.append(source_repo.revision_tree(p))
148
        except NoSuchRevision:
149
            pass # Ghost, ignore
3046 by Jelmer Vernooij
append_revisions_only now defaults to True, to prevent new users from accidently changing their Subversion mainline and upsetting their fellow committers.
150
    # TODO: Use iter_changes() ?
2592 by Jelmer Vernooij
Move push code to a separate file.
151
    replay_delta(builder, parent_trees, old_tree)
152
    try:
153
        revid = builder.commit(rev.message)
154
    except SubversionException, (msg, num):
155
        if num == ERR_FS_TXN_OUT_OF_DATE:
156
            raise DivergedBranches(source_repo, target_repo)
157
        raise
158
    except ChangesRootLHSHistory:
159
        raise BzrError("Unable to push revision %r because it would change the ordering of existing revisions on the Subversion repository root. Use rebase and try again or push to a non-root path" % revision_id)
160
2743 by Jelmer Vernooij
Return both foreign revid and mapping.
161
    return revid, (builder.result_foreign_revid, builder.mapping)
2592 by Jelmer Vernooij
Move push code to a separate file.
162
163
164
class InterToSvnRepository(InterRepository):
165
    """Any to Subversion repository actions."""
166
167
    _matching_repo_format = SvnRepositoryFormat()
168
2601 by Jelmer Vernooij
Use only a single graph object.
169
    def __init__(self, source, target, graph=None):
2594 by Jelmer Vernooij
Use InterToSvnRepository for pushing ancestors.
170
        InterRepository.__init__(self, source, target)
2601 by Jelmer Vernooij
Use only a single graph object.
171
        self._graph = graph
2743 by Jelmer Vernooij
Return both foreign revid and mapping.
172
        # Dictionary: revid -> branch_path -> (foreign_revid, mapping)
173
        self._foreign_info = defaultdict(dict)
2594 by Jelmer Vernooij
Use InterToSvnRepository for pushing ancestors.
174
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
175
    def _target_has_revision(self, revid):
176
        """Slightly optimized version of self.target.has_revision()."""
177
        if revid in self._foreign_info:
178
            return True
179
        return self.target.has_revision(revid)
180
2744 by Jelmer Vernooij
use remembered base revisions.
181
    def _get_base_revision(self, revid, path):
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
182
        """Find the revision info for a revision id."""
183
        if revid == NULL_REVISION:
184
            return None, None
2744 by Jelmer Vernooij
use remembered base revisions.
185
        if not revid in self._foreign_info:
3087 by Jelmer Vernooij
Try harder to use the same branch.
186
            # FIXME: Prefer revisions in path
3207 by Jelmer Vernooij
Use standard bzr naming for lookup functions.
187
            return self.target.lookup_bzr_revision_id(revid)
2744 by Jelmer Vernooij
use remembered base revisions.
188
        if path in self._foreign_info[revid]:
189
            return self._foreign_info[revid][path]
190
        else:
191
            return self._foreign_info[revid].values()[0]
192
3087 by Jelmer Vernooij
Try harder to use the same branch.
193
    def add_path_info(self, revid, path, foreign_info):
194
        self._foreign_info[revid][path] = foreign_info
195
2592 by Jelmer Vernooij
Move push code to a separate file.
196
    @staticmethod
197
    def _get_repo_format_to_test():
198
        """See InterRepository._get_repo_format_to_test()."""
199
        return None
200
2599 by Jelmer Vernooij
Use proper base revision during dpush rather than whatever is at the tip of the branch.
201
    def push_branch(self, todo, layout, project, target_branch, target_config,
3046 by Jelmer Vernooij
append_revisions_only now defaults to True, to prevent new users from accidently changing their Subversion mainline and upsetting their fellow committers.
202
        push_merged, overwrite):
2600 by Jelmer Vernooij
Reformat copy_content_into a bit.
203
        """Push a series of revisions into a Subversion repository.
204
205
        """
2828 by Jelmer Vernooij
Try really hard to avoid retrieving branch tip more than once.
206
        assert todo != []
207
        count = 0
2597 by Jelmer Vernooij
Move push to InterToSvnRepository.
208
        pb = ui.ui_factory.nested_progress_bar()
209
        try:
210
            for rev in self.source.get_revisions(todo):
211
                pb.update("pushing revisions", todo.index(rev.revision_id), 
212
                          len(todo))
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
213
                if push_merged and len(rev.parent_ids) > 1:
2597 by Jelmer Vernooij
Move push to InterToSvnRepository.
214
                    self.push_ancestors(layout, project, 
215
                        rev.parent_ids, create_prefix=True)
3046 by Jelmer Vernooij
append_revisions_only now defaults to True, to prevent new users from accidently changing their Subversion mainline and upsetting their fellow committers.
216
                last = self.push(target_branch, target_config, rev, 
217
                    overwrite=overwrite)
2828 by Jelmer Vernooij
Try really hard to avoid retrieving branch tip more than once.
218
                count += 1
2829 by Jelmer Vernooij
Print more info about pushed svn branches.
219
            return (count, last)
2597 by Jelmer Vernooij
Move push to InterToSvnRepository.
220
        finally:
221
            pb.finished()
222
3043 by Jelmer Vernooij
Simplify push.
223
    def push(self, target_path, target_config, rev, push_metadata=True, 
3046 by Jelmer Vernooij
append_revisions_only now defaults to True, to prevent new users from accidently changing their Subversion mainline and upsetting their fellow committers.
224
             base_revid=None, overwrite=False):
3043 by Jelmer Vernooij
Simplify push.
225
        if base_revid is None:
226
            if rev.parent_ids:
227
                base_revid = rev.parent_ids[0]
228
            else:
229
                base_revid = NULL_REVISION
230
        base_foreign_revid, base_mapping = self._get_base_revision(base_revid,
231
            target_path)
232
        mutter('pushing %r (%r)', rev.revision_id, rev.parent_ids)
233
        revid, foreign_info = push_revision_tree(self.get_graph(), self.target,
234
            target_path, target_config, self.source, base_revid,
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
235
            rev.revision_id, rev, 
236
            base_foreign_revid, base_mapping,
237
            push_metadata=push_metadata, 
3046 by Jelmer Vernooij
append_revisions_only now defaults to True, to prevent new users from accidently changing their Subversion mainline and upsetting their fellow committers.
238
            append_revisions_only=self.get_append_revisions_only(target_config, overwrite), 
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
239
            override_svn_revprops=target_config.get_override_svn_revprops())
3043 by Jelmer Vernooij
Simplify push.
240
        assert revid == rev.revision_id or not push_metadata
3087 by Jelmer Vernooij
Try harder to use the same branch.
241
        self.add_path_info(target_path, revid, foreign_info)
2829 by Jelmer Vernooij
Print more info about pushed svn branches.
242
        return (revid, foreign_info)
2742 by Jelmer Vernooij
Pass result foreign revid in a couple more cases.
243
2748 by Jelmer Vernooij
Avoid replaces when not necessary.
244
    def _get_branch_config(self, branch_path):
245
        return BranchConfig(urlutils.join(self.target.base, branch_path), 
246
                self.target.uuid)
247
2747 by Jelmer Vernooij
Move push to push module.
248
    def push_new(self, target_branch_path, 
2742 by Jelmer Vernooij
Pass result foreign revid in a couple more cases.
249
             stop_revision, push_metadata=True, append_revisions_only=False, 
2744 by Jelmer Vernooij
use remembered base revisions.
250
             override_svn_revprops=None):
2748 by Jelmer Vernooij
Avoid replaces when not necessary.
251
        """Push a revision into Subversion, creating a new branch.
252
253
        This will do a new commit in the target branch.
254
255
        :param graph: Repository graph.
256
        :param target_repository: Repository to push to
257
        :param target_branch_path: Path to create new branch at
258
        :param source: Source repository
259
        :return: Revision id of the pushed revision, foreign revision id that was 
260
            pushed
261
        """
262
        start_revid = find_push_base_revision(self.source, self.target, 
263
                stop_revision)
264
        rev = self.source.get_revision(start_revid)
265
        if rev.parent_ids == []:
266
            start_revid_parent = NULL_REVISION
267
        else:
268
            start_revid_parent = rev.parent_ids[0]
269
        # If this is just intended to create a new branch
270
        mapping = self.target.get_mapping()
271
        if (start_revid != NULL_REVISION and 
272
            start_revid_parent != NULL_REVISION and 
3087 by Jelmer Vernooij
Try harder to use the same branch.
273
            stop_revision == start_revid and mapping.supports_hidden and
274
            not append_revisions_only):
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
275
            if (self._target_has_revision(start_revid) or 
2748 by Jelmer Vernooij
Avoid replaces when not necessary.
276
                start_revid == NULL_REVISION):
277
                revid = start_revid
278
            else:
279
                revid = start_revid_parent
280
            revid, foreign_info = create_branch_with_hidden_commit(self.target, 
281
                target_branch_path, revid, set_metadata=True, deletefirst=None)
282
        else:
283
            base_foreign_revid, base_mapping = self._get_base_revision(start_revid_parent, target_branch_path)
284
            revid, foreign_info = push_revision_tree(self.get_graph(), self.target, target_branch_path, 
285
                self._get_branch_config(target_branch_path), 
286
                self.source, start_revid_parent, start_revid, 
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
287
                rev, base_foreign_revid, base_mapping,
288
                push_metadata=push_metadata,
2742 by Jelmer Vernooij
Pass result foreign revid in a couple more cases.
289
                append_revisions_only=append_revisions_only,
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
290
                override_svn_revprops=override_svn_revprops) 
3087 by Jelmer Vernooij
Try harder to use the same branch.
291
        self.add_path_info(target_branch_path, revid, foreign_info)
2743 by Jelmer Vernooij
Return both foreign revid and mapping.
292
        return revid, foreign_info
2742 by Jelmer Vernooij
Pass result foreign revid in a couple more cases.
293
2594 by Jelmer Vernooij
Use InterToSvnRepository for pushing ancestors.
294
    def push_ancestors(self, layout, project, parent_revids, 
295
                       create_prefix=False):
296
        """Push the ancestors of a revision.
297
298
        :param layout: Subversion layout
299
        :param project: Project name
300
        :param parent_revids: The revision ids of the basic ancestors to push
301
        :param create_prefix: Whether to optionally create the prefix of the branches.
302
        """
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
303
        present_rhs_parents = self.target.has_revisions(parent_revids[1:])
304
        unique_ancestors = set()
305
        for parent_revid in parent_revids[1:]:
306
            if parent_revid in present_rhs_parents:
307
                continue
2594 by Jelmer Vernooij
Use InterToSvnRepository for pushing ancestors.
308
            # Push merged revisions
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
309
            unique_ancestors.update(self.get_graph().find_unique_ancestors(parent_revid, [parent_revids[0]]))
310
        for x in self.get_graph().iter_topo_order(unique_ancestors):
311
            if self._target_has_revision(x):
312
                continue
313
            rev = self.source.get_revision(x)
314
            rhs_branch_path = determine_branch_path(rev, layout, project)
315
            try:
3081 by Jelmer Vernooij
Improve formatting.
316
                self.push_new(rhs_branch_path, x, append_revisions_only=False)
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
317
            except MissingPrefix, e:
318
                if not create_prefix:
319
                    raise
320
                revprops = {properties.PROP_REVISION_LOG: "Add branches directory."}
321
                if self.target.transport.has_capability("commit-revprops"):
3169 by Jelmer Vernooij
Fix import.
322
                    revprops[SVN_REVPROP_BZR_SKIP] = ""
3038 by Jelmer Vernooij
Simplify arguments for create_branch_prefix.
323
                create_branch_prefix(self.target.transport, revprops, e.path.split("/")[:-1], filter(lambda x: x != "", e.existing_path.split("/")))
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
324
                self.push_new(rhs_branch_path, x, append_revisions_only=False)
2594 by Jelmer Vernooij
Use InterToSvnRepository for pushing ancestors.
325
2748 by Jelmer Vernooij
Avoid replaces when not necessary.
326
    def push_new_branch(self, layout, project, target_branch_path, 
3046 by Jelmer Vernooij
append_revisions_only now defaults to True, to prevent new users from accidently changing their Subversion mainline and upsetting their fellow committers.
327
        stop_revision, push_merged=None, override_svn_revprops=None,
328
        overwrite=False):
2747 by Jelmer Vernooij
Move push to push module.
329
        if self.target.transport.check_path(target_branch_path,
330
            self.target.get_latest_revnum()) != subvertpy.NODE_NONE:
331
            raise AlreadyBranchError(target_branch_path)
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
332
        target_config = self._get_branch_config(target_branch_path)
333
        if push_merged is None:
334
            push_merged = (layout.push_merged_revisions(project) and
335
                           target_config.get_push_merged_revisions())
2748 by Jelmer Vernooij
Avoid replaces when not necessary.
336
        begin_revid, _ = self.push_new(target_branch_path, stop_revision, 
2747 by Jelmer Vernooij
Move push to push module.
337
                 append_revisions_only=True, 
338
                 override_svn_revprops=override_svn_revprops)
2748 by Jelmer Vernooij
Avoid replaces when not necessary.
339
        todo = []
340
        for revid in self.source.iter_reverse_revision_history(stop_revision):
341
            if revid == begin_revid:
342
                break
343
            todo.append(revid)
344
        todo.reverse()
2828 by Jelmer Vernooij
Try really hard to avoid retrieving branch tip more than once.
345
        if todo != []:
346
            self.push_branch(todo, layout, project, target_branch_path, 
3046 by Jelmer Vernooij
append_revisions_only now defaults to True, to prevent new users from accidently changing their Subversion mainline and upsetting their fellow committers.
347
                target_config, push_merged, overwrite)
2747 by Jelmer Vernooij
Move push to push module.
348
2605 by Jelmer Vernooij
Split revision pushing out of copy_content_intO.
349
    def push_nonmainline_revision(self, rev, layout):
350
        mutter('pushing %r', rev.revision_id)
2651 by Jelmer Vernooij
Fix branch from svn into svn.
351
        if rev.parent_ids:
352
            parent_revid = rev.parent_ids[0]
353
        else:
2605 by Jelmer Vernooij
Split revision pushing out of copy_content_intO.
354
            parent_revid = NULL_REVISION
355
356
        if parent_revid == NULL_REVISION:
2651 by Jelmer Vernooij
Fix branch from svn into svn.
357
            target_project = None
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
358
            base_foreign_revid = None
359
            base_mapping = None
2605 by Jelmer Vernooij
Split revision pushing out of copy_content_intO.
360
        else:
3207 by Jelmer Vernooij
Use standard bzr naming for lookup functions.
361
            base_foreign_revid, base_mapping = self.target.lookup_bzr_revision_id(parent_revid,
3212 by Jelmer Vernooij
Cope with pushing new non-mainline revisions.
362
                foreign_sibling=getattr(rev, "foreign_revid", None))
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
363
            (_, target_project, _, _) = layout.parse(base_foreign_revid[1])
2701 by Jelmer Vernooij
Fix concurrent access problems during push/commit.
364
        bp = determine_branch_path(rev, layout, target_project)
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
365
        target_config = self._get_branch_config(bp)
2606 by Jelmer Vernooij
Simplify pushing of first revision.
366
        if (layout.push_merged_revisions(target_project) and 
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
367
            target_config.get_push_merged_revisions() and
368
            len(rev.parent_ids) > 1):
2606 by Jelmer Vernooij
Simplify pushing of first revision.
369
            self.push_ancestors(layout, target_project, 
370
                rev.parent_ids, create_prefix=True)
2742 by Jelmer Vernooij
Pass result foreign revid in a couple more cases.
371
        return push_revision_tree(self.get_graph(), self.target, 
2606 by Jelmer Vernooij
Simplify pushing of first revision.
372
            bp, target_config, 
373
            self.source, parent_revid, rev.revision_id, rev, 
3086 by Jelmer Vernooij
Explicitly specify base_foreign_revid / base_mapping everywhere.
374
            base_foreign_revid, base_mapping,
3046 by Jelmer Vernooij
append_revisions_only now defaults to True, to prevent new users from accidently changing their Subversion mainline and upsetting their fellow committers.
375
            append_revisions_only=self.get_append_revisions_only(target_config))
376
377
    def get_append_revisions_only(self, target_config, overwrite=False):
3081 by Jelmer Vernooij
Improve formatting.
378
        return target_config.get_append_revisions_only(not overwrite)
2605 by Jelmer Vernooij
Split revision pushing out of copy_content_intO.
379
2737 by Jelmer Vernooij
Fix graph retrieval.
380
    def get_graph(self):
381
        if self._graph is None:
382
            self._graph = self.source.get_graph(self.target)
383
        return self._graph
384
2592 by Jelmer Vernooij
Move push code to a separate file.
385
    def copy_content(self, revision_id=None, pb=None):
386
        """See InterRepository.copy_content."""
387
        self.source.lock_read()
388
        try:
389
            assert revision_id is not None, "fetching all revisions not supported"
390
            # Go back over the LHS parent until we reach a revid we know
391
            todo = []
2604 by Jelmer Vernooij
use revision objects rather than revision ids internally in copy_content_into.
392
            for revision_id in self.source.iter_reverse_revision_history(revision_id):
2830 by Jelmer Vernooij
Improve push-merged-revisions support.
393
                if self._target_has_revision(revision_id):
2592 by Jelmer Vernooij
Move push code to a separate file.
394
                    break
395
                todo.append(revision_id)
396
            if todo == []:
397
                # Nothing to do
398
                return
2606 by Jelmer Vernooij
Simplify pushing of first revision.
399
            todo.reverse()
2592 by Jelmer Vernooij
Move push code to a separate file.
400
            mutter("pushing %r into svn", todo)
401
            layout = self.target.get_layout()
2606 by Jelmer Vernooij
Simplify pushing of first revision.
402
            for rev in self.source.get_revisions(todo):
2592 by Jelmer Vernooij
Move push code to a separate file.
403
                if pb is not None:
2604 by Jelmer Vernooij
use revision objects rather than revision ids internally in copy_content_into.
404
                    pb.update("pushing revisions", todo.index(rev.revision_id), 
2592 by Jelmer Vernooij
Move push code to a separate file.
405
                        len(todo))
2605 by Jelmer Vernooij
Split revision pushing out of copy_content_intO.
406
                self.push_nonmainline_revision(rev, layout)
2592 by Jelmer Vernooij
Move push code to a separate file.
407
        finally:
408
            self.source.unlock()
409
2781.1.8 by Jelmer Vernooij
Support fetch_spec in InterToSvnRepository.fetch.
410
    def fetch(self, revision_id=None, pb=None, find_ghosts=False, 
411
        fetch_spec=None):
2592 by Jelmer Vernooij
Move push code to a separate file.
412
        """Fetch revisions. """
2781.1.8 by Jelmer Vernooij
Support fetch_spec in InterToSvnRepository.fetch.
413
        if fetch_spec is not None:
414
            for revid in fetch_spec.heads:
415
                self.copy_content(revision_id=revid, pb=pb)
416
        else:
417
            self.copy_content(revision_id=revision_id, pb=pb)
2592 by Jelmer Vernooij
Move push code to a separate file.
418
419
    @staticmethod
420
    def is_compatible(source, target):
421
        """Be compatible with SvnRepository."""
422
        return isinstance(target, SvnRepository)
423
424
425
def determine_branch_path(rev, layout, project=None):
2606 by Jelmer Vernooij
Simplify pushing of first revision.
426
    """Create a sane branch path to use for a revision.
427
    
428
    :param rev: Revision object
429
    :param layout: Subversion layout
430
    :param project: Optional project name, as used by the layout
431
    :return: Branch path string
432
    """
2592 by Jelmer Vernooij
Move push code to a separate file.
433
    nick = (rev.properties.get('branch-nick') or "merged").encode("utf-8").replace("/","_")
434
    if project is None:
435
        return layout.get_branch_path(nick)
436
    else:
437
        return layout.get_branch_path(nick, project)
438
439
440
def create_branch_with_hidden_commit(repository, branch_path, revid,
441
                                     set_metadata=True,
442
                                     deletefirst=False):
443
    """Create a new branch using a simple "svn cp" operation.
444
445
    :param repository: Repository in which to create the branch.
446
    :param branch_path: Branch path
447
    :param revid: Revision id to keep as tip.
448
    :param deletefirst: Whether to delete an existing branch at this location first.
2741 by Jelmer Vernooij
return result foreign revid in a couple more cases.
449
    :return: Revision id that was pushed and the related foreign revision id.
2592 by Jelmer Vernooij
Move push code to a separate file.
450
    """
451
    revprops = {properties.PROP_REVISION_LOG: "Create new branch."}
452
    revmeta, mapping = repository._get_revmeta(revid)
453
    fileprops = dict(revmeta.get_fileprops().iteritems())
454
    if set_metadata:
455
        assert mapping.supports_hidden
456
        (set_custom_revprops, set_custom_fileprops) = repository._properties_to_set(mapping)
457
        if set_custom_revprops:
458
            mapping.export_hidden_revprops(branch_path, revprops)
459
            if (not set_custom_fileprops and 
460
                not repository.transport.has_capability("log-revprops")):
461
                # Tell clients about first approximate use of revision 
462
                # properties
463
                mapping.export_revprop_redirect(
464
                    repository.get_latest_revnum()+1, fileprops)
465
        if set_custom_fileprops:
466
            mapping.export_hidden_fileprops(fileprops)
467
    parent = urlutils.dirname(branch_path)
468
469
    bp_parts = branch_path.split("/")
3040 by Jelmer Vernooij
Support creating prefix.
470
    existing_bp_parts = check_dirs_exist(repository.transport, bp_parts, -1)
2592 by Jelmer Vernooij
Move push code to a separate file.
471
    if (len(bp_parts) not in (len(existing_bp_parts), len(existing_bp_parts)+1)):
472
        raise MissingPrefix("/".join(bp_parts), "/".join(existing_bp_parts))
473
474
    if deletefirst is None:
475
        deletefirst = (bp_parts == existing_bp_parts)
2741 by Jelmer Vernooij
return result foreign revid in a couple more cases.
476
    
477
    foreign_revid = [repository.uuid, branch_path]
478
479
    def done(revno, *args):
480
        foreign_revid.append(revno)
2592 by Jelmer Vernooij
Move push code to a separate file.
481
482
    conn = repository.transport.get_connection(parent)
483
    try:
2741 by Jelmer Vernooij
return result foreign revid in a couple more cases.
484
        ci = convert_svn_error(conn.get_commit_editor)(revprops, done)
2592 by Jelmer Vernooij
Move push code to a separate file.
485
        try:
486
            root = ci.open_root()
487
            if deletefirst:
488
                root.delete_entry(urlutils.basename(branch_path))
489
            branch_dir = root.add_directory(urlutils.basename(branch_path), 
490
                    url_join_unescaped_path(repository.base, revmeta.branch_path), revmeta.revnum)
491
            for k, (ov, nv) in properties.diff(fileprops, revmeta.get_fileprops()).iteritems():
492
                branch_dir.change_prop(k, nv)
493
            branch_dir.close()
494
            root.close()
495
        except:
496
            ci.abort()
497
            raise
498
        ci.close()
2743 by Jelmer Vernooij
Return both foreign revid and mapping.
499
        return revid, (tuple(foreign_revid), mapping)
2592 by Jelmer Vernooij
Move push code to a separate file.
500
    finally:
501
        repository.transport.add_connection(conn)