/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):
0.200.8 by Aaron Bentley
Work on log
229
        return self.get_revision_graph(revision_ids)
230
231
    def get_revision_graph(self, revision_ids=None):
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
232
        result = graph.Graph()
233
        for revision in self._ancestor_revisions(revision_ids):
234
            result.add_node(revision.revision_id, revision.parent_ids)
235
            self._revision_cache[revision.revision_id] = revision
236
        return result
0.200.1 by Robert Collins
Commit initial content.
237
238
    def get_revision(self, revision_id):
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
239
        if revision_id in self._revision_cache:
240
            return self._revision_cache[revision_id]
0.200.5 by Aaron Bentley
Switch to using GitModel
241
        raw = self.git.rev_list([gitrevid_from_bzr(revision_id)], max_count=1,
242
                                header=True)
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
243
        return self.parse_rev(raw)
244
0.200.8 by Aaron Bentley
Work on log
245
    def get_revisions(self, revisions):
246
        return [self.get_revision(r) for r in revisions]
247
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
248
    def parse_rev(self, raw):
0.200.1 by Robert Collins
Commit initial content.
249
        # first field is the rev itself.
250
        # then its 'field value'
251
        # until the EOF??
252
        parents = []
253
        log = []
254
        in_log = False
255
        committer = None
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
256
        revision_id = bzrrevid_from_git(raw[0][:-1])
0.200.1 by Robert Collins
Commit initial content.
257
        for field in raw[1:]:
258
            #if field.startswith('author '):
259
            #    committer = field[7:]
260
            if field.startswith('parent '):
261
                parents.append(bzrrevid_from_git(field.split()[1]))
262
            elif field.startswith('committer '):
263
                commit_fields = field.split()
264
                if committer is None:
265
                    committer = ' '.join(commit_fields[1:-3])
266
                timestamp = commit_fields[-2]
267
                timezone = commit_fields[-1]
268
            elif in_log:
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
269
                log.append(field[4:])
0.200.1 by Robert Collins
Commit initial content.
270
            elif field == '\n':
271
                in_log = True
272
273
        log = ''.join(log)
274
        result = Revision(revision_id)
275
        result.parent_ids = parents
276
        result.message = log
277
        result.inventory_sha1 = ""
278
        result.timezone = timezone and int(timezone)
279
        result.timestamp = float(timestamp)
280
        result.committer = committer 
281
        return result
0.200.5 by Aaron Bentley
Switch to using GitModel
282
283
class GitModel(object):
284
    """API that follows GIT model closely"""
285
286
    def __init__(self, git_dir):
287
        self.git_dir = git_dir
288
289
    def git_command(self, command, args):
290
        args = ' '.join(args)
291
        return 'git --git-dir=%s %s %s' % (self.git_dir, command, args) 
292
293
    def git_lines(self, command, args):
294
        return stgit.git._output_lines(self.git_command(command, args))
295
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
296
    def git_line(self, command, args):
297
        return stgit.git._output_one_line(self.git_command(command, args))
298
0.200.5 by Aaron Bentley
Switch to using GitModel
299
    def rev_list(self, heads, max_count=None, header=False):
300
        args = []
301
        if max_count is not None:
302
            args.append('--max-count=%d' % max_count)
303
        if header is not False:
304
            args.append('--header')
305
        args.extend(heads)
306
        return self.git_lines('rev-list', args)
0.200.6 by Aaron Bentley
Cache revisions from graph_ancestry_with_ghosts
307
308
    def rev_parse(self, git_id):
309
        args = ['--verify', git_id]
310
        return self.git_line('rev-parse', args)
311
312
    def get_head(self):
313
        return self.rev_parse('HEAD')
314
315
    def ancestor_lines(self, revisions):
316
        revision_lines = []
317
        for line in self.rev_list(revisions, header=True):
318
            if line.startswith('\x00'):
319
                yield revision_lines
320
                revision_lines = [line[1:]]
321
            else:
322
                revision_lines.append(line)
323
        assert revision_lines == ['']