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
    BareLocalGitControlDirFormat,
 
 
31
    LocalGitControlDirFormat,
 
 
34
    from bzrlib.controldir import (
 
 
40
    from bzrlib.bzrdir import (
 
 
47
class GitLock(object):
 
 
48
    """A lock that thunks through to Git."""
 
 
50
    def lock_write(self, token=None):
 
 
62
    def validate_token(self, token):
 
 
69
class GitLockableFiles(lockable_files.LockableFiles):
 
 
70
    """Git specific lockable files abstraction."""
 
 
72
    def __init__(self, transport, lock):
 
 
74
        self._transaction = None
 
 
75
        self._lock_mode = None
 
 
76
        self._transport = transport
 
 
77
        if LockWarner is None:
 
 
81
            self._lock_warner = LockWarner(repr(self))
 
 
84
class GitDirConfig(object):
 
 
86
    def get_default_stack_on(self):
 
 
89
    def set_default_stack_on(self, value):
 
 
90
        raise bzr_errors.BzrError("Cannot set configuration")
 
 
93
class GitDir(ControlDir):
 
 
94
    """An adapter to the '.git' dir used by git."""
 
 
96
    def is_supported(self):
 
 
99
    def can_convert_format(self):
 
 
102
    def break_lock(self):
 
 
105
    def cloning_metadir(self, stacked=False):
 
 
106
        return format_registry.make_bzrdir("default")
 
 
108
    def _branch_name_to_ref(self, name):
 
 
109
        raise NotImplementedError(self._branch_name_to_ref)
 
 
111
    if bzrlib_version >= (2, 2):
 
 
112
        def open_branch(self, name=None, unsupported=False, 
 
 
113
            ignore_fallbacks=None):
 
 
114
            return self._open_branch(name=name,
 
 
115
                ignore_fallbacks=ignore_fallbacks, unsupported=unsupported)
 
 
117
        def open_branch(self, ignore_fallbacks=None, unsupported=False):
 
 
118
            return self._open_branch(name=None,
 
 
119
                ignore_fallbacks=ignore_fallbacks, unsupported=unsupported)
 
 
121
    def get_config(self):
 
 
122
        return GitDirConfig()
 
 
125
class LocalGitDir(GitDir):
 
 
126
    """An adapter to the '.git' dir used by git."""
 
 
128
    def _get_gitrepository_class(self):
 
 
129
        from bzrlib.plugins.git.repository import LocalGitRepository
 
 
130
        return LocalGitRepository
 
 
132
    _gitrepository_class = property(_get_gitrepository_class)
 
 
135
    def user_transport(self):
 
 
136
        return self.root_transport
 
 
139
    def control_transport(self):
 
 
140
        return self.transport
 
 
142
    def __init__(self, transport, lockfiles, gitrepo, format):
 
 
143
        self._format = format
 
 
144
        self.root_transport = transport
 
 
145
        self._mode_check_done = False
 
 
148
            self.transport = transport
 
 
150
            self.transport = transport.clone('.git')
 
 
151
        self._lockfiles = lockfiles
 
 
152
        self._mode_check_done = None
 
 
154
    def _branch_name_to_ref(self, name):
 
 
155
        from bzrlib.plugins.git.refs import branch_name_to_ref
 
 
156
        ref = branch_name_to_ref(name, None)
 
 
158
            from dulwich.repo import SYMREF
 
 
159
            refcontents = self._git.refs.read_ref(ref)
 
 
160
            if refcontents.startswith(SYMREF):
 
 
161
                ref = refcontents[len(SYMREF):]
 
 
164
    def is_control_filename(self, filename):
 
 
165
        return filename == '.git' or filename.startswith('.git/')
 
 
167
    def get_branch_transport(self, branch_format, name=None):
 
 
168
        if branch_format is None:
 
 
169
            return self.transport
 
 
170
        if isinstance(branch_format, LocalGitControlDirFormat):
 
 
171
            return self.transport
 
 
172
        raise bzr_errors.IncompatibleFormat(branch_format, self._format)
 
 
174
    def get_repository_transport(self, format):
 
 
176
            return self.transport
 
 
177
        if isinstance(format, LocalGitControlDirFormat):
 
 
178
            return self.transport
 
 
179
        raise bzr_errors.IncompatibleFormat(format, self._format)
 
 
181
    def get_workingtree_transport(self, format):
 
 
183
            return self.transport
 
 
184
        if isinstance(format, LocalGitControlDirFormat):
 
 
185
            return self.transport
 
 
186
        raise bzr_errors.IncompatibleFormat(format, self._format)
 
 
188
    def _open_branch(self, name=None, ignore_fallbacks=None, unsupported=False):
 
 
189
        """'create' a branch for this dir."""
 
 
190
        repo = self.open_repository()
 
 
191
        from bzrlib.plugins.git.branch import LocalGitBranch
 
 
192
        return LocalGitBranch(self, repo, self._branch_name_to_ref(name),
 
 
195
    def destroy_branch(self, name=None):
 
 
196
        refname = self._branch_name_to_ref(name)
 
 
197
        if not refname in self._git.refs:
 
 
198
            raise bzr_errors.NotBranchError(self.root_transport.base,
 
 
200
        del self._git.refs[refname]
 
 
202
    def destroy_repository(self):
 
 
203
        raise bzr_errors.UnsupportedOperation(self.destroy_repository, self)
 
 
205
    def destroy_workingtree(self):
 
 
206
        raise bzr_errors.UnsupportedOperation(self.destroy_workingtree, self)
 
 
208
    def needs_format_conversion(self, format=None):
 
 
209
        return not isinstance(self._format, format.__class__)
 
 
211
    def list_branches(self):
 
 
213
        for name in self._git.get_refs():
 
 
214
            if name.startswith("refs/heads/"):
 
 
215
                ret.append(self.open_branch(name=name))
 
 
218
    def open_repository(self, shared=False):
 
 
219
        """'open' a repository for this dir."""
 
 
220
        return self._gitrepository_class(self, self._lockfiles)
 
 
222
    def open_workingtree(self, recommend_upgrade=True):
 
 
223
        if not self._git.bare:
 
 
224
            from dulwich.errors import NoIndexPresent
 
 
225
            repo = self.open_repository()
 
 
227
                index = repo._git.open_index()
 
 
228
            except NoIndexPresent:
 
 
231
                from bzrlib.plugins.git.workingtree import GitWorkingTree
 
 
233
                    branch = self.open_branch()
 
 
234
                except bzr_errors.NotBranchError:
 
 
237
                    return GitWorkingTree(self, repo, branch, index)
 
 
238
        loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
 
 
239
        raise bzr_errors.NoWorkingTree(loc)
 
 
241
    def create_repository(self, shared=False):
 
 
242
        return self.open_repository()
 
 
244
    def create_branch(self, name=None):
 
 
245
        refname = self._branch_name_to_ref(name)
 
 
246
        from dulwich.protocol import ZERO_SHA
 
 
247
        self._git.refs[refname or "HEAD"] = ZERO_SHA
 
 
248
        return self.open_branch(name)
 
 
250
    def backup_bzrdir(self):
 
 
252
            self.root_transport.copy_tree(".git", ".git.backup")
 
 
253
            return (self.root_transport.abspath(".git"),
 
 
254
                    self.root_transport.abspath(".git.backup"))
 
 
256
            raise bzr_errors.BzrError("Unable to backup bare repositories")
 
 
258
    def create_workingtree(self, revision_id=None, from_branch=None,
 
 
259
        accelerator_tree=None, hardlink=False):
 
 
261
            raise bzr_errors.BzrError("Can't create working tree in a bare repo")
 
 
262
        from dulwich.index import write_index
 
 
263
        from dulwich.pack import SHA1Writer
 
 
264
        f = open(self.transport.local_abspath("index"), 'w+')
 
 
270
        return self.open_workingtree()
 
 
272
    def find_repository(self):
 
 
273
        """Find the repository that should be used.
 
 
275
        This does not require a branch as we use it to find the repo for
 
 
276
        new branches as well as to hook existing branches up to their
 
 
279
        return self.open_repository()
 
 
281
    def _find_creation_modes(self):
 
 
282
        """Determine the appropriate modes for files and directories.
 
 
284
        They're always set to be consistent with the base directory,
 
 
285
        assuming that this transport allows setting modes.
 
 
287
        # TODO: Do we need or want an option (maybe a config setting) to turn
 
 
288
        # this off or override it for particular locations? -- mbp 20080512
 
 
289
        if self._mode_check_done:
 
 
291
        self._mode_check_done = True
 
 
293
            st = self.transport.stat('.')
 
 
294
        except TransportNotPossible:
 
 
295
            self._dir_mode = None
 
 
296
            self._file_mode = None
 
 
298
            # Check the directory mode, but also make sure the created
 
 
299
            # directories and files are read-write for this user. This is
 
 
300
            # mostly a workaround for filesystems which lie about being able to
 
 
301
            # write to a directory (cygwin & win32)
 
 
302
            if (st.st_mode & 07777 == 00000):
 
 
303
                # FTP allows stat but does not return dir/file modes
 
 
304
                self._dir_mode = None
 
 
305
                self._file_mode = None
 
 
307
                self._dir_mode = (st.st_mode & 07777) | 00700
 
 
308
                # Remove the sticky and execute bits for files
 
 
309
                self._file_mode = self._dir_mode & ~07111
 
 
311
    def _get_file_mode(self):
 
 
312
        """Return Unix mode for newly created files, or None.
 
 
314
        if not self._mode_check_done:
 
 
315
            self._find_creation_modes()
 
 
316
        return self._file_mode
 
 
318
    def _get_dir_mode(self):
 
 
319
        """Return Unix mode for newly created directories, or None.
 
 
321
        if not self._mode_check_done:
 
 
322
            self._find_creation_modes()
 
 
323
        return self._dir_mode