/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-09-02 11:51:19 UTC
  • mto: (7490.40.109 work)
  • mto: This revision was merged to the branch mainline in revision 7526.
  • Revision ID: jelmer@jelmer.uk-20200902115119-otuspc349t9rmhua
add test for git file merge.

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