1
# Copyright (C) 2007 Canonical Ltd
 
 
2
# Copyright (C) 2010 Jelmer Vernooij
 
 
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.
 
 
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.
 
 
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
 
 
18
"""An adapter between a Git control dir and a Bazaar ControlDir."""
 
 
24
    version_info as bzrlib_version,
 
 
27
LockWarner = getattr(lockable_files, "_LockWarner", None)
 
 
29
from bzrlib.plugins.git import (
 
 
30
    LocalGitControlDirFormat,
 
 
33
    from bzrlib.controldir import (
 
 
39
    from bzrlib.bzrdir import (
 
 
46
class GitLock(object):
 
 
47
    """A lock that thunks through to Git."""
 
 
49
    def lock_write(self, token=None):
 
 
61
    def validate_token(self, token):
 
 
68
class GitLockableFiles(lockable_files.LockableFiles):
 
 
69
    """Git specific lockable files abstraction."""
 
 
71
    def __init__(self, transport, lock):
 
 
73
        self._transaction = None
 
 
74
        self._lock_mode = None
 
 
75
        self._transport = transport
 
 
76
        if LockWarner is None:
 
 
80
            self._lock_warner = LockWarner(repr(self))
 
 
83
class GitDirConfig(object):
 
 
85
    def get_default_stack_on(self):
 
 
88
    def set_default_stack_on(self, value):
 
 
89
        raise bzr_errors.BzrError("Cannot set configuration")
 
 
92
class GitDir(ControlDir):
 
 
93
    """An adapter to the '.git' dir used by git."""
 
 
95
    def is_supported(self):
 
 
98
    def can_convert_format(self):
 
 
101
    def break_lock(self):
 
 
104
    def cloning_metadir(self, stacked=False):
 
 
105
        return format_registry.make_bzrdir("default")
 
 
107
    def _branch_name_to_ref(self, name):
 
 
108
        raise NotImplementedError(self._branch_name_to_ref)
 
 
110
    if bzrlib_version >= (2, 2):
 
 
111
        def open_branch(self, name=None, unsupported=False, 
 
 
112
            ignore_fallbacks=None):
 
 
113
            return self._open_branch(name=name,
 
 
114
                ignore_fallbacks=ignore_fallbacks, unsupported=unsupported)
 
 
116
        def open_branch(self, ignore_fallbacks=None, unsupported=False):
 
 
117
            return self._open_branch(name=None,
 
 
118
                ignore_fallbacks=ignore_fallbacks, unsupported=unsupported)
 
 
120
    def get_config(self):
 
 
121
        return GitDirConfig()
 
 
124
class LocalGitDir(GitDir):
 
 
125
    """An adapter to the '.git' dir used by git."""
 
 
127
    def _get_gitrepository_class(self):
 
 
128
        from bzrlib.plugins.git.repository import LocalGitRepository
 
 
129
        return LocalGitRepository
 
 
131
    _gitrepository_class = property(_get_gitrepository_class)
 
 
134
    def user_transport(self):
 
 
135
        return self.root_transport
 
 
138
    def control_transport(self):
 
 
139
        return self.transport
 
 
141
    def __init__(self, transport, lockfiles, gitrepo, format):
 
 
142
        self._format = format
 
 
143
        self.root_transport = transport
 
 
144
        self._mode_check_done = False
 
 
147
            self.transport = transport
 
 
149
            self.transport = transport.clone('.git')
 
 
150
        self._lockfiles = lockfiles
 
 
151
        self._mode_check_done = None
 
 
153
    def _branch_name_to_ref(self, name):
 
 
154
        from bzrlib.plugins.git.refs import branch_name_to_ref
 
 
155
        ref = branch_name_to_ref(name, None)
 
 
157
            from dulwich.repo import SYMREF
 
 
158
            refcontents = self._git.refs.read_ref(ref)
 
 
159
            if refcontents.startswith(SYMREF):
 
 
160
                ref = refcontents[len(SYMREF):]
 
 
163
    def is_control_filename(self, filename):
 
 
164
        return filename == '.git' or filename.startswith('.git/')
 
 
166
    def get_branch_transport(self, branch_format, name=None):
 
 
167
        if branch_format is None:
 
 
168
            return self.transport
 
 
169
        if isinstance(branch_format, LocalGitControlDirFormat):
 
 
170
            return self.transport
 
 
171
        raise bzr_errors.IncompatibleFormat(branch_format, self._format)
 
 
173
    def get_repository_transport(self, format):
 
 
175
            return self.transport
 
 
176
        if isinstance(format, LocalGitControlDirFormat):
 
 
177
            return self.transport
 
 
178
        raise bzr_errors.IncompatibleFormat(format, self._format)
 
 
180
    def get_workingtree_transport(self, format):
 
 
182
            return self.transport
 
 
183
        if isinstance(format, LocalGitControlDirFormat):
 
 
184
            return self.transport
 
 
185
        raise bzr_errors.IncompatibleFormat(format, self._format)
 
 
187
    def _open_branch(self, name=None, ignore_fallbacks=None, unsupported=False):
 
 
188
        """'create' a branch for this dir."""
 
 
189
        repo = self.open_repository()
 
 
190
        from bzrlib.plugins.git.branch import LocalGitBranch
 
 
191
        return LocalGitBranch(self, repo, self._branch_name_to_ref(name),
 
 
194
    def destroy_branch(self, name=None):
 
 
195
        refname = self._branch_name_to_ref(name)
 
 
196
        if not refname in self._git.refs:
 
 
197
            raise bzr_errors.NotBranchError(self.root_transport.base,
 
 
199
        del self._git.refs[refname]
 
 
201
    def destroy_repository(self):
 
 
202
        raise bzr_errors.UnsupportedOperation(self.destroy_repository, self)
 
 
204
    def destroy_workingtree(self):
 
 
205
        raise bzr_errors.UnsupportedOperation(self.destroy_workingtree, self)
 
 
207
    def needs_format_conversion(self, format=None):
 
 
208
        return not isinstance(self._format, format.__class__)
 
 
210
    def list_branches(self):
 
 
212
        for name in self._git.get_refs():
 
 
213
            if name.startswith("refs/heads/"):
 
 
214
                ret.append(self.open_branch(name=name))
 
 
217
    def open_repository(self, shared=False):
 
 
218
        """'open' a repository for this dir."""
 
 
219
        return self._gitrepository_class(self, self._lockfiles)
 
 
221
    def open_workingtree(self, recommend_upgrade=True):
 
 
222
        if not self._git.bare:
 
 
223
            from dulwich.errors import NoIndexPresent
 
 
224
            repo = self.open_repository()
 
 
226
                index = repo._git.open_index()
 
 
227
            except NoIndexPresent:
 
 
230
                from bzrlib.plugins.git.workingtree import GitWorkingTree
 
 
232
                    branch = self.open_branch()
 
 
233
                except bzr_errors.NotBranchError:
 
 
236
                    return GitWorkingTree(self, repo, branch, index)
 
 
237
        loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
 
 
238
        raise bzr_errors.NoWorkingTree(loc)
 
 
240
    def create_repository(self, shared=False):
 
 
241
        return self.open_repository()
 
 
243
    def create_branch(self, name=None):
 
 
244
        refname = self._branch_name_to_ref(name)
 
 
245
        from dulwich.protocol import ZERO_SHA
 
 
246
        self._git.refs[refname or "HEAD"] = ZERO_SHA
 
 
247
        return self.open_branch(name)
 
 
249
    def backup_bzrdir(self):
 
 
251
            self.root_transport.copy_tree(".git", ".git.backup")
 
 
252
            return (self.root_transport.abspath(".git"),
 
 
253
                    self.root_transport.abspath(".git.backup"))
 
 
255
            raise bzr_errors.BzrError("Unable to backup bare repositories")
 
 
257
    def create_workingtree(self, revision_id=None, from_branch=None,
 
 
258
        accelerator_tree=None, hardlink=False):
 
 
260
            raise bzr_errors.BzrError("Can't create working tree in a bare repo")
 
 
261
        from dulwich.index import write_index
 
 
262
        from dulwich.pack import SHA1Writer
 
 
263
        f = open(self.transport.local_abspath("index"), 'w+')
 
 
269
        return self.open_workingtree()
 
 
271
    def find_repository(self):
 
 
272
        """Find the repository that should be used.
 
 
274
        This does not require a branch as we use it to find the repo for
 
 
275
        new branches as well as to hook existing branches up to their
 
 
278
        return self.open_repository()
 
 
280
    def _find_creation_modes(self):
 
 
281
        """Determine the appropriate modes for files and directories.
 
 
283
        They're always set to be consistent with the base directory,
 
 
284
        assuming that this transport allows setting modes.
 
 
286
        # TODO: Do we need or want an option (maybe a config setting) to turn
 
 
287
        # this off or override it for particular locations? -- mbp 20080512
 
 
288
        if self._mode_check_done:
 
 
290
        self._mode_check_done = True
 
 
292
            st = self.transport.stat('.')
 
 
293
        except TransportNotPossible:
 
 
294
            self._dir_mode = None
 
 
295
            self._file_mode = None
 
 
297
            # Check the directory mode, but also make sure the created
 
 
298
            # directories and files are read-write for this user. This is
 
 
299
            # mostly a workaround for filesystems which lie about being able to
 
 
300
            # write to a directory (cygwin & win32)
 
 
301
            if (st.st_mode & 07777 == 00000):
 
 
302
                # FTP allows stat but does not return dir/file modes
 
 
303
                self._dir_mode = None
 
 
304
                self._file_mode = None
 
 
306
                self._dir_mode = (st.st_mode & 07777) | 00700
 
 
307
                # Remove the sticky and execute bits for files
 
 
308
                self._file_mode = self._dir_mode & ~07111
 
 
310
    def _get_file_mode(self):
 
 
311
        """Return Unix mode for newly created files, or None.
 
 
313
        if not self._mode_check_done:
 
 
314
            self._find_creation_modes()
 
 
315
        return self._file_mode
 
 
317
    def _get_dir_mode(self):
 
 
318
        """Return Unix mode for newly created directories, or None.
 
 
320
        if not self._mode_check_done:
 
 
321
            self._find_creation_modes()
 
 
322
        return self._dir_mode