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