/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: Breezy landing bot
  • Author(s): Colin Watson
  • Date: 2020-11-16 21:47:08 UTC
  • mfrom: (7521.1.1 remove-lp-workaround)
  • Revision ID: breezy.the.bot@gmail.com-20201116214708-jos209mgxi41oy15
Remove breezy.git workaround for bazaar.launchpad.net.

Merged from https://code.launchpad.net/~cjwatson/brz/remove-lp-workaround/+merge/393710

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