/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

Raise SettingFileIdUnsupported

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