/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.200.1 by Robert Collins
Commit initial content.
1
# Copyright (C) 2006 Canonical Ltd
2
# Authors: Robert Collins <robert.collins@canonical.com>
3
#
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.
8
#
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.
13
#
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
17
18
19
"""A GIT branch and repository format implementation for bzr."""
20
21
0.200.2 by Aaron Bentley
Get viz working, with new -r support
22
from StringIO import StringIO
23
0.200.1 by Robert Collins
Commit initial content.
24
import stgit
25
import stgit.git as git
26
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
27
from bzrlib import config, graph, urlutils
0.200.1 by Robert Collins
Commit initial content.
28
from bzrlib.decorators import *
29
import bzrlib.branch
30
import bzrlib.bzrdir
31
import bzrlib.errors as errors
32
import bzrlib.repository
33
from bzrlib.revision import Revision
34
35
0.200.4 by Aaron Bentley
Supply a BranchConfig instead of a Transport
36
class GitBranchConfig(config.BranchConfig):
37
    """BranchConfig that uses locations.conf in place of branch.conf""" 
38
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]
43
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)
0.200.2 by Aaron Bentley
Get viz working, with new -r support
47
48
0.200.1 by Robert Collins
Commit initial content.
49
def gitrevid_from_bzr(revision_id):
50
    return revision_id[4:]
51
52
53
def bzrrevid_from_git(revision_id):
54
    return "git:" + revision_id
55
56
57
class GitLock(object):
58
    """A lock that thunks through to Git."""
59
60
    def lock_write(self):
61
        pass
62
63
    def lock_read(self):
64
        pass
65
66
    def unlock(self):
67
        pass
68
69
70
class GitLockableFiles(bzrlib.lockable_files.LockableFiles):
71
    """Git specific lockable files abstraction."""
72
73
    def __init__(self, lock):
74
        self._lock = lock
75
        self._transaction = None
76
        self._lock_mode = None
77
        self._lock_count = 0
78
79
80
class GitDir(bzrlib.bzrdir.BzrDir):
81
    """An adapter to the '.git' dir used by git."""
82
83
    def __init__(self, transport, lockfiles, format):
84
        self._format = format
85
        self.root_transport = transport
86
        self.transport = transport.clone('.git')
87
        self._lockfiles = lockfiles
88
89
    def get_branch_transport(self, branch_format):
90
        if branch_format is None:
91
            return self.transport
92
        if isinstance(branch_format, GitBzrDirFormat):
93
            return self.transport
94
        raise errors.IncompatibleFormat(branch_format, self._format)
95
96
    get_repository_transport = get_branch_transport
97
    get_workingtree_transport = get_branch_transport
98
99
    def is_supported(self):
100
        return True
101
102
    def open_branch(self, ignored=None):
103
        """'crate' a branch for this dir."""
104
        return GitBranch(self, self._lockfiles)
105
106
    def open_repository(self, shared=False):
107
        """'open' a repository for this dir."""
108
        return GitRepository(self._gitrepo, self, self._lockfiles)
109
0.200.3 by Aaron Bentley
Avoid ugly errors opening working trees
110
    def open_workingtree(self):
111
        loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
112
        raise errors.NoWorkingTree(loc)
113
0.200.1 by Robert Collins
Commit initial content.
114
115
class GitBzrDirFormat(bzrlib.bzrdir.BzrDirFormat):
116
    """The .git directory control format."""
117
118
    @classmethod
119
    def _known_formats(self):
120
        return set([GitBzrDirFormat()])
121
122
    def open(self, transport, _create=False, _found=None):
123
        """Open this directory.
124
        
125
        :param _create: create the git dir on the fly. private to GitDirFormat.
126
        """
127
        # we dont grok readonly - git isn't integrated with transport.
128
        url = transport.base
129
        if url.startswith('readonly+'):
130
            url = url[len('readonly+'):]
131
        if url.startswith('file://'):
132
            url = url[len('file://'):]
133
        url = url.encode('utf8')
134
        lockfiles = GitLockableFiles(GitLock())
135
        return GitDir(transport, lockfiles, self)
136
137
    @classmethod
138
    def probe_transport(klass, transport):
139
        """Our format is present if the transport ends in '.not/'."""
140
        # little ugly, but works
141
        format = klass() 
142
        # try a manual probe first, its a little faster perhaps ?
143
        if transport.has('.git'):
144
            return format
145
        # delegate to the main opening code. This pays a double rtt cost at the
146
        # moment, so perhaps we want probe_transport to return the opened thing
147
        # rather than an openener ? or we could return a curried thing with the
148
        # dir to open already instantiated ? Needs more thought.
149
        try:
150
            format.open(transport)
151
            return format
152
        except Exception, e:
153
            raise errors.NotBranchError(path=transport.base)
154
        raise errors.NotBranchError(path=transport.base)
155
156
157
bzrlib.bzrdir.BzrDirFormat.register_control_format(GitBzrDirFormat)
158
159
160
class GitBranch(bzrlib.branch.Branch):
161
    """An adapter to git repositories for bzr Branch objects."""
162
163
    def __init__(self, gitdir, lockfiles):
164
        self.bzrdir = gitdir
165
        self.control_files = lockfiles
166
        self.repository = GitRepository(gitdir, lockfiles)
167
        self.base = gitdir.root_transport.base
0.200.7 by Aaron Bentley
open_branch fails in non-branches
168
        if '.git' not in gitdir.root_transport.list_dir('.'):
169
            raise errors.NotBranchError(self.base)
0.200.1 by Robert Collins
Commit initial content.
170
171
    def lock_write(self):
172
        self.control_files.lock_write()
173
174
    @needs_read_lock
175
    def last_revision(self):
176
        # perhaps should escape this ?
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
177
        return bzrrevid_from_git(self.repository.git.get_head())
0.200.1 by Robert Collins
Commit initial content.
178
0.200.4 by Aaron Bentley
Supply a BranchConfig instead of a Transport
179
    @needs_read_lock
0.200.2 by Aaron Bentley
Get viz working, with new -r support
180
    def revision_history(self):
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
181
        node = self.last_revision()
182
        graph = self.repository.get_revision_graph_with_ghosts([node])
183
        ancestors = graph.get_ancestors()
184
        history = []
185
        while node is not None:
186
            history.append(node)
187
            if len(ancestors[node]) > 0:
188
                node = ancestors[node][0]
189
            else:
190
                node = None
0.200.2 by Aaron Bentley
Get viz working, with new -r support
191
        return list(reversed(history))
192
0.200.4 by Aaron Bentley
Supply a BranchConfig instead of a Transport
193
    def get_config(self):
194
        return GitBranchConfig(self)
195
0.200.1 by Robert Collins
Commit initial content.
196
    def lock_read(self):
197
        self.control_files.lock_read()
198
199
    def unlock(self):
200
        self.control_files.unlock()
201
0.200.4 by Aaron Bentley
Supply a BranchConfig instead of a Transport
202
    def get_push_location(self):
203
        """See Branch.get_push_location."""
204
        push_loc = self.get_config().get_user_option('push_location')
205
        return push_loc
206
207
    def set_push_location(self, location):
208
        """See Branch.set_push_location."""
209
        self.get_config().set_user_option('push_location', location, 
210
                                          local=True)
211
0.200.1 by Robert Collins
Commit initial content.
212
213
class GitRepository(bzrlib.repository.Repository):
214
    """An adapter to git repositories for bzr."""
215
216
    def __init__(self, gitdir, lockfiles):
217
        self.bzrdir = gitdir
218
        self.control_files = lockfiles
0.200.5 by Aaron Bentley
Switch to using GitModel
219
        gitdirectory = urlutils.local_path_from_url(gitdir.transport.base)
220
        self.git = GitModel(gitdirectory)
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
221
        self._revision_cache = {}
222
223
    def _ancestor_revisions(self, revision_ids):
224
        git_revisions = [gitrevid_from_bzr(r) for r in revision_ids]
225
        for lines in self.git.ancestor_lines(git_revisions):
226
            yield self.parse_rev(lines)
227
228
    def get_revision_graph_with_ghosts(self, revision_ids=None):
229
        result = graph.Graph()
230
        for revision in self._ancestor_revisions(revision_ids):
231
            result.add_node(revision.revision_id, revision.parent_ids)
232
            self._revision_cache[revision.revision_id] = revision
233
        return result
0.200.1 by Robert Collins
Commit initial content.
234
235
    def get_revision(self, revision_id):
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
236
        if revision_id in self._revision_cache:
237
            return self._revision_cache[revision_id]
0.200.5 by Aaron Bentley
Switch to using GitModel
238
        raw = self.git.rev_list([gitrevid_from_bzr(revision_id)], max_count=1,
239
                                header=True)
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
240
        return self.parse_rev(raw)
241
242
    def parse_rev(self, raw):
0.200.1 by Robert Collins
Commit initial content.
243
        # first field is the rev itself.
244
        # then its 'field value'
245
        # until the EOF??
246
        parents = []
247
        log = []
248
        in_log = False
249
        committer = None
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
250
        revision_id = bzrrevid_from_git(raw[0][:-1])
0.200.1 by Robert Collins
Commit initial content.
251
        for field in raw[1:]:
252
            #if field.startswith('author '):
253
            #    committer = field[7:]
254
            if field.startswith('parent '):
255
                parents.append(bzrrevid_from_git(field.split()[1]))
256
            elif field.startswith('committer '):
257
                commit_fields = field.split()
258
                if committer is None:
259
                    committer = ' '.join(commit_fields[1:-3])
260
                timestamp = commit_fields[-2]
261
                timezone = commit_fields[-1]
262
            elif in_log:
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
263
                log.append(field[4:])
0.200.1 by Robert Collins
Commit initial content.
264
            elif field == '\n':
265
                in_log = True
266
267
        log = ''.join(log)
268
        result = Revision(revision_id)
269
        result.parent_ids = parents
270
        result.message = log
271
        result.inventory_sha1 = ""
272
        result.timezone = timezone and int(timezone)
273
        result.timestamp = float(timestamp)
274
        result.committer = committer 
275
        return result
0.200.5 by Aaron Bentley
Switch to using GitModel
276
277
class GitModel(object):
278
    """API that follows GIT model closely"""
279
280
    def __init__(self, git_dir):
281
        self.git_dir = git_dir
282
283
    def git_command(self, command, args):
284
        args = ' '.join(args)
285
        return 'git --git-dir=%s %s %s' % (self.git_dir, command, args) 
286
287
    def git_lines(self, command, args):
288
        return stgit.git._output_lines(self.git_command(command, args))
289
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
290
    def git_line(self, command, args):
291
        return stgit.git._output_one_line(self.git_command(command, args))
292
0.200.5 by Aaron Bentley
Switch to using GitModel
293
    def rev_list(self, heads, max_count=None, header=False):
294
        args = []
295
        if max_count is not None:
296
            args.append('--max-count=%d' % max_count)
297
        if header is not False:
298
            args.append('--header')
299
        args.extend(heads)
300
        return self.git_lines('rev-list', args)
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
301
302
    def rev_parse(self, git_id):
303
        args = ['--verify', git_id]
304
        return self.git_line('rev-parse', args)
305
306
    def get_head(self):
307
        return self.rev_parse('HEAD')
308
309
    def ancestor_lines(self, revisions):
310
        revision_lines = []
311
        for line in self.rev_list(revisions, header=True):
312
            if line.startswith('\x00'):
313
                yield revision_lines
314
                revision_lines = [line[1:]]
315
            else:
316
                revision_lines.append(line)
317
        assert revision_lines == ['']