/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/server.py

  • Committer: Jelmer Vernooij
  • Date: 2009-01-14 18:24:38 UTC
  • mto: (0.222.3 dulwich)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@samba.org-20090114182438-c0tn5eczyupi4ztn
Fix download url, add version number.

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_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
        objects = list(self.backend.fetch_objects(determine_wants, graph_walker, progress))
 
149
        progress("dul-daemon says what\n")
 
150
        progress("counting objects: %d, done.\n" % len(objects))
 
151
        write_pack_data(ProtocolFile(None, write), objects, len(objects))
 
152
        progress("how was that, then?\n")
 
153
        # we are done
 
154
        self.proto.write("0000")
 
155
 
 
156
 
 
157
class ReceivePackHandler(Handler):
 
158
 
 
159
    def default_capabilities(self):
 
160
        return ("report-status", "delete-refs")
 
161
 
 
162
    def handle(self):
 
163
        refs = self.backend.get_refs().items()
 
164
 
 
165
        if refs:
 
166
            self.proto.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
 
167
            for i in range(1, len(refs)):
 
168
                ref = refs[i]
 
169
                self.proto.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
 
170
        else:
 
171
            self.proto.write_pkt_line("0000000000000000000000000000000000000000 capabilities^{} %s" % self.capabilities())
 
172
 
 
173
        self.proto.write("0000")
 
174
 
 
175
        client_refs = []
 
176
        ref = self.proto.read_pkt_line()
 
177
 
 
178
        # if ref is none then client doesnt want to send us anything..
 
179
        if ref is None:
 
180
            return
 
181
 
 
182
        ref, client_capabilities = extract_capabilities(ref)
 
183
 
 
184
        # client will now send us a list of (oldsha, newsha, ref)
 
185
        while ref:
 
186
            client_refs.append(ref.split())
 
187
            ref = self.proto.read_pkt_line()
 
188
 
 
189
        # backend can now deal with this refs and read a pack using self.read
 
190
        self.backend.apply_pack(client_refs, self.proto.read)
 
191
 
 
192
        # when we have read all the pack from the client, it assumes everything worked OK
 
193
        # there is NO ack from the server before it reports victory.
 
194
 
 
195
 
 
196
class TCPGitRequestHandler(SocketServer.StreamRequestHandler):
 
197
 
 
198
    def handle(self):
 
199
        proto = Protocol(self.rfile.read, self.wfile.write)
 
200
        command, args = proto.read_cmd()
 
201
 
 
202
        # switch case to handle the specific git command
 
203
        if command == 'git-upload-pack':
 
204
            cls = UploadPackHandler
 
205
        elif command == 'git-receive-pack':
 
206
            cls = ReceivePackHandler
 
207
        else:
 
208
            return
 
209
 
 
210
        h = cls(self.server.backend, self.rfile.read, self.wfile.write)
 
211
        h.handle()
 
212
 
 
213
 
 
214
class TCPGitServer(SocketServer.TCPServer):
 
215
 
 
216
    allow_reuse_address = True
 
217
    serve = SocketServer.TCPServer.serve_forever
 
218
 
 
219
    def __init__(self, backend, listen_addr, port=TCP_GIT_PORT):
 
220
        self.backend = backend
 
221
        SocketServer.TCPServer.__init__(self, (listen_addr, port), TCPGitRequestHandler)
 
222
 
 
223