/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

Add basic tests for server.

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