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