/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 dir.py

Fix pulling into bound branches.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical Ltd
 
2
# Copyright (C) 2010 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
 
 
18
"""An adapter between a Git control dir and a Bazaar ControlDir."""
 
19
 
 
20
import urllib
 
21
 
 
22
from bzrlib import (
 
23
    errors as bzr_errors,
 
24
    trace,
 
25
    osutils,
 
26
    urlutils,
 
27
    )
 
28
from bzrlib.bzrdir import CreateRepository
 
29
from bzrlib.transport import do_catching_redirections
 
30
 
 
31
from bzrlib.controldir import (
 
32
    ControlDir,
 
33
    ControlDirFormat,
 
34
    format_registry,
 
35
    )
 
36
 
 
37
 
 
38
class GitDirConfig(object):
 
39
 
 
40
    def get_default_stack_on(self):
 
41
        return None
 
42
 
 
43
    def set_default_stack_on(self, value):
 
44
        raise bzr_errors.BzrError("Cannot set configuration")
 
45
 
 
46
 
 
47
class GitControlDirFormat(ControlDirFormat):
 
48
 
 
49
    colocated_branches = True
 
50
    fixed_components = True
 
51
 
 
52
    def __eq__(self, other):
 
53
        return type(self) == type(other)
 
54
 
 
55
    def is_supported(self):
 
56
        return True
 
57
 
 
58
    def network_name(self):
 
59
        return "git"
 
60
 
 
61
 
 
62
class GitDir(ControlDir):
 
63
    """An adapter to the '.git' dir used by git."""
 
64
 
 
65
    def is_supported(self):
 
66
        return True
 
67
 
 
68
    def can_convert_format(self):
 
69
        return False
 
70
 
 
71
    def break_lock(self):
 
72
        pass
 
73
 
 
74
    def cloning_metadir(self, stacked=False):
 
75
        return format_registry.make_bzrdir("default")
 
76
 
 
77
    def checkout_metadir(self, stacked=False):
 
78
        return format_registry.make_bzrdir("default")
 
79
 
 
80
    def _get_selected_ref(self, branch):
 
81
        if branch is None and getattr(self, "_get_selected_branch", False):
 
82
            branch = self._get_selected_branch()
 
83
        if branch is not None:
 
84
            from bzrlib.plugins.git.refs import branch_name_to_ref
 
85
            return branch_name_to_ref(branch, None)
 
86
        segment_parameters = getattr(
 
87
            self.user_transport, "get_segment_parameters", lambda: {})()
 
88
        ref = segment_parameters.get("ref")
 
89
        if ref is not None:
 
90
            ref = urlutils.unescape(ref)
 
91
        return ref
 
92
 
 
93
    def get_config(self):
 
94
        return GitDirConfig()
 
95
 
 
96
    def _available_backup_name(self, base):
 
97
        return osutils.available_backup_name(base, self.root_transport.has)
 
98
 
 
99
    def sprout(self, url, revision_id=None, force_new_repo=False,
 
100
               recurse='down', possible_transports=None,
 
101
               accelerator_tree=None, hardlink=False, stacked=False,
 
102
               source_branch=None, create_tree_if_local=True):
 
103
        from bzrlib.repository import InterRepository
 
104
        from bzrlib.transport.local import LocalTransport
 
105
        from bzrlib.transport import get_transport
 
106
        target_transport = get_transport(url, possible_transports)
 
107
        target_transport.ensure_base()
 
108
        cloning_format = self.cloning_metadir()
 
109
        # Create/update the result branch
 
110
        result = cloning_format.initialize_on_transport(target_transport)
 
111
        source_branch = self.open_branch()
 
112
        source_repository = self.find_repository()
 
113
        try:
 
114
            result_repo = result.find_repository()
 
115
        except bzr_errors.NoRepositoryPresent:
 
116
            result_repo = result.create_repository()
 
117
            target_is_empty = True
 
118
        else:
 
119
            target_is_empty = None # Unknown
 
120
        if stacked:
 
121
            raise bzr_errors.IncompatibleRepositories(source_repository, result_repo)
 
122
        interrepo = InterRepository.get(source_repository, result_repo)
 
123
 
 
124
        if revision_id is not None:
 
125
            determine_wants = interrepo.get_determine_wants_revids(
 
126
                [revision_id], include_tags=True)
 
127
        else:
 
128
            determine_wants = interrepo.determine_wants_all
 
129
        interrepo.fetch_objects(determine_wants=determine_wants,
 
130
            mapping=source_branch.mapping)
 
131
        result_branch = source_branch.sprout(result,
 
132
            revision_id=revision_id, repository=result_repo)
 
133
        if (create_tree_if_local
 
134
            and isinstance(target_transport, LocalTransport)
 
135
            and (result_repo is None or result_repo.make_working_trees())):
 
136
            wt = result.create_workingtree(accelerator_tree=accelerator_tree,
 
137
                hardlink=hardlink, from_branch=result_branch)
 
138
            wt.lock_write()
 
139
            try:
 
140
                if wt.path2id('') is None:
 
141
                    try:
 
142
                        wt.set_root_id(self.open_workingtree.get_root_id())
 
143
                    except bzr_errors.NoWorkingTree:
 
144
                        pass
 
145
            finally:
 
146
                wt.unlock()
 
147
        return result
 
148
 
 
149
    def clone_on_transport(self, transport, revision_id=None,
 
150
        force_new_repo=False, preserve_stacking=False, stacked_on=None,
 
151
        create_prefix=False, use_existing_dir=True, no_tree=False):
 
152
        """See ControlDir.clone_on_transport."""
 
153
        from bzrlib.repository import InterRepository
 
154
        from bzrlib.plugins.git.mapping import default_mapping
 
155
        if no_tree:
 
156
            format = BareLocalGitControlDirFormat()
 
157
        else:
 
158
            format = LocalGitControlDirFormat()
 
159
        (target_repo, target_controldir, stacking, repo_policy) = format.initialize_on_transport_ex(transport, use_existing_dir=use_existing_dir, create_prefix=create_prefix, force_new_repo=force_new_repo)
 
160
        target_git_repo = target_repo._git
 
161
        source_repo = self.open_repository()
 
162
        source_git_repo = source_repo._git
 
163
        interrepo = InterRepository.get(source_repo, target_repo)
 
164
        if revision_id is not None:
 
165
            determine_wants = interrepo.get_determine_wants_revids([revision_id], include_tags=True)
 
166
        else:
 
167
            determine_wants = interrepo.determine_wants_all
 
168
        (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants,
 
169
            mapping=default_mapping)
 
170
        for name, val in refs.iteritems():
 
171
            target_git_repo.refs[name] = val
 
172
        return self.__class__(transport, target_git_repo, format)
 
173
 
 
174
    def find_repository(self):
 
175
        """Find the repository that should be used.
 
176
 
 
177
        This does not require a branch as we use it to find the repo for
 
178
        new branches as well as to hook existing branches up to their
 
179
        repository.
 
180
        """
 
181
        return self.open_repository()
 
182
 
 
183
 
 
184
class LocalGitControlDirFormat(GitControlDirFormat):
 
185
    """The .git directory control format."""
 
186
 
 
187
    bare = False
 
188
 
 
189
    @classmethod
 
190
    def _known_formats(self):
 
191
        return set([LocalGitControlDirFormat()])
 
192
 
 
193
    @property
 
194
    def repository_format(self):
 
195
        from bzrlib.plugins.git.repository import GitRepositoryFormat
 
196
        return GitRepositoryFormat()
 
197
 
 
198
    def get_branch_format(self):
 
199
        from bzrlib.plugins.git.branch import GitBranchFormat
 
200
        return GitBranchFormat()
 
201
 
 
202
    def open(self, transport, _found=None):
 
203
        """Open this directory.
 
204
 
 
205
        """
 
206
        from bzrlib.plugins.git.transportgit import TransportRepo
 
207
        gitrepo = TransportRepo(transport, self.bare)
 
208
        return LocalGitDir(transport, gitrepo, self)
 
209
 
 
210
    def get_format_description(self):
 
211
        return "Local Git Repository"
 
212
 
 
213
    def initialize_on_transport(self, transport):
 
214
        from bzrlib.plugins.git.transportgit import TransportRepo
 
215
        TransportRepo.init(transport, bare=self.bare)
 
216
        return self.open(transport)
 
217
 
 
218
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
 
219
        create_prefix=False, force_new_repo=False, stacked_on=None,
 
220
        stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
 
221
        shared_repo=False, vfs_only=False):
 
222
        def make_directory(transport):
 
223
            transport.mkdir('.')
 
224
            return transport
 
225
        def redirected(transport, e, redirection_notice):
 
226
            trace.note(redirection_notice)
 
227
            return transport._redirected_to(e.source, e.target)
 
228
        try:
 
229
            transport = do_catching_redirections(make_directory, transport,
 
230
                redirected)
 
231
        except bzr_errors.FileExists:
 
232
            if not use_existing_dir:
 
233
                raise
 
234
        except bzr_errors.NoSuchFile:
 
235
            if not create_prefix:
 
236
                raise
 
237
            transport.create_prefix()
 
238
        controldir = self.initialize_on_transport(transport)
 
239
        repository = controldir.open_repository()
 
240
        repository.lock_write()
 
241
        return (repository, controldir, False, CreateRepository(controldir))
 
242
 
 
243
    def is_supported(self):
 
244
        return True
 
245
 
 
246
    def supports_transport(self, transport):
 
247
        try:
 
248
            external_url = transport.external_url()
 
249
        except bzr_errors.InProcessTransport:
 
250
            raise bzr_errors.NotBranchError(path=transport.base)
 
251
        return (external_url.startswith("http:") or
 
252
                external_url.startswith("https:") or
 
253
                external_url.startswith("file:"))
 
254
 
 
255
 
 
256
class BareLocalGitControlDirFormat(LocalGitControlDirFormat):
 
257
 
 
258
    bare = True
 
259
    supports_workingtrees = False
 
260
 
 
261
    def get_format_description(self):
 
262
        return "Local Git Repository (bare)"
 
263
 
 
264
 
 
265
class LocalGitDir(GitDir):
 
266
    """An adapter to the '.git' dir used by git."""
 
267
 
 
268
    def _get_gitrepository_class(self):
 
269
        from bzrlib.plugins.git.repository import LocalGitRepository
 
270
        return LocalGitRepository
 
271
 
 
272
    def __repr__(self):
 
273
        return "<%s at %r>" % (
 
274
            self.__class__.__name__, self.root_transport.base)
 
275
 
 
276
    _gitrepository_class = property(_get_gitrepository_class)
 
277
 
 
278
    @property
 
279
    def user_transport(self):
 
280
        return self.root_transport
 
281
 
 
282
    @property
 
283
    def control_transport(self):
 
284
        return self.transport
 
285
 
 
286
    def __init__(self, transport, gitrepo, format):
 
287
        self._format = format
 
288
        self.root_transport = transport
 
289
        self._mode_check_done = False
 
290
        self._git = gitrepo
 
291
        if gitrepo.bare:
 
292
            self.transport = transport
 
293
        else:
 
294
            self.transport = transport.clone('.git')
 
295
        self._mode_check_done = None
 
296
 
 
297
    def is_control_filename(self, filename):
 
298
        return (filename == '.git' or filename.startswith('.git/'))
 
299
 
 
300
    def _get_symref(self, ref):
 
301
        from dulwich.repo import SYMREF
 
302
        refcontents = self._git.refs.read_ref(ref)
 
303
        if refcontents is None: # no such ref
 
304
            return None
 
305
        if refcontents.startswith(SYMREF):
 
306
            return refcontents[len(SYMREF):].rstrip("\n")
 
307
        return None
 
308
 
 
309
    def set_branch_reference(self, name, target):
 
310
        ref = self._get_selected_ref(name)
 
311
        if ref is None:
 
312
            ref = "HEAD"
 
313
        if not getattr(target, "ref", None):
 
314
            raise bzr_errors.BzrError("Can only set symrefs to Git refs")
 
315
        self._git.refs.set_symbolic_ref(ref, target.ref)
 
316
 
 
317
    def get_branch_reference(self, name=None):
 
318
        ref = self._get_selected_ref(name)
 
319
        if ref is None:
 
320
            ref = "HEAD"
 
321
        target_ref = self._get_symref(ref)
 
322
        if target_ref is not None:
 
323
            return urlutils.join_segment_parameters(
 
324
                self.user_url.rstrip("/"), {"ref": urllib.quote(target_ref, '')})
 
325
        return None
 
326
 
 
327
    def find_branch_format(self, name=None):
 
328
        from bzrlib.plugins.git.branch import (
 
329
            GitBranchFormat,
 
330
            GitSymrefBranchFormat,
 
331
            )
 
332
        ref = self._get_selected_ref(name)
 
333
        if ref is None:
 
334
            ref = "HEAD"
 
335
        if self._get_symref(ref) is not None:
 
336
            return GitSymrefBranchFormat()
 
337
        else:
 
338
            return GitBranchFormat()
 
339
 
 
340
    def get_branch_transport(self, branch_format, name=None):
 
341
        if branch_format is None:
 
342
            return self.transport
 
343
        if isinstance(branch_format, LocalGitControlDirFormat):
 
344
            return self.transport
 
345
        raise bzr_errors.IncompatibleFormat(branch_format, self._format)
 
346
 
 
347
    def get_repository_transport(self, format):
 
348
        if format is None:
 
349
            return self.transport
 
350
        if isinstance(format, LocalGitControlDirFormat):
 
351
            return self.transport
 
352
        raise bzr_errors.IncompatibleFormat(format, self._format)
 
353
 
 
354
    def get_workingtree_transport(self, format):
 
355
        if format is None:
 
356
            return self.transport
 
357
        if isinstance(format, LocalGitControlDirFormat):
 
358
            return self.transport
 
359
        raise bzr_errors.IncompatibleFormat(format, self._format)
 
360
 
 
361
    def open_branch(self, name=None, unsupported=False, ignore_fallbacks=None):
 
362
        """'create' a branch for this dir."""
 
363
        repo = self.open_repository()
 
364
        from bzrlib.plugins.git.branch import LocalGitBranch
 
365
        ref = self._get_selected_ref(name)
 
366
        if ref is None:
 
367
            ref = "HEAD"
 
368
        ref, sha = self._git.refs._follow(ref)
 
369
        if not ref in self._git.refs:
 
370
            raise bzr_errors.NotBranchError(self.root_transport.base,
 
371
                    bzrdir=self)
 
372
        return LocalGitBranch(self, repo, ref)
 
373
 
 
374
    def destroy_branch(self, name=None):
 
375
        refname = self._get_selected_ref(name)
 
376
        if refname is None:
 
377
            refname = "refs/heads/master"
 
378
        try:
 
379
            del self._git.refs[refname]
 
380
        except KeyError:
 
381
            raise bzr_errors.NotBranchError(self.root_transport.base,
 
382
                    bzrdir=self)
 
383
 
 
384
    def destroy_repository(self):
 
385
        raise bzr_errors.UnsupportedOperation(self.destroy_repository, self)
 
386
 
 
387
    def destroy_workingtree(self):
 
388
        raise bzr_errors.UnsupportedOperation(self.destroy_workingtree, self)
 
389
 
 
390
    def needs_format_conversion(self, format=None):
 
391
        return not isinstance(self._format, format.__class__)
 
392
 
 
393
    def list_branches(self):
 
394
        ret = []
 
395
        for name in self._git.refs.keys():
 
396
            if name.startswith("refs/heads/"):
 
397
                ret.append(self.open_branch(name=name))
 
398
        return ret
 
399
 
 
400
    def open_repository(self):
 
401
        """'open' a repository for this dir."""
 
402
        return self._gitrepository_class(self)
 
403
 
 
404
    def open_workingtree(self, recommend_upgrade=True):
 
405
        if not self._git.bare:
 
406
            from dulwich.errors import NoIndexPresent
 
407
            repo = self.open_repository()
 
408
            try:
 
409
                index = repo._git.open_index()
 
410
            except NoIndexPresent:
 
411
                pass
 
412
            else:
 
413
                from bzrlib.plugins.git.workingtree import GitWorkingTree
 
414
                try:
 
415
                    branch = self.open_branch()
 
416
                except bzr_errors.NotBranchError:
 
417
                    pass
 
418
                else:
 
419
                    return GitWorkingTree(self, repo, branch, index)
 
420
        loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
 
421
        raise bzr_errors.NoWorkingTree(loc)
 
422
 
 
423
    def create_repository(self, shared=False):
 
424
        from bzrlib.plugins.git.repository import GitRepositoryFormat
 
425
        if shared:
 
426
            raise bzr_errors.IncompatibleFormat(GitRepositoryFormat(), self._format)
 
427
        return self.open_repository()
 
428
 
 
429
    def create_branch(self, name=None, repository=None,
 
430
                      append_revisions_only=None):
 
431
        refname = self._get_selected_ref(name)
 
432
        from dulwich.protocol import ZERO_SHA
 
433
        # FIXME: This is a bit awkward. Perhaps we should have a
 
434
        # a separate method for changing the default branch?
 
435
        if refname is None:
 
436
            refname = "refs/heads/master"
 
437
            set_head = True
 
438
        else:
 
439
            set_head = False
 
440
 
 
441
        if refname in self._git.refs:
 
442
            raise bzr_errors.AlreadyBranchError(self.base)
 
443
        self._git.refs[refname] = ZERO_SHA
 
444
        if set_head:
 
445
            self._git.refs.set_symbolic_ref("HEAD", refname)
 
446
        branch = self.open_branch(name)
 
447
        if append_revisions_only:
 
448
            branch.set_append_revisions_only(append_revisions_only)
 
449
        return branch
 
450
 
 
451
    def backup_bzrdir(self):
 
452
        if self._git.bare:
 
453
            self.root_transport.copy_tree(".git", ".git.backup")
 
454
            return (self.root_transport.abspath(".git"),
 
455
                    self.root_transport.abspath(".git.backup"))
 
456
        else:
 
457
            raise bzr_errors.BzrError("Unable to backup bare repositories")
 
458
 
 
459
    def create_workingtree(self, revision_id=None, from_branch=None,
 
460
        accelerator_tree=None, hardlink=False):
 
461
        if self._git.bare:
 
462
            raise bzr_errors.UnsupportedOperation(self.create_workingtree, self)
 
463
        from dulwich.index import write_index
 
464
        from dulwich.pack import SHA1Writer
 
465
        f = open(self.transport.local_abspath("index"), 'w+')
 
466
        try:
 
467
            f = SHA1Writer(f)
 
468
            write_index(f, [])
 
469
        finally:
 
470
            f.close()
 
471
        return self.open_workingtree()
 
472
 
 
473
    def _find_or_create_repository(self, force_new_repo=None):
 
474
        return self.create_repository(shared=False)
 
475
 
 
476
    def _find_creation_modes(self):
 
477
        """Determine the appropriate modes for files and directories.
 
478
 
 
479
        They're always set to be consistent with the base directory,
 
480
        assuming that this transport allows setting modes.
 
481
        """
 
482
        # TODO: Do we need or want an option (maybe a config setting) to turn
 
483
        # this off or override it for particular locations? -- mbp 20080512
 
484
        if self._mode_check_done:
 
485
            return
 
486
        self._mode_check_done = True
 
487
        try:
 
488
            st = self.transport.stat('.')
 
489
        except bzr_errors.TransportNotPossible:
 
490
            self._dir_mode = None
 
491
            self._file_mode = None
 
492
        else:
 
493
            # Check the directory mode, but also make sure the created
 
494
            # directories and files are read-write for this user. This is
 
495
            # mostly a workaround for filesystems which lie about being able to
 
496
            # write to a directory (cygwin & win32)
 
497
            if (st.st_mode & 07777 == 00000):
 
498
                # FTP allows stat but does not return dir/file modes
 
499
                self._dir_mode = None
 
500
                self._file_mode = None
 
501
            else:
 
502
                self._dir_mode = (st.st_mode & 07777) | 00700
 
503
                # Remove the sticky and execute bits for files
 
504
                self._file_mode = self._dir_mode & ~07111
 
505
 
 
506
    def _get_file_mode(self):
 
507
        """Return Unix mode for newly created files, or None.
 
508
        """
 
509
        if not self._mode_check_done:
 
510
            self._find_creation_modes()
 
511
        return self._file_mode
 
512
 
 
513
    def _get_dir_mode(self):
 
514
        """Return Unix mode for newly created directories, or None.
 
515
        """
 
516
        if not self._mode_check_done:
 
517
            self._find_creation_modes()
 
518
        return self._dir_mode
 
519