/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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-04-13 23:16:57 UTC
  • mfrom: (1662.1.1 bzr.mbp.integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060413231657-bce3d67d3e7a4f2b
(mbp/olaf) push/pull/merge --remember improvements

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)