/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: Robert Collins
  • Date: 2010-05-11 08:36:16 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511083616-b8fjb19zomwupid0
Make all lock methods return Result objects, rather than lock_read returning self, as per John's review.

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))