/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 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 urlutils.join_segment_parameters(
 
372
                self.user_url.rstrip("/"), {"ref": urllib.quote(target_ref)})
 
373
        return None
 
374
 
 
375
    def find_branch_format(self, name=None):
 
376
        from bzrlib.plugins.git.branch import (
 
377
            GitBranchFormat,
 
378
            GitSymrefBranchFormat,
 
379
            )
 
380
        ref = self._get_selected_ref(name)
 
381
        if ref is None:
 
382
            ref = "HEAD"
 
383
        if self._get_symref(ref) is not None:
 
384
            return GitSymrefBranchFormat()
 
385
        else:
 
386
            return GitBranchFormat()
 
387
 
 
388
    def get_branch_transport(self, branch_format, name=None):
 
389
        if branch_format is None:
 
390
            return self.transport
 
391
        if isinstance(branch_format, LocalGitControlDirFormat):
 
392
            return self.transport
 
393
        raise bzr_errors.IncompatibleFormat(branch_format, self._format)
 
394
 
 
395
    def get_repository_transport(self, format):
 
396
        if format is None:
 
397
            return self.transport
 
398
        if isinstance(format, LocalGitControlDirFormat):
 
399
            return self.transport
 
400
        raise bzr_errors.IncompatibleFormat(format, self._format)
 
401
 
 
402
    def get_workingtree_transport(self, format):
 
403
        if format is None:
 
404
            return self.transport
 
405
        if isinstance(format, LocalGitControlDirFormat):
 
406
            return self.transport
 
407
        raise bzr_errors.IncompatibleFormat(format, self._format)
 
408
 
 
409
    def open_branch(self, name=None, unsupported=False, ignore_fallbacks=None):
 
410
        """'create' a branch for this dir."""
 
411
        repo = self.open_repository()
 
412
        from bzrlib.plugins.git.branch import LocalGitBranch
 
413
        ref = self._get_selected_ref(name)
 
414
        if ref is None:
 
415
            ref = "HEAD"
 
416
        ref, sha = self._git.refs._follow(ref)
 
417
        if not ref in self._git.refs:
 
418
            raise bzr_errors.NotBranchError(self.root_transport.base,
 
419
                    bzrdir=self)
 
420
        return LocalGitBranch(self, repo, ref, self._lockfiles)
 
421
 
 
422
    def destroy_branch(self, name=None):
 
423
        refname = self._get_selected_ref(name)
 
424
        if refname is None:
 
425
            refname = "refs/heads/master"
 
426
        try:
 
427
            del self._git.refs[refname]
 
428
        except KeyError:
 
429
            raise bzr_errors.NotBranchError(self.root_transport.base,
 
430
                    bzrdir=self)
 
431
 
 
432
    def destroy_repository(self):
 
433
        raise bzr_errors.UnsupportedOperation(self.destroy_repository, self)
 
434
 
 
435
    def destroy_workingtree(self):
 
436
        raise bzr_errors.UnsupportedOperation(self.destroy_workingtree, self)
 
437
 
 
438
    def needs_format_conversion(self, format=None):
 
439
        return not isinstance(self._format, format.__class__)
 
440
 
 
441
    def list_branches(self):
 
442
        ret = []
 
443
        for name in self._git.refs.keys():
 
444
            if name.startswith("refs/heads/"):
 
445
                ret.append(self.open_branch(name=name))
 
446
        return ret
 
447
 
 
448
    def open_repository(self):
 
449
        """'open' a repository for this dir."""
 
450
        return self._gitrepository_class(self, self._lockfiles)
 
451
 
 
452
    def open_workingtree(self, recommend_upgrade=True):
 
453
        if not self._git.bare:
 
454
            from dulwich.errors import NoIndexPresent
 
455
            repo = self.open_repository()
 
456
            try:
 
457
                index = repo._git.open_index()
 
458
            except NoIndexPresent:
 
459
                pass
 
460
            else:
 
461
                from bzrlib.plugins.git.workingtree import GitWorkingTree
 
462
                try:
 
463
                    branch = self.open_branch()
 
464
                except bzr_errors.NotBranchError:
 
465
                    pass
 
466
                else:
 
467
                    return GitWorkingTree(self, repo, branch, index)
 
468
        loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
 
469
        raise bzr_errors.NoWorkingTree(loc)
 
470
 
 
471
    def create_repository(self, shared=False):
 
472
        from bzrlib.plugins.git.repository import GitRepositoryFormat
 
473
        if shared:
 
474
            raise bzr_errors.IncompatibleFormat(GitRepositoryFormat(), self._format)
 
475
        return self.open_repository()
 
476
 
 
477
    def create_branch(self, name=None, repository=None,
 
478
                      append_revisions_only=None):
 
479
        refname = self._get_selected_ref(name)
 
480
        from dulwich.protocol import ZERO_SHA
 
481
        # FIXME: This is a bit awkward. Perhaps we should have a
 
482
        # a separate method for changing the default branch?
 
483
        if refname is None:
 
484
            refname = "refs/heads/master"
 
485
            set_head = True
 
486
        else:
 
487
            set_head = False
 
488
 
 
489
        if refname in self._git.refs:
 
490
            raise bzr_errors.AlreadyBranchError(self.base)
 
491
        self._git.refs[refname] = ZERO_SHA
 
492
        if set_head:
 
493
            self._git.refs.set_symbolic_ref("HEAD", refname)
 
494
        branch = self.open_branch(name)
 
495
        if append_revisions_only:
 
496
            branch.set_append_revisions_only(append_revisions_only)
 
497
        return branch
 
498
 
 
499
    def backup_bzrdir(self):
 
500
        if self._git.bare:
 
501
            self.root_transport.copy_tree(".git", ".git.backup")
 
502
            return (self.root_transport.abspath(".git"),
 
503
                    self.root_transport.abspath(".git.backup"))
 
504
        else:
 
505
            raise bzr_errors.BzrError("Unable to backup bare repositories")
 
506
 
 
507
    def create_workingtree(self, revision_id=None, from_branch=None,
 
508
        accelerator_tree=None, hardlink=False):
 
509
        if self._git.bare:
 
510
            raise bzr_errors.UnsupportedOperation(self.create_workingtree, self)
 
511
        from dulwich.index import write_index
 
512
        from dulwich.pack import SHA1Writer
 
513
        f = open(self.transport.local_abspath("index"), 'w+')
 
514
        try:
 
515
            f = SHA1Writer(f)
 
516
            write_index(f, [])
 
517
        finally:
 
518
            f.close()
 
519
        return self.open_workingtree()
 
520
 
 
521
    def _find_or_create_repository(self, force_new_repo=None):
 
522
        return self.create_repository(shared=False)
 
523
 
 
524
    def _find_creation_modes(self):
 
525
        """Determine the appropriate modes for files and directories.
 
526
 
 
527
        They're always set to be consistent with the base directory,
 
528
        assuming that this transport allows setting modes.
 
529
        """
 
530
        # TODO: Do we need or want an option (maybe a config setting) to turn
 
531
        # this off or override it for particular locations? -- mbp 20080512
 
532
        if self._mode_check_done:
 
533
            return
 
534
        self._mode_check_done = True
 
535
        try:
 
536
            st = self.transport.stat('.')
 
537
        except bzr_errors.TransportNotPossible:
 
538
            self._dir_mode = None
 
539
            self._file_mode = None
 
540
        else:
 
541
            # Check the directory mode, but also make sure the created
 
542
            # directories and files are read-write for this user. This is
 
543
            # mostly a workaround for filesystems which lie about being able to
 
544
            # write to a directory (cygwin & win32)
 
545
            if (st.st_mode & 07777 == 00000):
 
546
                # FTP allows stat but does not return dir/file modes
 
547
                self._dir_mode = None
 
548
                self._file_mode = None
 
549
            else:
 
550
                self._dir_mode = (st.st_mode & 07777) | 00700
 
551
                # Remove the sticky and execute bits for files
 
552
                self._file_mode = self._dir_mode & ~07111
 
553
 
 
554
    def _get_file_mode(self):
 
555
        """Return Unix mode for newly created files, or None.
 
556
        """
 
557
        if not self._mode_check_done:
 
558
            self._find_creation_modes()
 
559
        return self._file_mode
 
560
 
 
561
    def _get_dir_mode(self):
 
562
        """Return Unix mode for newly created directories, or None.
 
563
        """
 
564
        if not self._mode_check_done:
 
565
            self._find_creation_modes()
 
566
        return self._dir_mode
 
567