/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

Prevent accidentally removing branch.

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