33
40
from bzrlib.revision import Revision
36
class GitBranchConfig(config.BranchConfig):
37
"""BranchConfig that uses locations.conf in place of branch.conf"""
39
def __init__(self, branch):
40
config.BranchConfig.__init__(self, branch)
41
# do not provide a BranchDataConfig
42
self.option_sources = self.option_sources[0], self.option_sources[2]
44
def set_user_option(self, name, value, local=False):
45
"""Force local to True"""
46
config.BranchConfig.set_user_option(self, name, value, local=True)
49
def gitrevid_from_bzr(revision_id):
50
if revision_id is None:
52
return revision_id[4:]
55
def bzrrevid_from_git(revision_id):
56
return "git:" + revision_id
59
class GitLock(object):
60
"""A lock that thunks through to Git."""
72
class GitLockableFiles(bzrlib.lockable_files.LockableFiles):
73
"""Git specific lockable files abstraction."""
75
def __init__(self, lock):
77
self._transaction = None
78
self._lock_mode = None
82
class GitDir(bzrlib.bzrdir.BzrDir):
83
"""An adapter to the '.git' dir used by git."""
85
def __init__(self, transport, lockfiles, format):
87
self.root_transport = transport
88
self.transport = transport.clone('.git')
89
self._lockfiles = lockfiles
91
def get_branch_transport(self, branch_format):
92
if branch_format is None:
94
if isinstance(branch_format, GitBzrDirFormat):
96
raise errors.IncompatibleFormat(branch_format, self._format)
98
get_repository_transport = get_branch_transport
99
get_workingtree_transport = get_branch_transport
101
def is_supported(self):
104
def open_branch(self, ignored=None):
105
"""'crate' a branch for this dir."""
106
return GitBranch(self, self._lockfiles)
108
def open_repository(self, shared=False):
109
"""'open' a repository for this dir."""
110
return GitRepository(self, self._lockfiles)
112
def open_workingtree(self):
113
loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
114
raise errors.NoWorkingTree(loc)
117
class GitBzrDirFormat(bzrlib.bzrdir.BzrDirFormat):
118
"""The .git directory control format."""
121
def _known_formats(self):
122
return set([GitBzrDirFormat()])
124
def open(self, transport, _create=False, _found=None):
125
"""Open this directory.
127
:param _create: create the git dir on the fly. private to GitDirFormat.
129
# we dont grok readonly - git isn't integrated with transport.
131
if url.startswith('readonly+'):
132
url = url[len('readonly+'):]
133
path = urlutils.local_path_from_url(url)
134
if not transport.has('.git'):
135
raise errors.NotBranchError(path=transport.base)
136
lockfiles = GitLockableFiles(GitLock())
137
return GitDir(transport, lockfiles, self)
140
def probe_transport(klass, transport):
141
"""Our format is present if the transport ends in '.not/'."""
142
# little ugly, but works
144
# delegate to the main opening code. This pays a double rtt cost at the
145
# moment, so perhaps we want probe_transport to return the opened thing
146
# rather than an openener ? or we could return a curried thing with the
147
# dir to open already instantiated ? Needs more thought.
149
format.open(transport)
152
raise errors.NotBranchError(path=transport.base)
153
raise errors.NotBranchError(path=transport.base)
156
bzrlib.bzrdir.BzrDirFormat.register_control_format(GitBzrDirFormat)
159
class GitBranchFormat(bzrlib.branch.BranchFormat):
161
def get_branch_description(self):
165
class GitBranch(bzrlib.branch.Branch):
166
"""An adapter to git repositories for bzr Branch objects."""
168
def __init__(self, gitdir, lockfiles):
170
self.control_files = lockfiles
171
self.repository = GitRepository(gitdir, lockfiles)
172
self.base = gitdir.root_transport.base
173
if '.git' not in gitdir.root_transport.list_dir('.'):
174
raise errors.NotBranchError(self.base)
175
self._format = GitBranchFormat()
177
def lock_write(self):
178
self.control_files.lock_write()
181
def last_revision(self):
182
# perhaps should escape this ?
183
return bzrrevid_from_git(self.repository.git.get_head())
186
def revision_history(self):
187
node = self.last_revision()
188
ancestors = self.repository.get_revision_graph(node)
190
while node is not None:
192
if len(ancestors[node]) > 0:
193
node = ancestors[node][0]
196
return list(reversed(history))
198
def get_config(self):
199
return GitBranchConfig(self)
202
self.control_files.lock_read()
205
self.control_files.unlock()
207
def get_push_location(self):
208
"""See Branch.get_push_location."""
209
push_loc = self.get_config().get_user_option('push_location')
212
def set_push_location(self, location):
213
"""See Branch.set_push_location."""
214
self.get_config().set_user_option('push_location', location,
218
class GitRepository(bzrlib.repository.Repository):
219
"""An adapter to git repositories for bzr."""
221
def __init__(self, gitdir, lockfiles):
223
self.control_files = lockfiles
224
gitdirectory = urlutils.local_path_from_url(gitdir.transport.base)
225
self.git = GitModel(gitdirectory)
226
self._revision_cache = {}
228
def _ancestor_revisions(self, revision_ids):
229
if revision_ids is not None:
230
git_revisions = [gitrevid_from_bzr(r) for r in revision_ids]
233
for lines in self.git.ancestor_lines(git_revisions):
234
yield self.parse_rev(lines)
239
def get_revision_graph(self, revision_id=None):
240
if revision_id is None:
243
revisions = [revision_id]
244
return self.get_revision_graph_with_ghosts(revisions).get_ancestors()
246
def get_revision_graph_with_ghosts(self, revision_ids=None):
247
result = deprecated_graph.Graph()
248
for revision in self._ancestor_revisions(revision_ids):
249
result.add_node(revision.revision_id, revision.parent_ids)
250
self._revision_cache[revision.revision_id] = revision
253
def get_revision(self, revision_id):
254
if revision_id in self._revision_cache:
255
return self._revision_cache[revision_id]
256
raw = self.git.rev_list([gitrevid_from_bzr(revision_id)], max_count=1,
258
return self.parse_rev(raw)
260
def has_revision(self, revision_id):
262
self.get_revision(revision_id)
263
except NoSuchRevision:
268
def get_revisions(self, revisions):
269
return [self.get_revision(r) for r in revisions]
271
def parse_rev(self, raw):
272
# first field is the rev itself.
273
# then its 'field value'
279
revision_id = bzrrevid_from_git(raw[0][:-1])
280
for field in raw[1:]:
281
#if field.startswith('author '):
282
# committer = field[7:]
283
if field.startswith('parent '):
284
parents.append(bzrrevid_from_git(field.split()[1]))
285
elif field.startswith('committer '):
286
commit_fields = field.split()
287
if committer is None:
288
committer = ' '.join(commit_fields[1:-3])
289
timestamp = commit_fields[-2]
290
timezone = commit_fields[-1]
291
elif field.startswith('tree '):
292
tree_id = field.split()[1]
294
log.append(field[4:])
299
result = Revision(revision_id)
300
result.parent_ids = parents
302
result.inventory_sha1 = ""
303
result.timezone = timezone and int(timezone)
304
result.timestamp = float(timestamp)
305
result.committer = committer
306
result.properties['git-tree-id'] = tree_id
309
def revision_trees(self, revids):
311
yield self.revision_tree(revid)
313
def revision_tree(self, revision_id):
314
return GitRevisionTree(self, revision_id)
316
def get_inventory(self, revision_id):
317
revision = self.get_revision(revision_id)
318
inventory = GitInventory(revision_id)
319
tree_id = revision.properties['git-tree-id']
320
type_map = {'blob': 'file', 'tree': 'directory' }
321
def get_inventory(tree_id, prefix):
322
for perms, type, obj_id, name in self.git.get_inventory(tree_id):
323
full_path = prefix + name
328
executable = (perms[-3] in ('1', '3', '5', '7'))
329
entry = GitEntry(full_path, type_map[type], revision_id,
330
text_sha1, executable)
331
inventory.entries[full_path] = entry
333
get_inventory(obj_id, full_path+'/')
334
get_inventory(tree_id, '')
338
class GitRevisionTree(object):
340
def __init__(self, repository, revision_id):
341
self.repository = repository
342
self.revision_id = revision_id
343
self.inventory = repository.get_inventory(revision_id)
345
def get_file(self, file_id):
346
obj_id = self.inventory[file_id].text_sha1
347
lines = self.repository.git.cat_file('blob', obj_id)
348
return iterablefile.IterableFile(lines)
350
def is_executable(self, file_id):
351
return self.inventory[file_id].executable
354
43
class GitInventory(object):
356
45
def __init__(self, revision_id):