/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/git/dir.py

  • Committer: Jelmer Vernooij
  • Date: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

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