/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 18:52:18 UTC
  • mto: (7490.7.7 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200322185218-4q1r1g0gof7ya53j
Fix reference handling.

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