/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: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

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