/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to dulwich/dulwich/server.py

More refactoring. Add some direct tests for GitModel.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# server.py -- Implementation of the server side git protocols
2
 
# Copryight (C) 2008 John Carr <john.carr@unrouted.co.uk>
3
 
#
4
 
# This program is free software; you can redistribute it and/or
5
 
# modify it under the terms of the GNU General Public License
6
 
# as published by the Free Software Foundation; version 2
7
 
# or (at your option) any later version of the License.
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., 51 Franklin Street, Fifth Floor, Boston,
17
 
# MA  02110-1301, USA.
18
 
 
19
 
import SocketServer
20
 
from dulwich.protocol import Protocol, ProtocolFile, TCP_GIT_PORT, extract_capabilities
21
 
from dulwich.repo import Repo
22
 
from dulwich.pack import write_pack_data
23
 
import tempfile
24
 
 
25
 
class Backend(object):
26
 
 
27
 
    def get_refs(self):
28
 
        """
29
 
        Get all the refs in the repository
30
 
 
31
 
        :return: dict of name -> sha
32
 
        """
33
 
        raise NotImplementedError
34
 
 
35
 
    def apply_pack(self, refs, read):
36
 
        """ Import a set of changes into a repository and update the refs
37
 
 
38
 
        :param refs: list of tuple(name, sha)
39
 
        :param read: callback to read from the incoming pack
40
 
        """
41
 
        raise NotImplementedError
42
 
 
43
 
    def fetch_objects(self, determine_wants, graph_walker, progress):
44
 
        """
45
 
        Yield the objects required for a list of commits.
46
 
 
47
 
        :param progress: is a callback to send progress messages to the client
48
 
        """
49
 
        raise NotImplementedError
50
 
 
51
 
 
52
 
class GitBackend(Backend):
53
 
 
54
 
    def __init__(self, gitdir=None):
55
 
        self.gitdir = gitdir
56
 
 
57
 
        if not self.gitdir:
58
 
            self.gitdir = tempfile.mkdtemp()
59
 
            Repo.create(self.gitdir)
60
 
 
61
 
        self.repo = Repo(self.gitdir)
62
 
        self.fetch_objects = self.repo.fetch_objects
63
 
        self.get_refs = self.repo.get_refs
64
 
 
65
 
    def apply_pack(self, refs, read):
66
 
        fd, commit = self.repo.object_store.add_thin_pack()
67
 
        fd.write(read())
68
 
        fd.close()
69
 
        commit()
70
 
 
71
 
        for oldsha, sha, ref in refs:
72
 
            if ref == "0" * 40:
73
 
                self.repo.remove_ref(ref)
74
 
            else:
75
 
                self.repo.set_ref(ref, sha)
76
 
 
77
 
        print "pack applied"
78
 
 
79
 
 
80
 
class Handler(object):
81
 
 
82
 
    def __init__(self, backend, read, write):
83
 
        self.backend = backend
84
 
        self.proto = Protocol(read, write)
85
 
 
86
 
    def capabilities(self):
87
 
        return " ".join(self.default_capabilities())
88
 
 
89
 
 
90
 
class UploadPackHandler(Handler):
91
 
 
92
 
    def default_capabilities(self):
93
 
        return ("multi_ack", "side-band-64k", "thin-pack", "ofs-delta")
94
 
 
95
 
    def handle(self):
96
 
        def determine_wants(heads):
97
 
            keys = heads.keys()
98
 
            if keys:
99
 
                self.proto.write_pkt_line("%s %s\x00%s\n" % ( heads[keys[0]], keys[0], self.capabilities()))
100
 
                for k in keys[1:]:
101
 
                    self.proto.write_pkt_line("%s %s\n" % (heads[k], k))
102
 
 
103
 
            # i'm done..
104
 
            self.proto.write("0000")
105
 
 
106
 
            # Now client will either send "0000", meaning that it doesnt want to pull.
107
 
            # or it will start sending want want want commands
108
 
            want = self.proto.read_pkt_line()
109
 
            if want == None:
110
 
                return []
111
 
 
112
 
            want, self.client_capabilities = extract_capabilities(want)
113
 
 
114
 
            want_revs = []
115
 
            while want and want[:4] == 'want':
116
 
                want_revs.append(want[5:45])
117
 
                want = self.proto.read_pkt_line()
118
 
            return want_revs
119
 
 
120
 
        progress = lambda x: self.proto.write_sideband(2, x)
121
 
        write = lambda x: self.proto.write_sideband(1, x)
122
 
 
123
 
        class ProtocolGraphWalker(object):
124
 
 
125
 
            def __init__(self, proto):
126
 
                self.proto = proto
127
 
                self._last_sha = None
128
 
 
129
 
            def ack(self, have_ref):
130
 
                self.proto.write_pkt_line("ACK %s continue\n" % have_ref)
131
 
 
132
 
            def next(self):
133
 
                have = self.proto.read_pkt_line()
134
 
                if have[:4] == 'have':
135
 
                    return have[5:45]
136
 
 
137
 
                #if have[:4] == 'done':
138
 
                #    return None
139
 
 
140
 
                if self._last_sha:
141
 
                    # Oddness: Git seems to resend the last ACK, without the "continue" statement
142
 
                    self.proto.write_pkt_line("ACK %s\n" % self._last_sha)
143
 
 
144
 
                # The exchange finishes with a NAK
145
 
                self.proto.write_pkt_line("NAK\n")
146
 
 
147
 
        graph_walker = ProtocolGraphWalker(self.proto)
148
 
        num_objects, objects_iter = self.backend.fetch_objects(determine_wants, graph_walker, progress)
149
 
 
150
 
        # Do they want any objects?
151
 
        if num_objects == 0:
152
 
            return
153
 
 
154
 
        progress("dul-daemon says what\n")
155
 
        progress("counting objects: %d, done.\n" % num_objects)
156
 
        write_pack_data(ProtocolFile(None, write), objects_iter, num_objects)
157
 
        progress("how was that, then?\n")
158
 
        # we are done
159
 
        self.proto.write("0000")
160
 
 
161
 
 
162
 
class ReceivePackHandler(Handler):
163
 
 
164
 
    def default_capabilities(self):
165
 
        return ("report-status", "delete-refs")
166
 
 
167
 
    def handle(self):
168
 
        refs = self.backend.get_refs().items()
169
 
 
170
 
        if refs:
171
 
            self.proto.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
172
 
            for i in range(1, len(refs)):
173
 
                ref = refs[i]
174
 
                self.proto.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
175
 
        else:
176
 
            self.proto.write_pkt_line("0000000000000000000000000000000000000000 capabilities^{} %s" % self.capabilities())
177
 
 
178
 
        self.proto.write("0000")
179
 
 
180
 
        client_refs = []
181
 
        ref = self.proto.read_pkt_line()
182
 
 
183
 
        # if ref is none then client doesnt want to send us anything..
184
 
        if ref is None:
185
 
            return
186
 
 
187
 
        ref, client_capabilities = extract_capabilities(ref)
188
 
 
189
 
        # client will now send us a list of (oldsha, newsha, ref)
190
 
        while ref:
191
 
            client_refs.append(ref.split())
192
 
            ref = self.proto.read_pkt_line()
193
 
 
194
 
        # backend can now deal with this refs and read a pack using self.read
195
 
        self.backend.apply_pack(client_refs, self.proto.read)
196
 
 
197
 
        # when we have read all the pack from the client, it assumes everything worked OK
198
 
        # there is NO ack from the server before it reports victory.
199
 
 
200
 
 
201
 
class TCPGitRequestHandler(SocketServer.StreamRequestHandler):
202
 
 
203
 
    def handle(self):
204
 
        proto = Protocol(self.rfile.read, self.wfile.write)
205
 
        command, args = proto.read_cmd()
206
 
 
207
 
        # switch case to handle the specific git command
208
 
        if command == 'git-upload-pack':
209
 
            cls = UploadPackHandler
210
 
        elif command == 'git-receive-pack':
211
 
            cls = ReceivePackHandler
212
 
        else:
213
 
            return
214
 
 
215
 
        h = cls(self.server.backend, self.rfile.read, self.wfile.write)
216
 
        h.handle()
217
 
 
218
 
 
219
 
class TCPGitServer(SocketServer.TCPServer):
220
 
 
221
 
    allow_reuse_address = True
222
 
    serve = SocketServer.TCPServer.serve_forever
223
 
 
224
 
    def __init__(self, backend, listen_addr, port=TCP_GIT_PORT):
225
 
        self.backend = backend
226
 
        SocketServer.TCPServer.__init__(self, (listen_addr, port), TCPGitRequestHandler)
227
 
 
228