~ubuntu-branches/debian/sid/bzr-svn/sid

« back to all changes in this revision

Viewing changes to workingtree.py

  • Committer: Bazaar Package Importer
  • Author(s): Jelmer Vernooij
  • Date: 2009-03-10 14:38:42 UTC
  • mfrom: (1.2.1 upstream) (3.1.4 jaunty)
  • Revision ID: james.westby@ubuntu.com-20090310143842-ucp9fxog1yi3la8f
Tags: 0.5.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
 
1
# Copyright (C) 2005-2009 Jelmer Vernooij <jelmer@samba.org>
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 3 of the License, or
 
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
7
 
8
8
# This program is distributed in the hope that it will be useful,
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
"""Checkouts and working trees (working copies)."""
17
17
 
18
 
import bzrlib
19
 
from bzrlib import urlutils
20
 
from bzrlib.branch import PullResult
21
 
from bzrlib.bzrdir import BzrDirFormat, BzrDir
22
 
from bzrlib.errors import (InvalidRevisionId, NotBranchError, NoSuchFile,
23
 
                           NoRepositoryPresent, BzrError, UninitializableFormat,
24
 
                           OutOfDateTree)
25
 
from bzrlib.inventory import Inventory, InventoryFile, InventoryLink
26
 
from bzrlib.lockable_files import TransportLock, LockableFiles
 
18
from collections import defaultdict
 
19
import os
 
20
import subvertpy
 
21
import subvertpy.wc
 
22
from subvertpy import properties
 
23
from subvertpy.wc import *
 
24
import urllib
 
25
 
 
26
import bzrlib, bzrlib.add
 
27
from bzrlib import (
 
28
    osutils,
 
29
    urlutils,
 
30
    )
 
31
from bzrlib.branch import (
 
32
    BranchReferenceFormat,
 
33
    )
 
34
from bzrlib.bzrdir import (
 
35
    BzrDirFormat,
 
36
    BzrDir,
 
37
    Converter,
 
38
    )
 
39
from bzrlib.errors import (
 
40
    NotBranchError,
 
41
    NoSuchFile,
 
42
    NoSuchRevision,
 
43
    NoRepositoryPresent,
 
44
    NoWorkingTree,
 
45
    UnsupportedFormatError,
 
46
    UninitializableFormat,
 
47
    )
 
48
from bzrlib.inventory import (
 
49
    Inventory,
 
50
    InventoryFile,
 
51
    InventoryLink,
 
52
    )
 
53
from bzrlib.lockable_files import LockableFiles
27
54
from bzrlib.lockdir import LockDir
28
 
from bzrlib.osutils import file_kind, fingerprint_file, supports_executable
29
55
from bzrlib.revision import NULL_REVISION
30
56
from bzrlib.trace import mutter
31
 
from bzrlib.revisiontree import RevisionTree
32
 
from bzrlib.transport.local import LocalTransport
33
 
from bzrlib.workingtree import WorkingTree, WorkingTreeFormat
 
57
from bzrlib.transport import get_transport
 
58
from bzrlib.workingtree import (
 
59
    WorkingTree,
 
60
    WorkingTreeFormat,
 
61
    )
34
62
 
35
 
from bzrlib.plugins.svn.branch import SvnBranch
36
 
from bzrlib.plugins.svn.commit import _revision_id_to_svk_feature
37
 
from bzrlib.plugins.svn.convert import SvnConverter
38
 
from bzrlib.plugins.svn.errors import LocalCommitsUnsupported, NoSvnRepositoryPresent
39
 
from bzrlib.plugins.svn.mapping import (SVN_PROP_BZR_ANCESTRY, SVN_PROP_BZR_FILEIDS, 
40
 
                     SVN_PROP_BZR_REVISION_ID, SVN_PROP_BZR_REVISION_INFO,
41
 
                     generate_revision_metadata)
 
63
from bzrlib.plugins.svn import (
 
64
    errors as bzrsvn_errors,
 
65
    svk,
 
66
    )
 
67
from bzrlib.plugins.svn.branch import (
 
68
    SvnBranch,
 
69
    )
 
70
from bzrlib.plugins.svn.commit import (
 
71
    _revision_id_to_svk_feature,
 
72
    )
 
73
from bzrlib.plugins.svn.errors import (
 
74
    convert_svn_error,
 
75
    )
 
76
from bzrlib.plugins.svn.fileids import (
 
77
    idmap_lookup,
 
78
    )
 
79
from bzrlib.plugins.svn.format import get_rich_root_format
 
80
from bzrlib.plugins.svn.mapping import escape_svn_path
42
81
from bzrlib.plugins.svn.remote import SvnRemoteAccess
43
82
from bzrlib.plugins.svn.repository import SvnRepository
44
 
from bzrlib.plugins.svn.svk import SVN_PROP_SVK_MERGE, parse_svk_features, serialize_svk_features
45
 
from bzrlib.plugins.svn.mapping import escape_svn_path
46
 
from bzrlib.plugins.svn.transport import (SvnRaTransport, bzr_to_svn_url, create_svn_client,
47
 
                       svn_config) 
48
 
from bzrlib.plugins.svn.tree import SvnBasisTree
49
 
 
50
 
import os
51
 
import urllib
52
 
 
53
 
import svn.core, svn.wc
54
 
from svn.core import SubversionException
55
 
 
56
 
from bzrlib.plugins.svn.errors import NoCheckoutSupport
57
 
from bzrlib.plugins.svn.format import get_rich_root_format
 
83
from bzrlib.plugins.svn.transport import (
 
84
    SvnRaTransport,
 
85
    svn_config,
 
86
    )
 
87
from bzrlib.plugins.svn.tree import (
 
88
    SvnBasisTree,
 
89
    SubversionTree,
 
90
    )
 
91
 
 
92
def update_wc(adm, basedir, conn, revnum):
 
93
    # FIXME: honor SVN_CONFIG_SECTION_HELPERS:SVN_CONFIG_OPTION_DIFF3_CMD
 
94
    # FIXME: honor SVN_CONFIG_SECTION_MISCELLANY:SVN_CONFIG_OPTION_USE_COMMIT_TIMES
 
95
    # FIXME: honor SVN_CONFIG_SECTION_MISCELLANY:SVN_CONFIG_OPTION_PRESERVED_CF_EXTS
 
96
    editor = adm.get_update_editor(basedir, False, True)
 
97
    assert editor is not None
 
98
    reporter = conn.do_update(revnum, "", True, editor)
 
99
    adm.crawl_revisions(basedir, reporter, restore_files=False, 
 
100
                        recurse=True, use_commit_times=False)
 
101
    # FIXME: handle externals
 
102
 
 
103
 
 
104
def apply_prop_changes(orig_props, prop_changes):
 
105
    """Apply a set of property changes to a properties dictionary.
 
106
    
 
107
    :param orig_props: Dictionary with original properties (will be modified)
 
108
    :param prop_changes: Dictionary of new property values (None for deletion)
 
109
    :return: New dictionary
 
110
    """
 
111
    for k,v in prop_changes:
 
112
        if v is None:
 
113
            del orig_props[k]
 
114
        else:
 
115
            orig_props[k] = v
 
116
    return orig_props
 
117
 
58
118
 
59
119
def generate_ignore_list(ignore_map):
60
120
    """Create a list of ignores, ordered by directory.
64
124
    """
65
125
    ignores = []
66
126
    keys = ignore_map.keys()
67
 
    keys.sort()
68
 
    for k in keys:
69
 
        ignores.append("./" + os.path.join(k.strip("/"), ignore_map[k].strip("/")))
 
127
    for k in sorted(keys):
 
128
        elements = ["."]
 
129
        if k.strip("/") != "":
 
130
            elements.append(k.strip("/"))
 
131
        elements.append(ignore_map[k].strip("/"))
 
132
        ignores.append("/".join(elements))
70
133
    return ignores
71
134
 
72
135
 
73
 
class SvnWorkingTree(WorkingTree):
74
 
    """WorkingTree implementation that uses a Subversion Working Copy for storage."""
 
136
class SvnWorkingTree(WorkingTree, SubversionTree):
 
137
    """WorkingTree implementation that uses a svn working copy for storage."""
 
138
 
75
139
    def __init__(self, bzrdir, local_path, branch):
76
 
        self._format = SvnWorkingTreeFormat()
 
140
        version = check_wc(local_path)
 
141
        self._format = SvnWorkingTreeFormat(version)
77
142
        self.basedir = local_path
 
143
        assert isinstance(self.basedir, unicode)
78
144
        self.bzrdir = bzrdir
79
145
        self._branch = branch
80
146
        self.base_revnum = 0
81
 
        self.client_ctx = create_svn_client(bzrdir.svn_url)
82
 
        self.client_ctx.log_msg_func2 = \
83
 
                svn.client.svn_swig_py_get_commit_log_func
84
 
 
85
 
        self._get_wc()
86
 
        status = svn.wc.revision_status(self.basedir, None, True, None, None)
87
 
        self.base_revnum = status.max_rev
88
 
        self.base_tree = SvnBasisTree(self)
89
 
        self.base_revid = branch.generate_revision_id(self.base_revnum)
90
 
 
91
 
        self.read_working_inventory()
92
 
 
93
 
        self.controldir = os.path.join(self.basedir, svn.wc.get_adm_dir(), 
94
 
                                       'bzr')
 
147
        max_rev = revision_status(self.basedir, None, True)[1]
 
148
        self._update_base_revnum(max_rev)
 
149
        self._detect_case_handling()
 
150
        self._transport = bzrdir.get_workingtree_transport(None)
 
151
        self.controldir = os.path.join(bzrdir.svn_controldir, 'bzr')
95
152
        try:
96
153
            os.makedirs(self.controldir)
97
154
            os.makedirs(os.path.join(self.controldir, 'lock'))
98
155
        except OSError:
99
156
            pass
100
157
        control_transport = bzrdir.transport.clone(urlutils.join(
101
 
                                                   svn.wc.get_adm_dir(), 'bzr'))
 
158
                                                   get_adm_dir(), 'bzr'))
102
159
        self._control_files = LockableFiles(control_transport, 'lock', LockDir)
 
160
        self.views = self._make_views()
103
161
 
104
162
    def get_ignore_list(self):
105
 
        ignores = set([svn.wc.get_adm_dir()])
106
 
        ignores.update(svn.wc.get_default_ignores(svn_config))
 
163
        """Obtain the list of ignore patterns for this working tree.
 
164
 
 
165
        :note: Will interpret the svn:ignore properties, rather than read 
 
166
               .bzrignore
 
167
        """
 
168
        ignores = set([get_adm_dir()])
 
169
        ignores.update(svn_config.get_default_ignores())
107
170
 
108
171
        def dir_add(wc, prefix, patprefix):
109
 
            ignorestr = svn.wc.prop_get(svn.core.SVN_PROP_IGNORE, 
110
 
                                        self.abspath(prefix).rstrip("/"), wc)
 
172
            ignorestr = wc.prop_get(properties.PROP_IGNORE, 
 
173
                                    self.abspath(prefix).encode("utf-8").rstrip("/"))
111
174
            if ignorestr is not None:
112
175
                for pat in ignorestr.splitlines():
113
176
                    ignores.add(urlutils.joinpath(patprefix, pat))
114
177
 
115
 
            entries = svn.wc.entries_read(wc, False)
 
178
            entries = wc.entries_read(False)
116
179
            for entry in entries:
117
180
                if entry == "":
118
181
                    continue
119
182
 
120
183
                # Ignore ignores on things that aren't directories
121
 
                if entries[entry].kind != svn.core.svn_node_dir:
 
184
                if entries[entry].kind != subvertpy.NODE_DIR:
122
185
                    continue
123
186
 
124
187
                subprefix = os.path.join(prefix, entry)
125
188
 
126
 
                subwc = svn.wc.adm_open3(wc, self.abspath(subprefix), False, 
127
 
                                         0, None)
 
189
                subwc = self._get_wc(subprefix, base=wc)
128
190
                try:
129
191
                    dir_add(subwc, subprefix, urlutils.joinpath(patprefix, entry))
130
192
                finally:
131
 
                    svn.wc.adm_close(subwc)
 
193
                    subwc.close()
132
194
 
133
195
        wc = self._get_wc()
134
196
        try:
135
197
            dir_add(wc, "", ".")
136
198
        finally:
137
 
            svn.wc.adm_close(wc)
 
199
            wc.close()
138
200
 
139
201
        return ignores
140
202
 
141
203
    def is_control_filename(self, path):
142
 
        return svn.wc.is_adm_dir(path)
 
204
        """Check whether path is a control file (used by bzr or svn)."""
 
205
        return is_adm_dir(path)
143
206
 
144
207
    def apply_inventory_delta(self, changes):
145
208
        raise NotImplementedError(self.apply_inventory_delta)
146
209
 
147
 
    def update(self, change_reporter=None):
148
 
        rev = svn.core.svn_opt_revision_t()
149
 
        rev.kind = svn.core.svn_opt_revision_head
150
 
        svn.client.update(self.basedir, rev, True, self.client_ctx)
151
 
 
152
 
    def remove(self, files, verbose=False, to_file=None):
 
210
    def _update(self, revnum=None):
 
211
        if revnum is None:
 
212
            # FIXME: should be able to use -1 here
 
213
            revnum = self.branch.get_revnum()
 
214
        adm = self._get_wc(write_lock=True, depth=-1)
 
215
        try:
 
216
            conn = self.branch.repository.transport.get_connection(self.branch.get_branch_path())
 
217
            try:
 
218
                update_wc(adm, self.basedir, conn, revnum)
 
219
            finally:
 
220
                self.branch.repository.transport.add_connection(conn)
 
221
        finally:
 
222
            adm.close()
 
223
        return revnum
 
224
 
 
225
    def update(self, change_reporter=None, possible_transports=None, 
 
226
               revnum=None):
 
227
        """Update the workingtree to a new Bazaar revision number.
 
228
        
 
229
        """
 
230
        orig_revnum = self.base_revnum
 
231
        self._update_base_revnum(self._update(revnum))
 
232
        return self.base_revnum - orig_revnum
 
233
 
 
234
    def remove(self, files, verbose=False, to_file=None, keep_files=True, 
 
235
               force=False):
 
236
        """Remove files from the working tree."""
153
237
        # FIXME: Use to_file argument
154
238
        # FIXME: Use verbose argument
155
239
        assert isinstance(files, list)
156
240
        wc = self._get_wc(write_lock=True)
157
241
        try:
158
242
            for file in files:
159
 
                svn.wc.delete2(self.abspath(file), wc, None, None, None)
 
243
                wc.delete(self.abspath(file).encode("utf-8"))
160
244
        finally:
161
 
            svn.wc.adm_close(wc)
 
245
            wc.close()
162
246
 
163
247
        for file in files:
164
248
            self._change_fileid_mapping(None, file)
165
249
        self.read_working_inventory()
166
250
 
167
 
    def _get_wc(self, relpath="", write_lock=False):
168
 
        return svn.wc.adm_open3(None, self.abspath(relpath).rstrip("/"), 
169
 
                                write_lock, 0, None)
 
251
    @convert_svn_error
 
252
    def _get_wc(self, relpath="", write_lock=False, depth=0, base=None):
 
253
        """Open a working copy handle."""
 
254
        return WorkingCopy(base, 
 
255
            self.abspath(relpath).rstrip("/").encode("utf-8"), 
 
256
            write_lock, depth)
170
257
 
171
258
    def _get_rel_wc(self, relpath, write_lock=False):
172
259
        dir = os.path.dirname(relpath)
174
261
        return (self._get_wc(dir, write_lock), file)
175
262
 
176
263
    def move(self, from_paths, to_dir=None, after=False, **kwargs):
 
264
        """Move files to a new location."""
177
265
        # FIXME: Use after argument
178
 
        assert after != True
179
 
        revt = svn.core.svn_opt_revision_t()
180
 
        revt.kind = svn.core.svn_opt_revision_working
 
266
        if after:
 
267
            raise NotImplementedError("move after not supported")
181
268
        for entry in from_paths:
182
 
            try:
183
 
                to_wc = self._get_wc(to_dir, write_lock=True)
184
 
                svn.wc.copy(self.abspath(entry), to_wc, 
185
 
                            os.path.basename(entry), None, None)
186
 
            finally:
187
 
                svn.wc.adm_close(to_wc)
188
 
            try:
189
 
                from_wc = self._get_wc(write_lock=True)
190
 
                svn.wc.delete2(self.abspath(entry), from_wc, None, None, None)
191
 
            finally:
192
 
                svn.wc.adm_close(from_wc)
 
269
            to_wc = self._get_wc(to_dir, write_lock=True)
 
270
            try:
 
271
                to_wc.copy(self.abspath(entry).encode("utf-8"), os.path.basename(entry))
 
272
            finally:
 
273
                to_wc.close()
 
274
            from_wc = self._get_wc(write_lock=True)
 
275
            try:
 
276
                from_wc.delete(self.abspath(entry).encode("utf-8"))
 
277
            finally:
 
278
                from_wc.close()
193
279
            new_name = urlutils.join(to_dir, os.path.basename(entry))
194
280
            self._change_fileid_mapping(self.inventory.path2id(entry), new_name)
195
281
            self._change_fileid_mapping(None, entry)
198
284
 
199
285
    def rename_one(self, from_rel, to_rel, after=False):
200
286
        # FIXME: Use after
201
 
        assert after != True
202
 
        revt = svn.core.svn_opt_revision_t()
203
 
        revt.kind = svn.core.svn_opt_revision_unspecified
 
287
        if after:
 
288
            raise NotImplementedError("rename_one after not supported")
 
289
        from_wc = None
204
290
        (to_wc, to_file) = self._get_rel_wc(to_rel, write_lock=True)
205
 
        if os.path.dirname(from_rel) == os.path.dirname(to_rel):
206
 
            # Prevent lock contention
207
 
            from_wc = to_wc
208
 
        else:
209
 
            (from_wc, _) = self._get_rel_wc(from_rel, write_lock=True)
210
 
        from_id = self.inventory.path2id(from_rel)
211
291
        try:
212
 
            svn.wc.copy(self.abspath(from_rel), to_wc, to_file, None, None)
213
 
            svn.wc.delete2(self.abspath(from_rel), from_wc, None, None, None)
 
292
            if os.path.dirname(from_rel) == os.path.dirname(to_rel):
 
293
                # Prevent lock contention
 
294
                from_wc = to_wc
 
295
            else:
 
296
                (from_wc, _) = self._get_rel_wc(from_rel, write_lock=True)
 
297
            try:
 
298
                from_id = self.inventory.path2id(from_rel)
 
299
                to_wc.copy(self.abspath(from_rel).encode("utf-8"), to_file)
 
300
                from_wc.delete(self.abspath(from_rel).encode("utf-8"))
 
301
            finally:
 
302
                from_wc.close()
214
303
        finally:
215
 
            svn.wc.adm_close(to_wc)
 
304
            if from_wc != to_wc:
 
305
                to_wc.close()
216
306
        self._change_fileid_mapping(None, from_rel)
217
307
        self._change_fileid_mapping(from_id, to_rel)
218
308
        self.read_working_inventory()
228
318
        assert isinstance(path, str)
229
319
 
230
320
        rp = self.branch.unprefix(path)
231
 
        entry = self.base_tree.id_map[rp]
 
321
        entry = idmap_lookup(self.basis_tree().id_map, self.basis_tree().workingtree.branch.mapping, rp.decode("utf-8"))
232
322
        assert entry[0] is not None
233
323
        assert isinstance(entry[0], str), "fileid %r for %r is not a string" % (entry[0], path)
234
 
        return entry
 
324
        return entry[:2]
235
325
 
236
326
    def read_working_inventory(self):
 
327
        """'Read' the working inventory.
 
328
 
 
329
        """
237
330
        inv = Inventory()
238
331
 
239
332
        def add_file_to_inv(relpath, id, revid, parent_id):
240
 
            """Add a file to the inventory."""
 
333
            """Add a file to the inventory.
 
334
            
 
335
            :param relpath: Path relative to working tree root
 
336
            :param id: File id of current directory
 
337
            :param revid: Revision id
 
338
            :param parent_id: File id of parent directory
 
339
            """
241
340
            assert isinstance(relpath, unicode)
242
341
            if os.path.islink(self.abspath(relpath)):
243
342
                file = InventoryLink(id, os.path.basename(relpath), parent_id)
244
343
                file.revision = revid
245
 
                file.symlink_target = os.readlink(self.abspath(relpath))
 
344
                file.symlink_target = os.readlink(self.abspath(relpath).encode(osutils._fs_enc))
246
345
                file.text_sha1 = None
247
346
                file.text_size = None
248
347
                file.executable = False
251
350
                file = InventoryFile(id, os.path.basename(relpath), parent_id)
252
351
                file.revision = revid
253
352
                try:
254
 
                    data = fingerprint_file(open(self.abspath(relpath)))
 
353
                    data = osutils.fingerprint_file(open(self.abspath(relpath)))
255
354
                    file.text_sha1 = data['sha1']
256
355
                    file.text_size = data['size']
257
356
                    file.executable = self.is_executable(id, relpath)
261
360
                    pass
262
361
 
263
362
        def find_copies(url, relpath=""):
 
363
            """Find copies of the specified path
 
364
 
 
365
            :param url: URL of which to find copies
 
366
            :param relpath: Optional subpath to search in
 
367
            :return: Yields all copies
 
368
            """
 
369
            ret = []
264
370
            wc = self._get_wc(relpath)
265
 
            entries = svn.wc.entries_read(wc, False)
266
 
            for entry in entries.values():
267
 
                subrelpath = os.path.join(relpath, entry.name)
268
 
                if entry.name == "" or entry.kind != 'directory':
269
 
                    if ((entry.copyfrom_url == url or entry.url == url) and 
270
 
                        not (entry.schedule in (svn.wc.schedule_delete,
271
 
                                                svn.wc.schedule_replace))):
272
 
                        yield os.path.join(
273
 
                                self.branch.get_branch_path().strip("/"), 
274
 
                                subrelpath)
275
 
                else:
276
 
                    find_copies(subrelpath)
277
 
            svn.wc.adm_close(wc)
 
371
            try:
 
372
                entries = wc.entries_read(False)
 
373
                for entry in entries.values():
 
374
                    subrelpath = os.path.join(relpath, entry.name)
 
375
                    if entry.name == "" or entry.kind != 'directory':
 
376
                        if ((entry.copyfrom_url == url or entry.url == url) and 
 
377
                            not (entry.schedule in (SCHEDULE_DELETE,
 
378
                                                    SCHEDULE_REPLACE))):
 
379
                            ret.append(os.path.join(
 
380
                                    self.branch.get_branch_path().strip("/"), 
 
381
                                    subrelpath))
 
382
                    else:
 
383
                        find_copies(subrelpath)
 
384
            finally:
 
385
                wc.close()
 
386
            return ret
278
387
 
279
388
        def find_ids(entry, rootwc):
280
389
            relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
281
 
            assert entry.schedule in (svn.wc.schedule_normal, 
282
 
                                      svn.wc.schedule_delete,
283
 
                                      svn.wc.schedule_add,
284
 
                                      svn.wc.schedule_replace)
285
 
            if entry.schedule == svn.wc.schedule_normal:
 
390
            assert entry.schedule in (SCHEDULE_NORMAL, 
 
391
                                      SCHEDULE_DELETE,
 
392
                                      SCHEDULE_ADD,
 
393
                                      SCHEDULE_REPLACE)
 
394
            if entry.schedule == SCHEDULE_NORMAL:
286
395
                assert entry.revision >= 0
287
396
                # Keep old id
288
397
                return self.path_to_file_id(entry.cmt_rev, entry.revision, 
289
398
                        relpath)
290
 
            elif entry.schedule == svn.wc.schedule_delete:
 
399
            elif entry.schedule == SCHEDULE_DELETE:
291
400
                return (None, None)
292
 
            elif (entry.schedule == svn.wc.schedule_add or 
293
 
                  entry.schedule == svn.wc.schedule_replace):
 
401
            elif (entry.schedule == SCHEDULE_ADD or 
 
402
                  entry.schedule == SCHEDULE_REPLACE):
294
403
                # See if the file this file was copied from disappeared
295
404
                # and has no other copies -> in that case, take id of other file
296
405
                if (entry.copyfrom_url and 
305
414
 
306
415
        def add_dir_to_inv(relpath, wc, parent_id):
307
416
            assert isinstance(relpath, unicode)
308
 
            entries = svn.wc.entries_read(wc, False)
 
417
            entries = wc.entries_read(False)
309
418
            entry = entries[""]
310
419
            assert parent_id is None or isinstance(parent_id, str), \
311
420
                    "%r is not a string" % parent_id
312
421
            (id, revid) = find_ids(entry, rootwc)
313
422
            if id is None:
314
 
                mutter('no id for %r' % entry.url)
 
423
                mutter('no id for %r', entry.url)
315
424
                return
316
425
            assert revid is None or isinstance(revid, str), "%r is not a string" % revid
317
426
            assert isinstance(id, str), "%r is not a string" % id
318
427
 
319
428
            # First handle directory itself
320
 
            inv.add_path(relpath.decode("utf-8"), 'directory', id, parent_id).revision = revid
321
 
            if relpath == "":
 
429
            inv.add_path(relpath, 'directory', id, parent_id).revision = revid
 
430
            if relpath == u"":
322
431
                inv.revision_id = revid
323
432
 
324
433
            for name in entries:
330
439
                entry = entries[name]
331
440
                assert entry
332
441
                
333
 
                if entry.kind == svn.core.svn_node_dir:
334
 
                    subwc = svn.wc.adm_open3(wc, self.abspath(subrelpath), 
335
 
                                             False, 0, None)
 
442
                if entry.kind == subvertpy.NODE_DIR:
 
443
                    subwc = self._get_wc(subrelpath, base=wc)
336
444
                    try:
 
445
                        assert isinstance(subrelpath, unicode)
337
446
                        add_dir_to_inv(subrelpath, subwc, id)
338
447
                    finally:
339
 
                        svn.wc.adm_close(subwc)
 
448
                        subwc.close()
340
449
                else:
341
450
                    (subid, subrevid) = find_ids(entry, rootwc)
342
451
                    if subid:
 
452
                        assert isinstance(subrelpath, unicode)
343
453
                        add_file_to_inv(subrelpath, subid, subrevid, id)
344
454
                    else:
345
 
                        mutter('no id for %r' % entry.url)
 
455
                        mutter('no id for %r', entry.url)
346
456
 
347
457
        rootwc = self._get_wc() 
348
458
        try:
349
459
            add_dir_to_inv(u"", rootwc, None)
350
460
        finally:
351
 
            svn.wc.adm_close(rootwc)
 
461
            rootwc.close()
352
462
 
353
463
        self._set_inventory(inv, dirty=False)
354
464
        return inv
355
465
 
356
466
    def set_last_revision(self, revid):
357
 
        mutter('setting last revision to %r' % revid)
 
467
        mutter('setting last revision to %r', revid)
358
468
        if revid is None or revid == NULL_REVISION:
359
469
            self.base_revid = revid
360
470
            self.base_revnum = 0
361
 
            self.base_tree = RevisionTree(self, Inventory(), revid)
 
471
            self.base_tree = None
362
472
            return
363
473
 
364
474
        rev = self.branch.lookup_revision_id(revid)
365
475
        self.base_revnum = rev
366
476
        self.base_revid = revid
367
 
        self.base_tree = SvnBasisTree(self)
368
 
 
369
 
        # TODO: Implement more efficient version
370
 
        newrev = self.branch.repository.get_revision(revid)
371
 
        newrevtree = self.branch.repository.revision_tree(revid)
372
 
 
373
 
        def update_settings(wc, path):
374
 
            id = newrevtree.inventory.path2id(path)
375
 
            mutter("Updating settings for %r" % id)
376
 
            revnum = self.branch.lookup_revision_id(
377
 
                    newrevtree.inventory[id].revision)
378
 
 
379
 
            svn.wc.process_committed2(self.abspath(path).rstrip("/"), wc, 
380
 
                          False, revnum, 
381
 
                          svn.core.svn_time_to_cstring(newrev.timestamp), 
382
 
                          newrev.committer, None, False)
383
 
 
384
 
            if newrevtree.inventory[id].kind != 'directory':
385
 
                return
386
 
 
387
 
            entries = svn.wc.entries_read(wc, True)
388
 
            for entry in entries:
389
 
                if entry == "":
390
 
                    continue
391
 
 
392
 
                subwc = svn.wc.adm_open3(wc, os.path.join(self.basedir, path, entry), False, 0, None)
 
477
        self.base_tree = None
 
478
 
 
479
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
 
480
        """See MutableTree.set_parent_trees."""
 
481
        self.set_parent_ids([rev for (rev, tree) in parents_list])
 
482
 
 
483
    def set_parent_ids(self, parent_ids):
 
484
        """See MutableTree.set_parent_ids."""
 
485
        super(SvnWorkingTree, self).set_parent_ids(parent_ids)
 
486
        if parent_ids == [] or parent_ids[0] == NULL_REVISION:
 
487
            merges = []
 
488
        else:
 
489
            merges = parent_ids[1:]
 
490
        adm = self._get_wc(write_lock=True)
 
491
        try:
 
492
            svk_merges = svk.parse_svk_features(self._get_svk_merges(self._get_base_branch_props()))
 
493
 
 
494
            # Set svk:merge
 
495
            for merge in merges:
393
496
                try:
394
 
                    update_settings(subwc, os.path.join(path, entry))
395
 
                finally:
396
 
                    svn.wc.adm_close(subwc)
397
 
 
398
 
        # Set proper version for all files in the wc
399
 
        wc = self._get_wc(write_lock=True)
400
 
        try:
401
 
            update_settings(wc, "")
402
 
        finally:
403
 
            svn.wc.adm_close(wc)
404
 
        self.base_revid = revid
405
 
 
406
 
    def commit(self, message=None, message_callback=None, revprops=None, 
407
 
               timestamp=None, timezone=None, committer=None, rev_id=None, 
408
 
               allow_pointless=True, strict=False, verbose=False, local=False, 
409
 
               reporter=None, config=None, specific_files=None, author=None):
410
 
        if author is not None:
411
 
            revprops['author'] = author
412
 
        # FIXME: Use allow_pointless
413
 
        # FIXME: Use verbose
414
 
        # FIXME: Use reporter
415
 
        # FIXME: Use strict
416
 
        if local:
417
 
            raise LocalCommitsUnsupported()
418
 
 
419
 
        if specific_files:
420
 
            specific_files = [self.abspath(x).encode('utf8') for x in specific_files]
421
 
        else:
422
 
            specific_files = [self.basedir.encode('utf8')]
423
 
 
424
 
        if message_callback is not None:
425
 
            def log_message_func(items, pool):
426
 
                """ Simple log message provider for unit tests. """
427
 
                return message_callback(self).encode("utf-8")
428
 
        else:
429
 
            assert isinstance(message, basestring)
430
 
            def log_message_func(items, pool):
431
 
                """ Simple log message provider for unit tests. """
432
 
                return message.encode("utf-8")
433
 
 
434
 
        self.client_ctx.log_msg_baton2 = log_message_func
435
 
        if rev_id is not None:
436
 
            extra = "%d %s\n" % (self.branch.revno()+1, rev_id)
437
 
        else:
438
 
            extra = ""
439
 
        wc = self._get_wc(write_lock=True)
440
 
        try:
441
 
            svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.mapping.scheme), 
442
 
                             self._get_bzr_revids(self._get_base_branch_props()) + extra,
443
 
                             self.basedir, wc)
444
 
            svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO, 
445
 
                             generate_revision_metadata(timestamp, 
446
 
                                                        timezone, 
447
 
                                                        committer,
448
 
                                                        revprops),
449
 
                             self.basedir, wc)
450
 
        finally:
451
 
            svn.wc.adm_close(wc)
452
 
 
453
 
        try:
454
 
            try:
455
 
                commit_info = svn.client.commit3(specific_files, True, False, 
456
 
                                                 self.client_ctx)
457
 
            except SubversionException, (_, num):
458
 
                if num == svn.core.SVN_ERR_FS_TXN_OUT_OF_DATE:
459
 
                    raise OutOfDateTree(self)
460
 
                raise
461
 
        except:
462
 
            # Reset properties so the next subversion commit won't 
463
 
            # accidently set these properties.
464
 
            wc = self._get_wc(write_lock=True)
465
 
            base_branch_props = self._get_base_branch_props()
466
 
            svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.mapping.scheme), 
467
 
                             self._get_bzr_revids(base_branch_props), self.basedir, wc)
468
 
            svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO, 
469
 
                              base_branch_props.get(SVN_PROP_BZR_REVISION_INFO, ""),
470
 
                              self.basedir, wc)
471
 
            svn.wc.adm_close(wc)
472
 
            raise
473
 
 
474
 
        self.client_ctx.log_msg_baton2 = None
475
 
 
476
 
        revid = self.branch.generate_revision_id(commit_info.revision)
477
 
 
478
 
        self.base_revid = revid
479
 
        self.base_revnum = commit_info.revision
480
 
        self.base_tree = SvnBasisTree(self)
481
 
 
482
 
        return revid
 
497
                    svk_merges.add(_revision_id_to_svk_feature(merge,
 
498
                        self.branch.repository.lookup_revision_id))
 
499
                except NoSuchRevision:
 
500
                    pass
 
501
 
 
502
            adm.prop_set(svk.SVN_PROP_SVK_MERGE, 
 
503
                         svk.serialize_svk_features(svk_merges), self.basedir)
 
504
        finally:
 
505
            adm.close()
483
506
 
484
507
    def smart_add(self, file_list, recurse=True, action=None, save=True):
 
508
        """See MutableTree.smart_add()."""
485
509
        assert isinstance(recurse, bool)
486
510
        if action is None:
487
511
            action = bzrlib.add.AddAction()
489
513
        if not file_list:
490
514
            # no paths supplied: add the entire tree.
491
515
            file_list = [u'.']
492
 
        ignored = {}
 
516
        ignored = defaultdict(list)
493
517
        added = []
494
518
 
495
519
        for file_path in file_list:
500
524
            try:
501
525
                if not self.inventory.has_filename(f):
502
526
                    if save:
503
 
                        mutter('adding %r' % file_path)
504
 
                        svn.wc.add2(file_path, wc, None, 0, None, None, None)
 
527
                        mutter('adding %r', file_path)
 
528
                        wc.add(file_path)
505
529
                    added.append(file_path)
506
 
                if recurse and file_kind(file_path) == 'directory':
 
530
                if recurse and osutils.file_kind(file_path) == 'directory':
507
531
                    # Filter out ignored files and update ignored
508
532
                    for c in os.listdir(file_path):
509
533
                        if self.is_control_filename(c):
511
535
                        c_path = os.path.join(file_path, c)
512
536
                        ignore_glob = self.is_ignored(c)
513
537
                        if ignore_glob is not None:
514
 
                            ignored.setdefault(ignore_glob, []).append(c_path)
 
538
                            ignored[ignore_glob].append(c_path)
515
539
                        todo.append(c_path)
516
540
            finally:
517
 
                svn.wc.adm_close(wc)
 
541
                wc.close()
518
542
            if todo != []:
519
543
                cadded, cignored = self.smart_add(todo, recurse, action, save)
520
544
                added.extend(cadded)
522
546
        return added, ignored
523
547
 
524
548
    def add(self, files, ids=None, kinds=None):
 
549
        """Add files to the working tree."""
525
550
        # TODO: Use kinds
526
551
        if isinstance(files, str):
527
552
            files = [files]
534
559
            wc = self._get_wc(os.path.dirname(f), write_lock=True)
535
560
            try:
536
561
                try:
537
 
                    svn.wc.add2(os.path.join(self.basedir, f), wc, None, 0, 
538
 
                            None, None, None)
 
562
                    wc.add(self.abspath(f).encode("utf-8"))
539
563
                    if ids is not None:
540
564
                        self._change_fileid_mapping(ids.next(), f, wc)
541
 
                except SubversionException, (_, num):
542
 
                    if num == svn.core.SVN_ERR_ENTRY_EXISTS:
 
565
                except subvertpy.SubversionException, (_, num):
 
566
                    if num == subvertpy.ERR_ENTRY_EXISTS:
543
567
                        continue
544
 
                    elif num == svn.core.SVN_ERR_WC_PATH_NOT_FOUND:
 
568
                    elif num == subvertpy.ERR_WC_PATH_NOT_FOUND:
545
569
                        raise NoSuchFile(path=f)
546
570
                    raise
547
571
            finally:
548
 
                svn.wc.adm_close(wc)
 
572
                wc.close()
549
573
        self.read_working_inventory()
550
574
 
551
575
    def basis_tree(self):
 
576
        """Return the basis tree for a working tree."""
552
577
        if self.base_revid is None or self.base_revid == NULL_REVISION:
553
578
            return self.branch.repository.revision_tree(self.base_revid)
554
579
 
 
580
        if self.base_tree is None:
 
581
            self.base_tree = SvnBasisTree(self)
 
582
 
555
583
        return self.base_tree
556
584
 
 
585
    def _update_base_revnum(self, fetched):
 
586
        self.base_revnum = fetched
 
587
        self.base_revid = self.branch.generate_revision_id(fetched)
 
588
        self.base_tree = None
 
589
        self.read_working_inventory()
 
590
 
557
591
    def pull(self, source, overwrite=False, stop_revision=None, 
558
592
             delta_reporter=None, possible_transports=None):
 
593
        """Pull in changes from another branch into this working tree."""
559
594
        # FIXME: Use delta_reporter
 
595
        # FIXME: Use source
560
596
        # FIXME: Use overwrite
561
 
        result = PullResult()
562
 
        result.source_branch = source
563
 
        result.master_branch = None
564
 
        result.target_branch = self.branch
565
 
        (result.old_revno, result.old_revid) = self.branch.last_revision_info()
566
 
        if stop_revision is None:
567
 
            stop_revision = self.branch.last_revision()
568
 
        rev = svn.core.svn_opt_revision_t()
569
 
        rev.kind = svn.core.svn_opt_revision_number
570
 
        rev.value.number = self.branch.lookup_revision_id(stop_revision)
571
 
        fetched = svn.client.update(self.basedir, rev, True, self.client_ctx)
572
 
        self.base_revid = self.branch.generate_revision_id(fetched)
573
 
        result.new_revid = self.base_revid
574
 
        result.new_revno = self.branch.revision_id_to_revno(result.new_revid)
 
597
        result = self.branch.pull(source, overwrite=overwrite, stop_revision=stop_revision)
 
598
        fetched = self._update(self.branch.get_revnum())
 
599
        self._update_base_revnum(fetched)
575
600
        return result
576
601
 
 
602
    def get_file_properties(self, file_id, path=None):
 
603
        if not path:
 
604
            path = self._inventory.id2path(file_id)
 
605
        wc = self._get_wc()
 
606
        try:
 
607
            (prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
 
608
        finally:
 
609
            wc.close()
 
610
        return apply_prop_changes(orig_props, prop_changes)
 
611
 
577
612
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
613
        """Determine the SHA1 for a file."""
578
614
        if not path:
579
615
            path = self._inventory.id2path(file_id)
580
 
        return fingerprint_file(open(self.abspath(path)))['sha1']
 
616
        return osutils.fingerprint_file(open(self.abspath(path)))['sha1']
581
617
 
582
618
    def _change_fileid_mapping(self, id, path, wc=None):
583
619
        if wc is None:
584
620
            subwc = self._get_wc(write_lock=True)
585
621
        else:
586
622
            subwc = wc
587
 
        new_entries = self._get_new_file_ids(subwc)
588
 
        if id is None:
589
 
            if new_entries.has_key(path):
590
 
                del new_entries[path]
591
 
        else:
592
 
            assert isinstance(id, str)
593
 
            new_entries[path] = id
594
 
        existing = "".join(map(lambda (path, id): "%s\t%s\n" % (path, id), new_entries.items()))
595
 
        if existing != "":
596
 
            svn.wc.prop_set(SVN_PROP_BZR_FILEIDS, existing.encode("utf-8"), self.basedir, subwc)
597
 
        if wc is None:
598
 
            svn.wc.adm_close(subwc)
 
623
        try:
 
624
            new_entries = self._get_new_file_ids(subwc)
 
625
            if id is None:
 
626
                if new_entries.has_key(path):
 
627
                    del new_entries[path]
 
628
            else:
 
629
                assert isinstance(id, str)
 
630
                new_entries[path] = id
 
631
            fileprops = self._get_branch_props()
 
632
            self.branch.mapping.export_fileid_map_fileprops(new_entries, fileprops)
 
633
            self._set_branch_props(subwc, fileprops)
 
634
        finally:
 
635
            if wc is None:
 
636
                subwc.close()
 
637
 
 
638
    def _get_changed_branch_props(self):
 
639
        wc = self._get_wc()
 
640
        try:
 
641
            ret = {}
 
642
            (prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
 
643
            for k,v in prop_changes:
 
644
                ret[k] = (orig_props.get(k), v)
 
645
            return ret
 
646
        finally:
 
647
            wc.close()
 
648
 
 
649
    def _get_branch_props(self):
 
650
        wc = self._get_wc()
 
651
        try:
 
652
            (prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
 
653
            return apply_prop_changes(orig_props, prop_changes)
 
654
        finally:
 
655
            wc.close()
 
656
 
 
657
    def _set_branch_props(self, wc, fileprops):
 
658
        for k,v in fileprops.iteritems():
 
659
            wc.prop_set(k, v, self.basedir)
599
660
 
600
661
    def _get_base_branch_props(self):
601
 
        return self.branch.repository.branchprop_list.get_properties(
602
 
                self.branch.get_branch_path(self.base_revnum), self.base_revnum)
 
662
        wc = self._get_wc()
 
663
        try:
 
664
            (prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
 
665
            return orig_props
 
666
        finally:
 
667
            wc.close()
603
668
 
604
669
    def _get_new_file_ids(self, wc):
605
 
        committed = self._get_base_branch_props().get(SVN_PROP_BZR_FILEIDS, "")
606
 
        existing = svn.wc.prop_get(SVN_PROP_BZR_FILEIDS, self.basedir, wc)
607
 
        if existing is None or committed == existing:
608
 
            return {}
609
 
        return dict(map(lambda x: str(x).split("\t"), 
610
 
            existing.splitlines()))
611
 
 
612
 
    def _get_bzr_revids(self, base_branch_props):
613
 
        return base_branch_props.get(SVN_PROP_BZR_REVISION_ID+str(self.branch.mapping.scheme), "")
614
 
 
615
 
    def _get_bzr_merges(self, base_branch_props):
616
 
        return base_branch_props.get(SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), "")
 
670
        return self.branch.mapping.import_fileid_map_fileprops(
 
671
            self._get_changed_branch_props())
617
672
 
618
673
    def _get_svk_merges(self, base_branch_props):
619
 
        return base_branch_props.get(SVN_PROP_SVK_MERGE, "")
620
 
 
621
 
    def set_pending_merges(self, merges):
622
 
        """See MutableTree.set_pending_merges()."""
623
 
        wc = self._get_wc(write_lock=True)
624
 
        try:
625
 
            # Set bzr:merge
626
 
            if len(merges) > 0:
627
 
                bzr_merge = "\t".join(merges) + "\n"
628
 
            else:
629
 
                bzr_merge = ""
630
 
 
631
 
            svn.wc.prop_set(SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), 
632
 
                                 self._get_bzr_merges(self._get_base_branch_props()) + bzr_merge, 
633
 
                                 self.basedir, wc)
634
 
            
635
 
            svk_merges = parse_svk_features(self._get_svk_merges(self._get_base_branch_props()))
636
 
 
637
 
            # Set svk:merge
638
 
            for merge in merges:
639
 
                try:
640
 
                    svk_merges.add(_revision_id_to_svk_feature(merge))
641
 
                except InvalidRevisionId:
642
 
                    pass
643
 
 
644
 
            svn.wc.prop_set2(SVN_PROP_SVK_MERGE, 
645
 
                             serialize_svk_features(svk_merges), self.basedir, 
646
 
                             wc, False)
647
 
        finally:
648
 
            svn.wc.adm_close(wc)
649
 
 
650
 
    def add_pending_merge(self, revid):
651
 
        merges = self.pending_merges()
652
 
        merges.append(revid)
653
 
        self.set_pending_merges(merges)
654
 
 
655
 
    def pending_merges(self):
656
 
        merged = self._get_bzr_merges(self._get_base_branch_props()).splitlines()
657
 
        wc = self._get_wc()
658
 
        try:
659
 
            merged_data = svn.wc.prop_get(
660
 
                SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), self.basedir, wc)
661
 
            if merged_data is None:
662
 
                set_merged = []
663
 
            else:
664
 
                set_merged = merged_data.splitlines()
665
 
        finally:
666
 
            svn.wc.adm_close(wc)
667
 
 
668
 
        assert (len(merged) == len(set_merged) or 
669
 
               len(merged)+1 == len(set_merged))
670
 
 
671
 
        if len(set_merged) > len(merged):
672
 
            return set_merged[-1].split("\t")
673
 
 
674
 
        return []
 
674
        return base_branch_props.get(svk.SVN_PROP_SVK_MERGE, "")
 
675
 
 
676
    def apply_inventory_delta(self, delta):
 
677
        assert delta == []
 
678
 
 
679
    def _last_revision(self):
 
680
        return self.base_revid
 
681
 
 
682
    def path_content_summary(self, path, _lstat=os.lstat,
 
683
        _mapper=osutils.file_kind_from_stat_mode):
 
684
        """See Tree.path_content_summary."""
 
685
        abspath = self.abspath(path)
 
686
        try:
 
687
            stat_result = _lstat(abspath)
 
688
        except OSError, e:
 
689
            if getattr(e, 'errno', None) == errno.ENOENT:
 
690
                # no file.
 
691
                return ('missing', None, None, None)
 
692
            # propagate other errors
 
693
            raise
 
694
        kind = _mapper(stat_result.st_mode)
 
695
        if kind == 'file':
 
696
            size = stat_result.st_size
 
697
            # try for a stat cache lookup
 
698
            executable = self._is_executable_from_path_and_stat(path, stat_result)
 
699
            return (kind, size, executable, self._sha_from_stat(
 
700
                path, stat_result))
 
701
        elif kind == 'directory':
 
702
            return kind, None, None, None
 
703
        elif kind == 'symlink':
 
704
            return ('symlink', None, None, os.readlink(abspath.encode(osutils._fs_enc)))
 
705
        else:
 
706
            return (kind, None, None, None)
 
707
 
 
708
    def _get_base_revmeta(self):
 
709
        return self.branch.repository._revmeta_provider.lookup_revision(self.branch.get_branch_path(self.base_revnum), self.base_revnum)
675
710
 
676
711
    def _reset_data(self):
677
712
        pass
678
713
 
 
714
    def break_lock(self):
 
715
        """Break a lock if one is present from another instance.
 
716
 
 
717
        Uses the ui factory to ask for confirmation if the lock may be from
 
718
        an active process.
 
719
 
 
720
        This will probe the repository for its lock as well.
 
721
        """
 
722
        if getattr(subvertpy.wc, "cleanup", None) is not None:
 
723
            subvertpy.wc.cleanup(self.basedir)
 
724
        self._control_files.break_lock()
 
725
 
679
726
    def unlock(self):
680
727
        # non-implementation specific cleanup
681
728
        self._cleanup()
686
733
        finally:
687
734
            self.branch.unlock()
688
735
 
689
 
    if not supports_executable():
 
736
    if not osutils.supports_executable():
690
737
        def is_executable(self, file_id, path=None):
691
738
            inv = self.basis_tree()._inventory
692
739
            if file_id in inv:
694
741
            # Default to not executable
695
742
            return False
696
743
 
 
744
    def update_basis_by_delta(self, new_revid, delta):
 
745
        """Update the parents of this tree after a commit.
 
746
 
 
747
        This gives the tree one parent, with revision id new_revid. The
 
748
        inventory delta is applied to the current basis tree to generate the
 
749
        inventory for the parent new_revid, and all other parent trees are
 
750
        discarded.
 
751
 
 
752
        All the changes in the delta should be changes synchronising the basis
 
753
        tree with some or all of the working tree, with a change to a directory
 
754
        requiring that its contents have been recursively included. That is,
 
755
        this is not a general purpose tree modification routine, but a helper
 
756
        for commit which is not required to handle situations that do not arise
 
757
        outside of commit.
 
758
 
 
759
        :param new_revid: The new revision id for the trees parent.
 
760
        :param delta: An inventory delta (see apply_inventory_delta) describing
 
761
            the changes from the current left most parent revision to new_revid.
 
762
        """
 
763
        rev = self.branch.lookup_revision_id(new_revid)
 
764
        self.base_revnum = rev
 
765
        self.base_revid = new_revid
 
766
        self.base_tree = None
 
767
 
 
768
        # TODO: Implement more efficient version
 
769
        newrev = self.branch.repository.get_revision(new_revid)
 
770
        newrevtree = self.branch.repository.revision_tree(new_revid)
 
771
        svn_revprops = self.branch.repository._log.revprop_list(rev)
 
772
 
 
773
        def process_committed(adm, relpath, revid, svn_revprops):
 
774
            mutter("process %r -> %r", relpath, revid)
 
775
            abspath = self.abspath(relpath).encode("utf-8").rstrip("/")
 
776
            adm.process_committed(abspath,
 
777
                False, self.branch.lookup_revision_id(revid),
 
778
                svn_revprops[properties.PROP_REVISION_DATE], 
 
779
                svn_revprops.get(properties.PROP_REVISION_AUTHOR, ""))
 
780
            mutter("doneprocess %r -> %r", relpath, revid)
 
781
 
 
782
        def update_settings(adm, path, id):
 
783
            if newrevtree.inventory[id].kind != 'directory':
 
784
                return
 
785
 
 
786
            entries = adm.entries_read(False)
 
787
            for name, entry in entries.iteritems():
 
788
                if name == "":
 
789
                    process_committed(adm, path, 
 
790
                                  newrevtree.inventory[id].revision,
 
791
                                  svn_revprops)
 
792
                    continue
 
793
 
 
794
                child_path = os.path.join(path, name.decode("utf-8"))
 
795
 
 
796
                child_id = newrevtree.inventory.path2id(child_path)
 
797
 
 
798
                child_abspath = self.abspath(child_path).encode("utf-8").rstrip("/")
 
799
 
 
800
                if not child_id in newrevtree.inventory:
 
801
                    pass
 
802
                elif newrevtree.inventory[child_id].kind == 'directory':
 
803
                    subwc = self._get_wc(child_path, write_lock=True, base=adm)
 
804
                    try:
 
805
                        update_settings(subwc, child_path, child_id)
 
806
                    finally:
 
807
                        subwc.close()
 
808
                else:
 
809
                    pristine_abspath = get_pristine_copy_path(child_abspath)
 
810
                    if os.path.exists(pristine_abspath):
 
811
                        os.remove(pristine_abspath)
 
812
                    source_f = open(child_abspath, "r")
 
813
                    target_f = open(pristine_abspath, "w")
 
814
                    target_f.write(source_f.read())
 
815
                    target_f.close()
 
816
                    source_f.close()
 
817
                    process_committed(adm, child_path, 
 
818
                                  newrevtree.inventory[child_id].revision,
 
819
                                  svn_revprops)
 
820
 
 
821
        # Set proper version for all files in the wc
 
822
        adm = self._get_wc(write_lock=True)
 
823
        try:
 
824
            update_settings(adm, "", newrevtree.inventory.root.file_id)
 
825
        finally:
 
826
            adm.close()
 
827
 
 
828
        self.set_parent_ids([new_revid])
 
829
 
697
830
 
698
831
class SvnWorkingTreeFormat(WorkingTreeFormat):
699
832
    """Subversion working copy format."""
 
833
    def __init__(self, version):
 
834
        self.version = version
 
835
 
700
836
    def __get_matchingbzrdir(self):
701
837
        return SvnWorkingTreeDirFormat()
702
838
 
703
839
    _matchingbzrdir = property(__get_matchingbzrdir)
704
840
 
705
841
    def get_format_description(self):
706
 
        return "Subversion Working Copy"
 
842
        return "Subversion Working Copy Version %d" % self.version
707
843
 
708
844
    def get_format_string(self):
709
 
        return "Subversion Working Copy Format"
 
845
        raise NotImplementedError
710
846
 
711
847
    def initialize(self, a_bzrdir, revision_id=None):
712
848
        raise NotImplementedError(self.initialize)
723
859
        self.local_path = transport.local_abspath(".")
724
860
        
725
861
        # Open related remote repository + branch
726
 
        wc = svn.wc.adm_open3(None, self.local_path, False, 0, None)
727
 
        try:
728
 
            self.svn_url = svn.wc.entry(self.local_path, wc, True).url
 
862
        try:
 
863
            wc = WorkingCopy(None, self.local_path)
 
864
        except subvertpy.SubversionException, (msg, ERR_WC_UNSUPPORTED_FORMAT):
 
865
            raise UnsupportedFormatError(msg, kind='workingtree')
 
866
        try:
 
867
            self.svn_url = wc.entry(self.local_path, True).url
729
868
        finally:
730
 
            svn.wc.adm_close(wc)
 
869
            wc.close()
731
870
 
732
 
        self.remote_transport = SvnRaTransport(self.svn_url)
733
 
        self.remote_bzrdir = SvnRemoteAccess(self.remote_transport)
734
 
        self.svn_root_transport = self.remote_transport.clone_root()
 
871
        self._remote_transport = None
 
872
        self._remote_bzrdir = None
 
873
        self.svn_controldir = os.path.join(self.local_path, get_adm_dir())
735
874
        self.root_transport = self.transport = transport
 
875
 
 
876
    def backup_bzrdir(self):
 
877
        self.root_transport.copy_tree(".svn", ".svn.backup")
 
878
        return (self.root_transport.abspath(".svn"),
 
879
                self.root_transport.abspath(".svn.backup"))
 
880
 
 
881
    def is_control_filename(self, filename):
 
882
        return filename == '.svn' or filename.startswith('.svn/')
 
883
 
 
884
    def get_remote_bzrdir(self):
 
885
        if self._remote_bzrdir is None:
 
886
            self._remote_bzrdir = SvnRemoteAccess(self.get_remote_transport())
 
887
        return self._remote_bzrdir
 
888
 
 
889
    def get_remote_transport(self):
 
890
        if self._remote_transport is None:
 
891
            self._remote_transport = SvnRaTransport(self.svn_url)
 
892
        return self._remote_transport
736
893
        
737
894
    def clone(self, path, revision_id=None, force_new_repo=False):
738
895
        raise NotImplementedError(self.clone)
739
896
 
740
897
    def open_workingtree(self, _unsupported=False, recommend_upgrade=False):
741
 
        return SvnWorkingTree(self, self.local_path, self.open_branch())
 
898
        try:
 
899
            return SvnWorkingTree(self, self.local_path, self.open_branch())
 
900
        except bzrsvn_errors.NotSvnBranchPath, e:
 
901
            raise NoWorkingTree(self.local_path)
742
902
 
743
903
    def sprout(self, url, revision_id=None, force_new_repo=False, 
744
904
               recurse='down', possible_transports=None, accelerator_tree=None,
753
913
        result.create_workingtree(hardlink=hardlink)
754
914
        return result
755
915
 
 
916
    def create_repository(self, shared=False):
 
917
        raise UninitializableFormat(self._format)
 
918
 
756
919
    def open_repository(self):
757
920
        raise NoRepositoryPresent(self)
758
921
 
759
 
    def find_repository(self):
 
922
    def find_repository(self, _ignore_branch_path=False):
760
923
        raise NoRepositoryPresent(self)
761
924
 
762
925
    def _find_repository(self):
763
 
        return SvnRepository(self, self.svn_root_transport, 
764
 
                             self.remote_bzrdir.branch_path)
 
926
        return SvnRepository(self, self.get_remote_transport().clone_root(), 
 
927
                             self.get_remote_bzrdir().branch_path)
765
928
 
766
929
    def needs_format_conversion(self, format=None):
767
930
        if format is None:
768
931
            format = BzrDirFormat.get_default_format()
769
932
        return not isinstance(self._format, format.__class__)
770
933
 
 
934
    def get_workingtree_transport(self, format):
 
935
        assert format is None
 
936
        return get_transport(self.svn_controldir)
 
937
 
771
938
    def create_workingtree(self, revision_id=None, hardlink=None):
772
939
        """See BzrDir.create_workingtree().
773
940
 
785
952
        repos = self._find_repository()
786
953
 
787
954
        try:
788
 
            branch = SvnBranch(self.remote_transport.base, repos, 
789
 
                               self.remote_bzrdir.branch_path)
790
 
        except SubversionException, (_, num):
791
 
            if num == svn.core.SVN_ERR_WC_NOT_DIRECTORY:
 
955
            branch = SvnBranch(repos, self.get_remote_bzrdir().branch_path)
 
956
        except subvertpy.SubversionException, (_, num):
 
957
            if num == subvertpy.ERR_WC_NOT_DIRECTORY:
792
958
                raise NotBranchError(path=self.base)
793
959
            raise
794
960
 
795
 
        branch.bzrdir = self.remote_bzrdir
 
961
        branch.bzrdir = self.get_remote_bzrdir()
796
962
 
797
963
        return branch
798
964
 
799
965
 
 
966
class SvnCheckoutConverter(Converter):
 
967
    """Converts from a Subversion directory to a bzr dir."""
 
968
    def __init__(self, target_format):
 
969
        """Create a SvnCheckoutConverter.
 
970
        :param target_format: The format the resulting repository should be.
 
971
        """
 
972
        super(SvnCheckoutConverter, self).__init__()
 
973
        self.target_format = target_format
800
974
 
 
975
    def convert(self, to_convert, pb):
 
976
        """See Converter.convert()."""
 
977
        remote_branch = to_convert.open_branch()
 
978
        bzrdir = self.target_format.initialize(to_convert.root_transport.base)
 
979
        branch = BranchReferenceFormat().initialize(bzrdir, remote_branch)
 
980
        wt = bzrdir.create_workingtree()
 
981
        # FIXME: Convert working tree
 
982
        to_convert.root_transport.delete_tree(".svn")
 
983
        return bzrdir