/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

  • Committer: Jelmer Vernooij
  • Date: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

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