/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/git/dir.py

[merge] robertc's integration, updated tests to check for retcode=3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 Canonical Ltd
2
 
# Copyright (C) 2010-2018 Jelmer Vernooij
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
 
 
18
 
"""An adapter between a Git control dir and a Bazaar ControlDir."""
19
 
 
20
 
from __future__ import absolute_import
21
 
 
22
 
from .. import (
23
 
    branch as _mod_branch,
24
 
    cleanup,
25
 
    errors as brz_errors,
26
 
    trace,
27
 
    osutils,
28
 
    urlutils,
29
 
    )
30
 
from ..sixish import (
31
 
    PY3,
32
 
    viewitems,
33
 
    )
34
 
from ..transport import (
35
 
    do_catching_redirections,
36
 
    get_transport_from_path,
37
 
    )
38
 
 
39
 
from ..controldir import (
40
 
    BranchReferenceLoop,
41
 
    ControlDir,
42
 
    ControlDirFormat,
43
 
    format_registry,
44
 
    RepositoryAcquisitionPolicy,
45
 
    )
46
 
 
47
 
from .push import (
48
 
    GitPushResult,
49
 
    )
50
 
from .transportgit import (
51
 
    OBJECTDIR,
52
 
    TransportObjectStore,
53
 
    )
54
 
 
55
 
 
56
 
class GitDirConfig(object):
57
 
 
58
 
    def get_default_stack_on(self):
59
 
        return None
60
 
 
61
 
    def set_default_stack_on(self, value):
62
 
        raise brz_errors.BzrError("Cannot set configuration")
63
 
 
64
 
 
65
 
class GitControlDirFormat(ControlDirFormat):
66
 
 
67
 
    colocated_branches = True
68
 
    fixed_components = True
69
 
 
70
 
    def __eq__(self, other):
71
 
        return type(self) == type(other)
72
 
 
73
 
    def is_supported(self):
74
 
        return True
75
 
 
76
 
    def network_name(self):
77
 
        return b"git"
78
 
 
79
 
 
80
 
class UseExistingRepository(RepositoryAcquisitionPolicy):
81
 
    """A policy of reusing an existing repository"""
82
 
 
83
 
    def __init__(self, repository, stack_on=None, stack_on_pwd=None,
84
 
                 require_stacking=False):
85
 
        """Constructor.
86
 
 
87
 
        :param repository: The repository to use.
88
 
        :param stack_on: A location to stack on
89
 
        :param stack_on_pwd: If stack_on is relative, the location it is
90
 
            relative to.
91
 
        """
92
 
        super(UseExistingRepository, self).__init__(
93
 
            stack_on, stack_on_pwd, require_stacking)
94
 
        self._repository = repository
95
 
 
96
 
    def acquire_repository(self, make_working_trees=None, shared=False,
97
 
                           possible_transports=None):
98
 
        """Implementation of RepositoryAcquisitionPolicy.acquire_repository
99
 
 
100
 
        Returns an existing repository to use.
101
 
        """
102
 
        return self._repository, False
103
 
 
104
 
 
105
 
class GitDir(ControlDir):
106
 
    """An adapter to the '.git' dir used by git."""
107
 
 
108
 
    def is_supported(self):
109
 
        return True
110
 
 
111
 
    def can_convert_format(self):
112
 
        return False
113
 
 
114
 
    def break_lock(self):
115
 
        # There are no global locks, so nothing to break.
116
 
        raise NotImplementedError(self.break_lock)
117
 
 
118
 
    def cloning_metadir(self, stacked=False):
119
 
        return format_registry.make_controldir("git")
120
 
 
121
 
    def checkout_metadir(self, stacked=False):
122
 
        return format_registry.make_controldir("git")
123
 
 
124
 
    def _get_selected_ref(self, branch, ref=None):
125
 
        if ref is not None and branch is not None:
126
 
            raise brz_errors.BzrError("can't specify both ref and branch")
127
 
        if ref is not None:
128
 
            return ref
129
 
        if branch is not None:
130
 
            from .refs import branch_name_to_ref
131
 
            return branch_name_to_ref(branch)
132
 
        segment_parameters = getattr(
133
 
            self.user_transport, "get_segment_parameters", lambda: {})()
134
 
        ref = segment_parameters.get("ref")
135
 
        if ref is not None:
136
 
            return urlutils.unquote_to_bytes(ref)
137
 
        if branch is None and getattr(self, "_get_selected_branch", False):
138
 
            branch = self._get_selected_branch()
139
 
            if branch is not None:
140
 
                from .refs import branch_name_to_ref
141
 
                return branch_name_to_ref(branch)
142
 
        return b"HEAD"
143
 
 
144
 
    def get_config(self):
145
 
        return GitDirConfig()
146
 
 
147
 
    def _available_backup_name(self, base):
148
 
        return osutils.available_backup_name(base, self.root_transport.has)
149
 
 
150
 
    def sprout(self, url, revision_id=None, force_new_repo=False,
151
 
               recurse='down', possible_transports=None,
152
 
               accelerator_tree=None, hardlink=False, stacked=False,
153
 
               source_branch=None, create_tree_if_local=True):
154
 
        from ..repository import InterRepository
155
 
        from ..transport.local import LocalTransport
156
 
        from ..transport import get_transport
157
 
        target_transport = get_transport(url, possible_transports)
158
 
        target_transport.ensure_base()
159
 
        cloning_format = self.cloning_metadir()
160
 
        # Create/update the result branch
161
 
        try:
162
 
            result = ControlDir.open_from_transport(target_transport)
163
 
        except brz_errors.NotBranchError:
164
 
            result = cloning_format.initialize_on_transport(target_transport)
165
 
        source_branch = self.open_branch()
166
 
        source_repository = self.find_repository()
167
 
        try:
168
 
            result_repo = result.find_repository()
169
 
        except brz_errors.NoRepositoryPresent:
170
 
            result_repo = result.create_repository()
171
 
        if stacked:
172
 
            raise _mod_branch.UnstackableBranchFormat(
173
 
                self._format, self.user_url)
174
 
        interrepo = InterRepository.get(source_repository, result_repo)
175
 
 
176
 
        if revision_id is not None:
177
 
            determine_wants = interrepo.get_determine_wants_revids(
178
 
                [revision_id], include_tags=True)
179
 
        else:
180
 
            determine_wants = interrepo.determine_wants_all
181
 
        interrepo.fetch_objects(determine_wants=determine_wants,
182
 
                                mapping=source_branch.mapping)
183
 
        result_branch = source_branch.sprout(
184
 
            result, revision_id=revision_id, repository=result_repo)
185
 
        if (create_tree_if_local and
186
 
            result.open_branch(name="").name == result_branch.name and
187
 
            isinstance(target_transport, LocalTransport) and
188
 
                (result_repo is None or result_repo.make_working_trees())):
189
 
            wt = result.create_workingtree(
190
 
                accelerator_tree=accelerator_tree,
191
 
                hardlink=hardlink, from_branch=result_branch)
192
 
        else:
193
 
            wt = None
194
 
        if recurse == 'down':
195
 
            with cleanup.ExitStack() as stack:
196
 
                basis = None
197
 
                if wt is not None:
198
 
                    basis = wt.basis_tree()
199
 
                elif result_branch is not None:
200
 
                    basis = result_branch.basis_tree()
201
 
                elif source_branch is not None:
202
 
                    basis = source_branch.basis_tree()
203
 
                if basis is not None:
204
 
                    stack.enter_context(basis.lock_read())
205
 
                    subtrees = basis.iter_references()
206
 
                else:
207
 
                    subtrees = []
208
 
                for path in subtrees:
209
 
                    target = urlutils.join(url, urlutils.escape(path))
210
 
                    sublocation = wt.reference_parent(
211
 
                        path, possible_transports=possible_transports)
212
 
                    if sublocation is None:
213
 
                        trace.warning(
214
 
                            'Ignoring nested tree %s, parent location unknown.',
215
 
                            path)
216
 
                        continue
217
 
                    sublocation.controldir.sprout(
218
 
                        target, basis.get_reference_revision(path),
219
 
                        force_new_repo=force_new_repo, recurse=recurse,
220
 
                        stacked=stacked)
221
 
        return result
222
 
 
223
 
    def clone_on_transport(self, transport, revision_id=None,
224
 
                           force_new_repo=False, preserve_stacking=False,
225
 
                           stacked_on=None, create_prefix=False,
226
 
                           use_existing_dir=True, no_tree=False,
227
 
                           tag_selector=None):
228
 
        """See ControlDir.clone_on_transport."""
229
 
        from ..repository import InterRepository
230
 
        from .mapping import default_mapping
231
 
        from ..transport.local import LocalTransport
232
 
        if stacked_on is not None:
233
 
            raise _mod_branch.UnstackableBranchFormat(
234
 
                self._format, self.user_url)
235
 
        if no_tree:
236
 
            format = BareLocalGitControlDirFormat()
237
 
        else:
238
 
            format = LocalGitControlDirFormat()
239
 
        (target_repo, target_controldir, stacking,
240
 
         repo_policy) = format.initialize_on_transport_ex(
241
 
            transport, use_existing_dir=use_existing_dir,
242
 
            create_prefix=create_prefix,
243
 
            force_new_repo=force_new_repo)
244
 
        target_repo = target_controldir.find_repository()
245
 
        target_git_repo = target_repo._git
246
 
        source_repo = self.find_repository()
247
 
        interrepo = InterRepository.get(source_repo, target_repo)
248
 
        if revision_id is not None:
249
 
            determine_wants = interrepo.get_determine_wants_revids(
250
 
                [revision_id], include_tags=True, tag_selector=tag_selector)
251
 
        else:
252
 
            determine_wants = interrepo.determine_wants_all
253
 
        (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants,
254
 
                                                       mapping=default_mapping)
255
 
        for name, val in viewitems(refs):
256
 
            target_git_repo.refs[name] = val
257
 
        result_dir = LocalGitDir(transport, target_git_repo, format)
258
 
        if revision_id is not None:
259
 
            result_dir.open_branch().set_last_revision(revision_id)
260
 
        if not no_tree and isinstance(result_dir.root_transport, LocalTransport):
261
 
            if result_dir.open_repository().make_working_trees():
262
 
                try:
263
 
                    local_wt = self.open_workingtree()
264
 
                except brz_errors.NoWorkingTree:
265
 
                    pass
266
 
                except brz_errors.NotLocalUrl:
267
 
                    result_dir.create_workingtree(revision_id=revision_id)
268
 
                else:
269
 
                    local_wt.clone(result_dir, revision_id=revision_id)
270
 
 
271
 
        return result_dir
272
 
 
273
 
    def find_repository(self):
274
 
        """Find the repository that should be used.
275
 
 
276
 
        This does not require a branch as we use it to find the repo for
277
 
        new branches as well as to hook existing branches up to their
278
 
        repository.
279
 
        """
280
 
        return self._gitrepository_class(self._find_commondir())
281
 
 
282
 
    def get_refs_container(self):
283
 
        """Retrieve the refs container.
284
 
        """
285
 
        raise NotImplementedError(self.get_refs_container)
286
 
 
287
 
    def determine_repository_policy(self, force_new_repo=False, stack_on=None,
288
 
                                    stack_on_pwd=None, require_stacking=False):
289
 
        """Return an object representing a policy to use.
290
 
 
291
 
        This controls whether a new repository is created, and the format of
292
 
        that repository, or some existing shared repository used instead.
293
 
 
294
 
        If stack_on is supplied, will not seek a containing shared repo.
295
 
 
296
 
        :param force_new_repo: If True, require a new repository to be created.
297
 
        :param stack_on: If supplied, the location to stack on.  If not
298
 
            supplied, a default_stack_on location may be used.
299
 
        :param stack_on_pwd: If stack_on is relative, the location it is
300
 
            relative to.
301
 
        """
302
 
        return UseExistingRepository(self.find_repository())
303
 
 
304
 
    def branch_names(self):
305
 
        from .refs import ref_to_branch_name
306
 
        ret = []
307
 
        for ref in self.get_refs_container().keys():
308
 
            try:
309
 
                branch_name = ref_to_branch_name(ref)
310
 
            except UnicodeDecodeError:
311
 
                trace.warning("Ignoring branch %r with unicode error ref", ref)
312
 
                continue
313
 
            except ValueError:
314
 
                continue
315
 
            ret.append(branch_name)
316
 
        return ret
317
 
 
318
 
    def get_branches(self):
319
 
        from .refs import ref_to_branch_name
320
 
        ret = {}
321
 
        for ref in self.get_refs_container().keys():
322
 
            try:
323
 
                branch_name = ref_to_branch_name(ref)
324
 
            except UnicodeDecodeError:
325
 
                trace.warning("Ignoring branch %r with unicode error ref", ref)
326
 
                continue
327
 
            except ValueError:
328
 
                continue
329
 
            ret[branch_name] = self.open_branch(ref=ref)
330
 
        return ret
331
 
 
332
 
    def list_branches(self):
333
 
        return list(self.get_branches().values())
334
 
 
335
 
    def push_branch(self, source, revision_id=None, overwrite=False,
336
 
                    remember=False, create_prefix=False, lossy=False,
337
 
                    name=None, tag_selector=None):
338
 
        """Push the source branch into this ControlDir."""
339
 
        push_result = GitPushResult()
340
 
        push_result.workingtree_updated = None
341
 
        push_result.master_branch = None
342
 
        push_result.source_branch = source
343
 
        push_result.stacked_on = None
344
 
        from .branch import GitBranch
345
 
        if isinstance(source, GitBranch) and lossy:
346
 
            raise brz_errors.LossyPushToSameVCS(source.controldir, self)
347
 
        target = self.open_branch(name, nascent_ok=True)
348
 
        push_result.branch_push_result = source.push(
349
 
            target, overwrite=overwrite, stop_revision=revision_id,
350
 
            lossy=lossy, tag_selector=tag_selector)
351
 
        push_result.new_revid = push_result.branch_push_result.new_revid
352
 
        push_result.old_revid = push_result.branch_push_result.old_revid
353
 
        try:
354
 
            wt = self.open_workingtree()
355
 
        except brz_errors.NoWorkingTree:
356
 
            push_result.workingtree_updated = None
357
 
        else:
358
 
            if self.open_branch(name="").name == target.name:
359
 
                wt._update_git_tree(
360
 
                    old_revision=push_result.old_revid,
361
 
                    new_revision=push_result.new_revid)
362
 
                push_result.workingtree_updated = True
363
 
            else:
364
 
                push_result.workingtree_updated = False
365
 
        push_result.target_branch = target
366
 
        if source.get_push_location() is None or remember:
367
 
            source.set_push_location(push_result.target_branch.base)
368
 
        return push_result
369
 
 
370
 
 
371
 
class LocalGitControlDirFormat(GitControlDirFormat):
372
 
    """The .git directory control format."""
373
 
 
374
 
    bare = False
375
 
 
376
 
    @classmethod
377
 
    def _known_formats(self):
378
 
        return set([LocalGitControlDirFormat()])
379
 
 
380
 
    @property
381
 
    def repository_format(self):
382
 
        from .repository import GitRepositoryFormat
383
 
        return GitRepositoryFormat()
384
 
 
385
 
    @property
386
 
    def workingtree_format(self):
387
 
        from .workingtree import GitWorkingTreeFormat
388
 
        return GitWorkingTreeFormat()
389
 
 
390
 
    def get_branch_format(self):
391
 
        from .branch import LocalGitBranchFormat
392
 
        return LocalGitBranchFormat()
393
 
 
394
 
    def open(self, transport, _found=None):
395
 
        """Open this directory.
396
 
 
397
 
        """
398
 
        from .transportgit import TransportRepo
399
 
 
400
 
        def _open(transport):
401
 
            try:
402
 
                return TransportRepo(transport, self.bare,
403
 
                                     refs_text=getattr(self, "_refs_text", None))
404
 
            except ValueError as e:
405
 
                if e.args == ('Expected file to start with \'gitdir: \'', ):
406
 
                    raise brz_errors.NotBranchError(path=transport.base)
407
 
                raise
408
 
 
409
 
        def redirected(transport, e, redirection_notice):
410
 
            trace.note(redirection_notice)
411
 
            return transport._redirected_to(e.source, e.target)
412
 
        gitrepo = do_catching_redirections(_open, transport, redirected)
413
 
        if not _found and not gitrepo._controltransport.has('objects'):
414
 
            raise brz_errors.NotBranchError(path=transport.base)
415
 
        return LocalGitDir(transport, gitrepo, self)
416
 
 
417
 
    def get_format_description(self):
418
 
        return "Local Git Repository"
419
 
 
420
 
    def initialize_on_transport(self, transport):
421
 
        from .transportgit import TransportRepo
422
 
        git_repo = TransportRepo.init(transport, bare=self.bare)
423
 
        return LocalGitDir(transport, git_repo, self)
424
 
 
425
 
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
426
 
                                   create_prefix=False, force_new_repo=False,
427
 
                                   stacked_on=None,
428
 
                                   stack_on_pwd=None, repo_format_name=None,
429
 
                                   make_working_trees=None,
430
 
                                   shared_repo=False, vfs_only=False):
431
 
        if shared_repo:
432
 
            raise brz_errors.SharedRepositoriesUnsupported(self)
433
 
 
434
 
        def make_directory(transport):
435
 
            transport.mkdir('.')
436
 
            return transport
437
 
 
438
 
        def redirected(transport, e, redirection_notice):
439
 
            trace.note(redirection_notice)
440
 
            return transport._redirected_to(e.source, e.target)
441
 
        try:
442
 
            transport = do_catching_redirections(
443
 
                make_directory, transport, redirected)
444
 
        except brz_errors.FileExists:
445
 
            if not use_existing_dir:
446
 
                raise
447
 
        except brz_errors.NoSuchFile:
448
 
            if not create_prefix:
449
 
                raise
450
 
            transport.create_prefix()
451
 
        controldir = self.initialize_on_transport(transport)
452
 
        if repo_format_name:
453
 
            result_repo = controldir.find_repository()
454
 
            repository_policy = UseExistingRepository(result_repo)
455
 
            result_repo.lock_write()
456
 
        else:
457
 
            result_repo = None
458
 
            repository_policy = None
459
 
        return (result_repo, controldir, False,
460
 
                repository_policy)
461
 
 
462
 
    def is_supported(self):
463
 
        return True
464
 
 
465
 
    def supports_transport(self, transport):
466
 
        try:
467
 
            external_url = transport.external_url()
468
 
        except brz_errors.InProcessTransport:
469
 
            raise brz_errors.NotBranchError(path=transport.base)
470
 
        return external_url.startswith("file:")
471
 
 
472
 
    def is_control_filename(self, filename):
473
 
        return (filename == '.git'
474
 
                or filename.startswith('.git/')
475
 
                or filename.startswith('.git\\'))
476
 
 
477
 
 
478
 
class BareLocalGitControlDirFormat(LocalGitControlDirFormat):
479
 
 
480
 
    bare = True
481
 
    supports_workingtrees = False
482
 
 
483
 
    def get_format_description(self):
484
 
        return "Local Git Repository (bare)"
485
 
 
486
 
    def is_control_filename(self, filename):
487
 
        return False
488
 
 
489
 
 
490
 
class LocalGitDir(GitDir):
491
 
    """An adapter to the '.git' dir used by git."""
492
 
 
493
 
    def _get_gitrepository_class(self):
494
 
        from .repository import LocalGitRepository
495
 
        return LocalGitRepository
496
 
 
497
 
    def __repr__(self):
498
 
        return "<%s at %r>" % (
499
 
            self.__class__.__name__, self.root_transport.base)
500
 
 
501
 
    _gitrepository_class = property(_get_gitrepository_class)
502
 
 
503
 
    @property
504
 
    def user_transport(self):
505
 
        return self.root_transport
506
 
 
507
 
    @property
508
 
    def control_transport(self):
509
 
        return self._git._controltransport
510
 
 
511
 
    def __init__(self, transport, gitrepo, format):
512
 
        self._format = format
513
 
        self.root_transport = transport
514
 
        self._mode_check_done = False
515
 
        self._git = gitrepo
516
 
        if gitrepo.bare:
517
 
            self.transport = transport
518
 
        else:
519
 
            self.transport = transport.clone('.git')
520
 
        self._mode_check_done = None
521
 
 
522
 
    def _get_symref(self, ref):
523
 
        ref_chain, unused_sha = self._git.refs.follow(ref)
524
 
        if len(ref_chain) == 1:
525
 
            return None
526
 
        return ref_chain[1]
527
 
 
528
 
    def set_branch_reference(self, target_branch, name=None):
529
 
        ref = self._get_selected_ref(name)
530
 
        target_transport = target_branch.controldir.control_transport
531
 
        if self.control_transport.base == target_transport.base:
532
 
            if ref == target_branch.ref:
533
 
                raise BranchReferenceLoop(target_branch)
534
 
            self._git.refs.set_symbolic_ref(ref, target_branch.ref)
535
 
        else:
536
 
            try:
537
 
                target_path = (
538
 
                    target_branch.controldir.control_transport.local_abspath(
539
 
                        '.'))
540
 
            except brz_errors.NotLocalUrl:
541
 
                raise brz_errors.IncompatibleFormat(
542
 
                    target_branch._format, self._format)
543
 
            # TODO(jelmer): Do some consistency checking across branches..
544
 
            self.control_transport.put_bytes(
545
 
                'commondir', target_path.encode('utf-8'))
546
 
            # TODO(jelmer): Urgh, avoid mucking about with internals.
547
 
            self._git._commontransport = (
548
 
                target_branch.repository._git._commontransport.clone())
549
 
            self._git.object_store = TransportObjectStore(
550
 
                self._git._commontransport.clone(OBJECTDIR))
551
 
            self._git.refs.transport = self._git._commontransport
552
 
            target_ref_chain, unused_sha = (
553
 
                target_branch.controldir._git.refs.follow(target_branch.ref))
554
 
            for target_ref in target_ref_chain:
555
 
                if target_ref == b'HEAD':
556
 
                    continue
557
 
                break
558
 
            else:
559
 
                # Can't create a reference to something that is not a in a repository.
560
 
                raise brz_errors.IncompatibleFormat(
561
 
                    self.set_branch_reference, self)
562
 
            self._git.refs.set_symbolic_ref(ref, target_ref)
563
 
 
564
 
    def get_branch_reference(self, name=None):
565
 
        ref = self._get_selected_ref(name)
566
 
        target_ref = self._get_symref(ref)
567
 
        if target_ref is not None:
568
 
            from .refs import ref_to_branch_name
569
 
            try:
570
 
                branch_name = ref_to_branch_name(target_ref)
571
 
            except ValueError:
572
 
                params = {'ref': urlutils.quote(
573
 
                    target_ref.decode('utf-8'), '')}
574
 
            else:
575
 
                if branch_name != '':
576
 
                    params = {'branch': urlutils.quote(branch_name, '')}
577
 
                else:
578
 
                    params = {}
579
 
            try:
580
 
                commondir = self.control_transport.get_bytes('commondir')
581
 
            except brz_errors.NoSuchFile:
582
 
                base_url = self.user_url.rstrip('/')
583
 
            else:
584
 
                base_url = urlutils.local_path_to_url(
585
 
                    commondir.decode(osutils._fs_enc)).rstrip('/.git/') + '/'
586
 
            if not PY3:
587
 
                params = {k: v.encode('utf-8') for (k, v) in viewitems(params)}
588
 
            return urlutils.join_segment_parameters(base_url, params)
589
 
        return None
590
 
 
591
 
    def find_branch_format(self, name=None):
592
 
        from .branch import (
593
 
            LocalGitBranchFormat,
594
 
            )
595
 
        return LocalGitBranchFormat()
596
 
 
597
 
    def get_branch_transport(self, branch_format, name=None):
598
 
        if branch_format is None:
599
 
            return self.transport
600
 
        if isinstance(branch_format, LocalGitControlDirFormat):
601
 
            return self.transport
602
 
        raise brz_errors.IncompatibleFormat(branch_format, self._format)
603
 
 
604
 
    def get_repository_transport(self, format):
605
 
        if format is None:
606
 
            return self.transport
607
 
        if isinstance(format, LocalGitControlDirFormat):
608
 
            return self.transport
609
 
        raise brz_errors.IncompatibleFormat(format, self._format)
610
 
 
611
 
    def get_workingtree_transport(self, format):
612
 
        if format is None:
613
 
            return self.transport
614
 
        if isinstance(format, LocalGitControlDirFormat):
615
 
            return self.transport
616
 
        raise brz_errors.IncompatibleFormat(format, self._format)
617
 
 
618
 
    def open_branch(self, name=None, unsupported=False, ignore_fallbacks=None,
619
 
                    ref=None, possible_transports=None, nascent_ok=False):
620
 
        """'create' a branch for this dir."""
621
 
        repo = self.find_repository()
622
 
        from .branch import LocalGitBranch
623
 
        ref = self._get_selected_ref(name, ref)
624
 
        if not nascent_ok and ref not in self._git.refs:
625
 
            raise brz_errors.NotBranchError(
626
 
                self.root_transport.base, controldir=self)
627
 
        ref_chain, unused_sha = self._git.refs.follow(ref)
628
 
        if ref_chain[-1] == b'HEAD':
629
 
            controldir = self
630
 
        else:
631
 
            controldir = self._find_commondir()
632
 
        return LocalGitBranch(controldir, repo, ref_chain[-1])
633
 
 
634
 
    def destroy_branch(self, name=None):
635
 
        refname = self._get_selected_ref(name)
636
 
        if refname == b'HEAD':
637
 
            # HEAD can't be removed
638
 
            raise brz_errors.UnsupportedOperation(
639
 
                self.destroy_branch, self)
640
 
        try:
641
 
            del self._git.refs[refname]
642
 
        except KeyError:
643
 
            raise brz_errors.NotBranchError(
644
 
                self.root_transport.base, controldir=self)
645
 
 
646
 
    def destroy_repository(self):
647
 
        raise brz_errors.UnsupportedOperation(self.destroy_repository, self)
648
 
 
649
 
    def destroy_workingtree(self):
650
 
        raise brz_errors.UnsupportedOperation(self.destroy_workingtree, self)
651
 
 
652
 
    def destroy_workingtree_metadata(self):
653
 
        raise brz_errors.UnsupportedOperation(
654
 
            self.destroy_workingtree_metadata, self)
655
 
 
656
 
    def needs_format_conversion(self, format=None):
657
 
        return not isinstance(self._format, format.__class__)
658
 
 
659
 
    def open_repository(self):
660
 
        """'open' a repository for this dir."""
661
 
        if self.control_transport.has('commondir'):
662
 
            raise brz_errors.NoRepositoryPresent(self)
663
 
        return self._gitrepository_class(self)
664
 
 
665
 
    def has_workingtree(self):
666
 
        return not self._git.bare
667
 
 
668
 
    def open_workingtree(self, recommend_upgrade=True, unsupported=False):
669
 
        if not self._git.bare:
670
 
            repo = self.find_repository()
671
 
            from .workingtree import GitWorkingTree
672
 
            branch = self.open_branch(ref=b'HEAD', nascent_ok=True)
673
 
            return GitWorkingTree(self, repo, branch)
674
 
        loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
675
 
        raise brz_errors.NoWorkingTree(loc)
676
 
 
677
 
    def create_repository(self, shared=False):
678
 
        from .repository import GitRepositoryFormat
679
 
        if shared:
680
 
            raise brz_errors.IncompatibleFormat(
681
 
                GitRepositoryFormat(), self._format)
682
 
        return self.find_repository()
683
 
 
684
 
    def create_branch(self, name=None, repository=None,
685
 
                      append_revisions_only=None, ref=None):
686
 
        refname = self._get_selected_ref(name, ref)
687
 
        if refname != b'HEAD' and refname in self._git.refs:
688
 
            raise brz_errors.AlreadyBranchError(self.user_url)
689
 
        repo = self.open_repository()
690
 
        if refname in self._git.refs:
691
 
            ref_chain, unused_sha = self._git.refs.follow(
692
 
                self._get_selected_ref(None))
693
 
            if ref_chain[0] == b'HEAD':
694
 
                refname = ref_chain[1]
695
 
        from .branch import LocalGitBranch
696
 
        branch = LocalGitBranch(self, repo, refname)
697
 
        if append_revisions_only:
698
 
            branch.set_append_revisions_only(append_revisions_only)
699
 
        return branch
700
 
 
701
 
    def backup_bzrdir(self):
702
 
        if not self._git.bare:
703
 
            self.root_transport.copy_tree(".git", ".git.backup")
704
 
            return (self.root_transport.abspath(".git"),
705
 
                    self.root_transport.abspath(".git.backup"))
706
 
        else:
707
 
            basename = urlutils.basename(self.root_transport.base)
708
 
            parent = self.root_transport.clone('..')
709
 
            parent.copy_tree(basename, basename + ".backup")
710
 
 
711
 
    def create_workingtree(self, revision_id=None, from_branch=None,
712
 
                           accelerator_tree=None, hardlink=False):
713
 
        if self._git.bare:
714
 
            raise brz_errors.UnsupportedOperation(
715
 
                self.create_workingtree, self)
716
 
        if from_branch is None:
717
 
            from_branch = self.open_branch(nascent_ok=True)
718
 
        if revision_id is None:
719
 
            revision_id = from_branch.last_revision()
720
 
        repo = self.find_repository()
721
 
        from .workingtree import GitWorkingTree
722
 
        wt = GitWorkingTree(self, repo, from_branch)
723
 
        wt.set_last_revision(revision_id)
724
 
        wt._build_checkout_with_index()
725
 
        return wt
726
 
 
727
 
    def _find_or_create_repository(self, force_new_repo=None):
728
 
        return self.create_repository(shared=False)
729
 
 
730
 
    def _find_creation_modes(self):
731
 
        """Determine the appropriate modes for files and directories.
732
 
 
733
 
        They're always set to be consistent with the base directory,
734
 
        assuming that this transport allows setting modes.
735
 
        """
736
 
        # TODO: Do we need or want an option (maybe a config setting) to turn
737
 
        # this off or override it for particular locations? -- mbp 20080512
738
 
        if self._mode_check_done:
739
 
            return
740
 
        self._mode_check_done = True
741
 
        try:
742
 
            st = self.transport.stat('.')
743
 
        except brz_errors.TransportNotPossible:
744
 
            self._dir_mode = None
745
 
            self._file_mode = None
746
 
        else:
747
 
            # Check the directory mode, but also make sure the created
748
 
            # directories and files are read-write for this user. This is
749
 
            # mostly a workaround for filesystems which lie about being able to
750
 
            # write to a directory (cygwin & win32)
751
 
            if (st.st_mode & 0o7777 == 0o0000):
752
 
                # FTP allows stat but does not return dir/file modes
753
 
                self._dir_mode = None
754
 
                self._file_mode = None
755
 
            else:
756
 
                self._dir_mode = (st.st_mode & 0o7777) | 0o0700
757
 
                # Remove the sticky and execute bits for files
758
 
                self._file_mode = self._dir_mode & ~0o7111
759
 
 
760
 
    def _get_file_mode(self):
761
 
        """Return Unix mode for newly created files, or None.
762
 
        """
763
 
        if not self._mode_check_done:
764
 
            self._find_creation_modes()
765
 
        return self._file_mode
766
 
 
767
 
    def _get_dir_mode(self):
768
 
        """Return Unix mode for newly created directories, or None.
769
 
        """
770
 
        if not self._mode_check_done:
771
 
            self._find_creation_modes()
772
 
        return self._dir_mode
773
 
 
774
 
    def get_refs_container(self):
775
 
        return self._git.refs
776
 
 
777
 
    def get_peeled(self, ref):
778
 
        return self._git.get_peeled(ref)
779
 
 
780
 
    def _find_commondir(self):
781
 
        try:
782
 
            commondir = self.control_transport.get_bytes('commondir')
783
 
        except brz_errors.NoSuchFile:
784
 
            return self
785
 
        else:
786
 
            commondir = commondir.rstrip(b'/.git/').decode(osutils._fs_enc)
787
 
            return ControlDir.open_from_transport(
788
 
                get_transport_from_path(commondir))