/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-01-18 02:42:17 UTC
  • mto: This revision was merged to the branch mainline in revision 7453.
  • Revision ID: jelmer@jelmer.uk-20200118024217-xkr4cy9vpdpf713o
Add smart call for receiving all reference info.

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 = source_branch.reference_parent(
 
211
                        path, possible_transports=possible_transports)
 
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
        """See ControlDir.clone_on_transport."""
 
223
        from ..repository import InterRepository
 
224
        from .mapping import default_mapping
 
225
        if stacked_on is not None:
 
226
            raise _mod_branch.UnstackableBranchFormat(
 
227
                self._format, self.user_url)
 
228
        if no_tree:
 
229
            format = BareLocalGitControlDirFormat()
 
230
        else:
 
231
            format = LocalGitControlDirFormat()
 
232
        (target_repo, target_controldir, stacking,
 
233
         repo_policy) = format.initialize_on_transport_ex(
 
234
            transport, use_existing_dir=use_existing_dir,
 
235
            create_prefix=create_prefix,
 
236
            force_new_repo=force_new_repo)
 
237
        target_repo = target_controldir.find_repository()
 
238
        target_git_repo = target_repo._git
 
239
        source_repo = self.find_repository()
 
240
        interrepo = InterRepository.get(source_repo, target_repo)
 
241
        if revision_id is not None:
 
242
            determine_wants = interrepo.get_determine_wants_revids(
 
243
                [revision_id], include_tags=True)
 
244
        else:
 
245
            determine_wants = interrepo.determine_wants_all
 
246
        (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants,
 
247
                                                       mapping=default_mapping)
 
248
        for name, val in viewitems(refs):
 
249
            target_git_repo.refs[name] = val
 
250
        result_dir = self.__class__(transport, target_git_repo, format)
 
251
        if revision_id is not None:
 
252
            result_dir.open_branch().set_last_revision(revision_id)
 
253
        try:
 
254
            # Cheaper to check if the target is not local, than to try making
 
255
            # the tree and fail.
 
256
            result_dir.root_transport.local_abspath('.')
 
257
            if result_dir.open_repository().make_working_trees():
 
258
                self.open_workingtree().clone(
 
259
                    result_dir, revision_id=revision_id)
 
260
        except (brz_errors.NoWorkingTree, brz_errors.NotLocalUrl):
 
261
            pass
 
262
 
 
263
        return result_dir
 
264
 
 
265
    def find_repository(self):
 
266
        """Find the repository that should be used.
 
267
 
 
268
        This does not require a branch as we use it to find the repo for
 
269
        new branches as well as to hook existing branches up to their
 
270
        repository.
 
271
        """
 
272
        return self._gitrepository_class(self._find_commondir())
 
273
 
 
274
    def get_refs_container(self):
 
275
        """Retrieve the refs container.
 
276
        """
 
277
        raise NotImplementedError(self.get_refs_container)
 
278
 
 
279
    def determine_repository_policy(self, force_new_repo=False, stack_on=None,
 
280
                                    stack_on_pwd=None, require_stacking=False):
 
281
        """Return an object representing a policy to use.
 
282
 
 
283
        This controls whether a new repository is created, and the format of
 
284
        that repository, or some existing shared repository used instead.
 
285
 
 
286
        If stack_on is supplied, will not seek a containing shared repo.
 
287
 
 
288
        :param force_new_repo: If True, require a new repository to be created.
 
289
        :param stack_on: If supplied, the location to stack on.  If not
 
290
            supplied, a default_stack_on location may be used.
 
291
        :param stack_on_pwd: If stack_on is relative, the location it is
 
292
            relative to.
 
293
        """
 
294
        return UseExistingRepository(self.find_repository())
 
295
 
 
296
    def get_branches(self):
 
297
        from .refs import ref_to_branch_name
 
298
        ret = {}
 
299
        for ref in self.get_refs_container().keys():
 
300
            try:
 
301
                branch_name = ref_to_branch_name(ref)
 
302
            except UnicodeDecodeError:
 
303
                trace.warning("Ignoring branch %r with unicode error ref", ref)
 
304
                continue
 
305
            except ValueError:
 
306
                continue
 
307
            ret[branch_name] = self.open_branch(ref=ref)
 
308
        return ret
 
309
 
 
310
    def list_branches(self):
 
311
        return list(self.get_branches().values())
 
312
 
 
313
    def push_branch(self, source, revision_id=None, overwrite=False,
 
314
                    remember=False, create_prefix=False, lossy=False,
 
315
                    name=None):
 
316
        """Push the source branch into this ControlDir."""
 
317
        push_result = GitPushResult()
 
318
        push_result.workingtree_updated = None
 
319
        push_result.master_branch = None
 
320
        push_result.source_branch = source
 
321
        push_result.stacked_on = None
 
322
        from .branch import GitBranch
 
323
        if isinstance(source, GitBranch) and lossy:
 
324
            raise brz_errors.LossyPushToSameVCS(source.controldir, self)
 
325
        target = self.open_branch(name, nascent_ok=True)
 
326
        push_result.branch_push_result = source.push(
 
327
            target, overwrite=overwrite, stop_revision=revision_id,
 
328
            lossy=lossy)
 
329
        push_result.new_revid = push_result.branch_push_result.new_revid
 
330
        push_result.old_revid = push_result.branch_push_result.old_revid
 
331
        try:
 
332
            wt = self.open_workingtree()
 
333
        except brz_errors.NoWorkingTree:
 
334
            push_result.workingtree_updated = None
 
335
        else:
 
336
            if self.open_branch(name="").name == target.name:
 
337
                wt._update_git_tree(
 
338
                    old_revision=push_result.old_revid,
 
339
                    new_revision=push_result.new_revid)
 
340
                push_result.workingtree_updated = True
 
341
            else:
 
342
                push_result.workingtree_updated = False
 
343
        push_result.target_branch = target
 
344
        if source.get_push_location() is None or remember:
 
345
            source.set_push_location(push_result.target_branch.base)
 
346
        return push_result
 
347
 
 
348
 
 
349
class LocalGitControlDirFormat(GitControlDirFormat):
 
350
    """The .git directory control format."""
 
351
 
 
352
    bare = False
 
353
 
 
354
    @classmethod
 
355
    def _known_formats(self):
 
356
        return set([LocalGitControlDirFormat()])
 
357
 
 
358
    @property
 
359
    def repository_format(self):
 
360
        from .repository import GitRepositoryFormat
 
361
        return GitRepositoryFormat()
 
362
 
 
363
    @property
 
364
    def workingtree_format(self):
 
365
        from .workingtree import GitWorkingTreeFormat
 
366
        return GitWorkingTreeFormat()
 
367
 
 
368
    def get_branch_format(self):
 
369
        from .branch import LocalGitBranchFormat
 
370
        return LocalGitBranchFormat()
 
371
 
 
372
    def open(self, transport, _found=None):
 
373
        """Open this directory.
 
374
 
 
375
        """
 
376
        from .transportgit import TransportRepo
 
377
 
 
378
        def _open(transport):
 
379
            try:
 
380
                return TransportRepo(transport, self.bare,
 
381
                                     refs_text=getattr(self, "_refs_text", None))
 
382
            except ValueError as e:
 
383
                if e.args == ('Expected file to start with \'gitdir: \'', ):
 
384
                    raise brz_errors.NotBranchError(path=transport.base)
 
385
                raise
 
386
 
 
387
        def redirected(transport, e, redirection_notice):
 
388
            trace.note(redirection_notice)
 
389
            return transport._redirected_to(e.source, e.target)
 
390
        gitrepo = do_catching_redirections(_open, transport, redirected)
 
391
        if not _found and not gitrepo._controltransport.has('objects'):
 
392
            raise brz_errors.NotBranchError(path=transport.base)
 
393
        return LocalGitDir(transport, gitrepo, self)
 
394
 
 
395
    def get_format_description(self):
 
396
        return "Local Git Repository"
 
397
 
 
398
    def initialize_on_transport(self, transport):
 
399
        from .transportgit import TransportRepo
 
400
        git_repo = TransportRepo.init(transport, bare=self.bare)
 
401
        return LocalGitDir(transport, git_repo, self)
 
402
 
 
403
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
 
404
                                   create_prefix=False, force_new_repo=False,
 
405
                                   stacked_on=None,
 
406
                                   stack_on_pwd=None, repo_format_name=None,
 
407
                                   make_working_trees=None,
 
408
                                   shared_repo=False, vfs_only=False):
 
409
        if shared_repo:
 
410
            raise brz_errors.SharedRepositoriesUnsupported(self)
 
411
 
 
412
        def make_directory(transport):
 
413
            transport.mkdir('.')
 
414
            return transport
 
415
 
 
416
        def redirected(transport, e, redirection_notice):
 
417
            trace.note(redirection_notice)
 
418
            return transport._redirected_to(e.source, e.target)
 
419
        try:
 
420
            transport = do_catching_redirections(
 
421
                make_directory, transport, redirected)
 
422
        except brz_errors.FileExists:
 
423
            if not use_existing_dir:
 
424
                raise
 
425
        except brz_errors.NoSuchFile:
 
426
            if not create_prefix:
 
427
                raise
 
428
            transport.create_prefix()
 
429
        controldir = self.initialize_on_transport(transport)
 
430
        if repo_format_name:
 
431
            result_repo = controldir.find_repository()
 
432
            repository_policy = UseExistingRepository(result_repo)
 
433
            result_repo.lock_write()
 
434
        else:
 
435
            result_repo = None
 
436
            repository_policy = None
 
437
        return (result_repo, controldir, False,
 
438
                repository_policy)
 
439
 
 
440
    def is_supported(self):
 
441
        return True
 
442
 
 
443
    def supports_transport(self, transport):
 
444
        try:
 
445
            external_url = transport.external_url()
 
446
        except brz_errors.InProcessTransport:
 
447
            raise brz_errors.NotBranchError(path=transport.base)
 
448
        return external_url.startswith("file:")
 
449
 
 
450
    def is_control_filename(self, filename):
 
451
        return (filename == '.git'
 
452
                or filename.startswith('.git/')
 
453
                or filename.startswith('.git\\'))
 
454
 
 
455
 
 
456
class BareLocalGitControlDirFormat(LocalGitControlDirFormat):
 
457
 
 
458
    bare = True
 
459
    supports_workingtrees = False
 
460
 
 
461
    def get_format_description(self):
 
462
        return "Local Git Repository (bare)"
 
463
 
 
464
    def is_control_filename(self, filename):
 
465
        return False
 
466
 
 
467
 
 
468
class LocalGitDir(GitDir):
 
469
    """An adapter to the '.git' dir used by git."""
 
470
 
 
471
    def _get_gitrepository_class(self):
 
472
        from .repository import LocalGitRepository
 
473
        return LocalGitRepository
 
474
 
 
475
    def __repr__(self):
 
476
        return "<%s at %r>" % (
 
477
            self.__class__.__name__, self.root_transport.base)
 
478
 
 
479
    _gitrepository_class = property(_get_gitrepository_class)
 
480
 
 
481
    @property
 
482
    def user_transport(self):
 
483
        return self.root_transport
 
484
 
 
485
    @property
 
486
    def control_transport(self):
 
487
        return self._git._controltransport
 
488
 
 
489
    def __init__(self, transport, gitrepo, format):
 
490
        self._format = format
 
491
        self.root_transport = transport
 
492
        self._mode_check_done = False
 
493
        self._git = gitrepo
 
494
        if gitrepo.bare:
 
495
            self.transport = transport
 
496
        else:
 
497
            self.transport = transport.clone('.git')
 
498
        self._mode_check_done = None
 
499
 
 
500
    def _get_symref(self, ref):
 
501
        ref_chain, unused_sha = self._git.refs.follow(ref)
 
502
        if len(ref_chain) == 1:
 
503
            return None
 
504
        return ref_chain[1]
 
505
 
 
506
    def set_branch_reference(self, target_branch, name=None):
 
507
        ref = self._get_selected_ref(name)
 
508
        target_transport = target_branch.controldir.control_transport
 
509
        if self.control_transport.base == target_transport.base:
 
510
            if ref == target_branch.ref:
 
511
                raise BranchReferenceLoop(target_branch)
 
512
            self._git.refs.set_symbolic_ref(ref, target_branch.ref)
 
513
        else:
 
514
            try:
 
515
                target_path = (
 
516
                    target_branch.controldir.control_transport.local_abspath(
 
517
                        '.'))
 
518
            except brz_errors.NotLocalUrl:
 
519
                raise brz_errors.IncompatibleFormat(
 
520
                    target_branch._format, self._format)
 
521
            # TODO(jelmer): Do some consistency checking across branches..
 
522
            self.control_transport.put_bytes(
 
523
                'commondir', target_path.encode('utf-8'))
 
524
            # TODO(jelmer): Urgh, avoid mucking about with internals.
 
525
            self._git._commontransport = (
 
526
                target_branch.repository._git._commontransport.clone())
 
527
            self._git.object_store = TransportObjectStore(
 
528
                self._git._commontransport.clone(OBJECTDIR))
 
529
            self._git.refs.transport = self._git._commontransport
 
530
            target_ref_chain, unused_sha = (
 
531
                target_branch.controldir._git.refs.follow(target_branch.ref))
 
532
            for target_ref in target_ref_chain:
 
533
                if target_ref == b'HEAD':
 
534
                    continue
 
535
                break
 
536
            else:
 
537
                # Can't create a reference to something that is not a in a repository.
 
538
                raise brz_errors.IncompatibleFormat(
 
539
                    self.set_branch_reference, self)
 
540
            self._git.refs.set_symbolic_ref(ref, target_ref)
 
541
 
 
542
    def get_branch_reference(self, name=None):
 
543
        ref = self._get_selected_ref(name)
 
544
        target_ref = self._get_symref(ref)
 
545
        if target_ref is not None:
 
546
            from .refs import ref_to_branch_name
 
547
            try:
 
548
                branch_name = ref_to_branch_name(target_ref)
 
549
            except ValueError:
 
550
                params = {'ref': urlutils.quote(
 
551
                    target_ref.decode('utf-8'), '')}
 
552
            else:
 
553
                if branch_name != '':
 
554
                    params = {'branch': urlutils.quote(branch_name, '')}
 
555
                else:
 
556
                    params = {}
 
557
            try:
 
558
                commondir = self.control_transport.get_bytes('commondir')
 
559
            except brz_errors.NoSuchFile:
 
560
                base_url = self.user_url.rstrip('/')
 
561
            else:
 
562
                base_url = urlutils.local_path_to_url(
 
563
                    commondir.decode(osutils._fs_enc)).rstrip('/.git/') + '/'
 
564
            if not PY3:
 
565
                params = {k: v.encode('utf-8') for (k, v) in viewitems(params)}
 
566
            return urlutils.join_segment_parameters(base_url, params)
 
567
        return None
 
568
 
 
569
    def find_branch_format(self, name=None):
 
570
        from .branch import (
 
571
            LocalGitBranchFormat,
 
572
            )
 
573
        return LocalGitBranchFormat()
 
574
 
 
575
    def get_branch_transport(self, branch_format, name=None):
 
576
        if branch_format is None:
 
577
            return self.transport
 
578
        if isinstance(branch_format, LocalGitControlDirFormat):
 
579
            return self.transport
 
580
        raise brz_errors.IncompatibleFormat(branch_format, self._format)
 
581
 
 
582
    def get_repository_transport(self, format):
 
583
        if format is None:
 
584
            return self.transport
 
585
        if isinstance(format, LocalGitControlDirFormat):
 
586
            return self.transport
 
587
        raise brz_errors.IncompatibleFormat(format, self._format)
 
588
 
 
589
    def get_workingtree_transport(self, format):
 
590
        if format is None:
 
591
            return self.transport
 
592
        if isinstance(format, LocalGitControlDirFormat):
 
593
            return self.transport
 
594
        raise brz_errors.IncompatibleFormat(format, self._format)
 
595
 
 
596
    def open_branch(self, name=None, unsupported=False, ignore_fallbacks=None,
 
597
                    ref=None, possible_transports=None, nascent_ok=False):
 
598
        """'create' a branch for this dir."""
 
599
        repo = self.find_repository()
 
600
        from .branch import LocalGitBranch
 
601
        ref = self._get_selected_ref(name, ref)
 
602
        if not nascent_ok and ref not in self._git.refs:
 
603
            raise brz_errors.NotBranchError(
 
604
                self.root_transport.base, controldir=self)
 
605
        ref_chain, unused_sha = self._git.refs.follow(ref)
 
606
        if ref_chain[-1] == b'HEAD':
 
607
            controldir = self
 
608
        else:
 
609
            controldir = self._find_commondir()
 
610
        return LocalGitBranch(controldir, repo, ref_chain[-1])
 
611
 
 
612
    def destroy_branch(self, name=None):
 
613
        refname = self._get_selected_ref(name)
 
614
        if refname == b'HEAD':
 
615
            # HEAD can't be removed
 
616
            raise brz_errors.UnsupportedOperation(
 
617
                self.destroy_branch, self)
 
618
        try:
 
619
            del self._git.refs[refname]
 
620
        except KeyError:
 
621
            raise brz_errors.NotBranchError(
 
622
                self.root_transport.base, controldir=self)
 
623
 
 
624
    def destroy_repository(self):
 
625
        raise brz_errors.UnsupportedOperation(self.destroy_repository, self)
 
626
 
 
627
    def destroy_workingtree(self):
 
628
        raise brz_errors.UnsupportedOperation(self.destroy_workingtree, self)
 
629
 
 
630
    def destroy_workingtree_metadata(self):
 
631
        raise brz_errors.UnsupportedOperation(
 
632
            self.destroy_workingtree_metadata, self)
 
633
 
 
634
    def needs_format_conversion(self, format=None):
 
635
        return not isinstance(self._format, format.__class__)
 
636
 
 
637
    def open_repository(self):
 
638
        """'open' a repository for this dir."""
 
639
        if self.control_transport.has('commondir'):
 
640
            raise brz_errors.NoRepositoryPresent(self)
 
641
        return self._gitrepository_class(self)
 
642
 
 
643
    def has_workingtree(self):
 
644
        return not self._git.bare
 
645
 
 
646
    def open_workingtree(self, recommend_upgrade=True, unsupported=False):
 
647
        if not self._git.bare:
 
648
            repo = self.find_repository()
 
649
            from .workingtree import GitWorkingTree
 
650
            branch = self.open_branch(ref=b'HEAD', nascent_ok=True)
 
651
            return GitWorkingTree(self, repo, branch)
 
652
        loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
 
653
        raise brz_errors.NoWorkingTree(loc)
 
654
 
 
655
    def create_repository(self, shared=False):
 
656
        from .repository import GitRepositoryFormat
 
657
        if shared:
 
658
            raise brz_errors.IncompatibleFormat(
 
659
                GitRepositoryFormat(), self._format)
 
660
        return self.find_repository()
 
661
 
 
662
    def create_branch(self, name=None, repository=None,
 
663
                      append_revisions_only=None, ref=None):
 
664
        refname = self._get_selected_ref(name, ref)
 
665
        if refname != b'HEAD' and refname in self._git.refs:
 
666
            raise brz_errors.AlreadyBranchError(self.user_url)
 
667
        repo = self.open_repository()
 
668
        if refname in self._git.refs:
 
669
            ref_chain, unused_sha = self._git.refs.follow(
 
670
                self._get_selected_ref(None))
 
671
            if ref_chain[0] == b'HEAD':
 
672
                refname = ref_chain[1]
 
673
        from .branch import LocalGitBranch
 
674
        branch = LocalGitBranch(self, repo, refname)
 
675
        if append_revisions_only:
 
676
            branch.set_append_revisions_only(append_revisions_only)
 
677
        return branch
 
678
 
 
679
    def backup_bzrdir(self):
 
680
        if not self._git.bare:
 
681
            self.root_transport.copy_tree(".git", ".git.backup")
 
682
            return (self.root_transport.abspath(".git"),
 
683
                    self.root_transport.abspath(".git.backup"))
 
684
        else:
 
685
            basename = urlutils.basename(self.root_transport.base)
 
686
            parent = self.root_transport.clone('..')
 
687
            parent.copy_tree(basename, basename + ".backup")
 
688
 
 
689
    def create_workingtree(self, revision_id=None, from_branch=None,
 
690
                           accelerator_tree=None, hardlink=False):
 
691
        if self._git.bare:
 
692
            raise brz_errors.UnsupportedOperation(
 
693
                self.create_workingtree, self)
 
694
        if from_branch is None:
 
695
            from_branch = self.open_branch(nascent_ok=True)
 
696
        if revision_id is None:
 
697
            revision_id = from_branch.last_revision()
 
698
        repo = self.find_repository()
 
699
        from .workingtree import GitWorkingTree
 
700
        wt = GitWorkingTree(self, repo, from_branch)
 
701
        wt.set_last_revision(revision_id)
 
702
        wt._build_checkout_with_index()
 
703
        return wt
 
704
 
 
705
    def _find_or_create_repository(self, force_new_repo=None):
 
706
        return self.create_repository(shared=False)
 
707
 
 
708
    def _find_creation_modes(self):
 
709
        """Determine the appropriate modes for files and directories.
 
710
 
 
711
        They're always set to be consistent with the base directory,
 
712
        assuming that this transport allows setting modes.
 
713
        """
 
714
        # TODO: Do we need or want an option (maybe a config setting) to turn
 
715
        # this off or override it for particular locations? -- mbp 20080512
 
716
        if self._mode_check_done:
 
717
            return
 
718
        self._mode_check_done = True
 
719
        try:
 
720
            st = self.transport.stat('.')
 
721
        except brz_errors.TransportNotPossible:
 
722
            self._dir_mode = None
 
723
            self._file_mode = None
 
724
        else:
 
725
            # Check the directory mode, but also make sure the created
 
726
            # directories and files are read-write for this user. This is
 
727
            # mostly a workaround for filesystems which lie about being able to
 
728
            # write to a directory (cygwin & win32)
 
729
            if (st.st_mode & 0o7777 == 0o0000):
 
730
                # FTP allows stat but does not return dir/file modes
 
731
                self._dir_mode = None
 
732
                self._file_mode = None
 
733
            else:
 
734
                self._dir_mode = (st.st_mode & 0o7777) | 0o0700
 
735
                # Remove the sticky and execute bits for files
 
736
                self._file_mode = self._dir_mode & ~0o7111
 
737
 
 
738
    def _get_file_mode(self):
 
739
        """Return Unix mode for newly created files, or None.
 
740
        """
 
741
        if not self._mode_check_done:
 
742
            self._find_creation_modes()
 
743
        return self._file_mode
 
744
 
 
745
    def _get_dir_mode(self):
 
746
        """Return Unix mode for newly created directories, or None.
 
747
        """
 
748
        if not self._mode_check_done:
 
749
            self._find_creation_modes()
 
750
        return self._dir_mode
 
751
 
 
752
    def get_refs_container(self):
 
753
        return self._git.refs
 
754
 
 
755
    def get_peeled(self, ref):
 
756
        return self._git.get_peeled(ref)
 
757
 
 
758
    def _find_commondir(self):
 
759
        try:
 
760
            commondir = self.control_transport.get_bytes('commondir')
 
761
        except brz_errors.NoSuchFile:
 
762
            return self
 
763
        else:
 
764
            commondir = commondir.rstrip(b'/.git/').decode(osutils._fs_enc)
 
765
            return ControlDir.open_from_transport(
 
766
                get_transport_from_path(commondir))