/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

  • Committer: Aaron Bentley
  • Date: 2009-03-11 07:24:19 UTC
  • mfrom: (4110 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4130.
  • Revision ID: aaron@aaronbentley.com-20090311072419-029z2ec0lxfc6ns1
Merge bzr.dev into send-hookage

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
 
# 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 PackData, Pack, write_pack_data
23
 
import os, sha, 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
 
        # store the incoming pack in the repository
67
 
        fd, name = tempfile.mkstemp(suffix='.pack', prefix='pack-', dir=self.repo.pack_dir())
68
 
        os.write(fd, read())
69
 
        os.close(fd)
70
 
 
71
 
        # strip '.pack' off our filename
72
 
        basename = name[:-5]
73
 
 
74
 
        # generate an index for it
75
 
        pd = PackData(name)
76
 
        pd.create_index_v2(basename+".idx")
77
 
 
78
 
        for oldsha, sha, ref in refs:
79
 
            if ref == "0" * 40:
80
 
                self.repo.remove_ref(ref)
81
 
            else:
82
 
                self.repo.set_ref(ref, sha)
83
 
 
84
 
        print "pack applied"
85
 
 
86
 
 
87
 
class Handler(object):
88
 
 
89
 
    def __init__(self, backend, read, write):
90
 
        self.backend = backend
91
 
        self.proto = Protocol(read, write)
92
 
 
93
 
    def capabilities(self):
94
 
        return " ".join(self.default_capabilities())
95
 
 
96
 
 
97
 
class UploadPackHandler(Handler):
98
 
 
99
 
    def default_capabilities(self):
100
 
        return ("multi_ack", "side-band-64k", "thin-pack", "ofs-delta")
101
 
 
102
 
    def handle(self):
103
 
        def determine_wants(heads):
104
 
            keys = heads.keys()
105
 
            if keys:
106
 
                self.proto.write_pkt_line("%s %s\x00%s\n" % ( heads[keys[0]], keys[0], self.capabilities()))
107
 
                for k in keys[1:]:
108
 
                    self.proto.write_pkt_line("%s %s\n" % (heads[k], k))
109
 
 
110
 
            # i'm done..
111
 
            self.proto.write("0000")
112
 
 
113
 
            # Now client will either send "0000", meaning that it doesnt want to pull.
114
 
            # or it will start sending want want want commands
115
 
            want = self.proto.read_pkt_line()
116
 
            if want == None:
117
 
                return []
118
 
 
119
 
            want, self.client_capabilities = extract_capabilities(want)
120
 
 
121
 
            want_revs = []
122
 
            while want and want[:4] == 'want':
123
 
                want_revs.append(want[5:45])
124
 
                want = self.proto.read_pkt_line()
125
 
            return want_revs
126
 
 
127
 
        progress = lambda x: self.proto.write_sideband(2, x)
128
 
        write = lambda x: self.proto.write_sideband(1, x)
129
 
 
130
 
        class ProtocolGraphWalker(object):
131
 
 
132
 
            def __init__(self, proto):
133
 
                self.proto = proto
134
 
                self._last_sha = None
135
 
 
136
 
            def ack(self, have_ref):
137
 
                self.proto.write_pkt_line("ACK %s continue\n" % have_ref)
138
 
 
139
 
            def next(self):
140
 
                have = self.proto.read_pkt_line()
141
 
                if have[:4] == 'have':
142
 
                    return have[5:45]
143
 
 
144
 
                #if have[:4] == 'done':
145
 
                #    return None
146
 
 
147
 
                if self._last_sha:
148
 
                    # Oddness: Git seems to resend the last ACK, without the "continue" statement
149
 
                    self.proto.write_pkt_line("ACK %s\n" % self._last_sha)
150
 
 
151
 
                # The exchange finishes with a NAK
152
 
                self.proto.write_pkt_line("NAK\n")
153
 
 
154
 
        graph_walker = ProtocolGraphWalker(self.proto)
155
 
        objects = list(self.backend.fetch_objects(determine_wants, graph_walker, progress))
156
 
        progress("dul-daemon says what\n")
157
 
        progress("counting objects: %d, done.\n" % len(objects))
158
 
        write_pack_data(ProtocolFile(None, write), objects, len(objects))
159
 
        progress("how was that, then?\n")
160
 
        # we are done
161
 
        self.proto.write("0000")
162
 
 
163
 
 
164
 
class ReceivePackHandler(Handler):
165
 
 
166
 
    def default_capabilities(self):
167
 
        return ("report-status", "delete-refs")
168
 
 
169
 
    def handle(self):
170
 
        refs = self.backend.get_refs().items()
171
 
 
172
 
        if refs:
173
 
            self.proto.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
174
 
            for i in range(1, len(refs)):
175
 
                ref = refs[i]
176
 
                self.proto.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
177
 
        else:
178
 
            self.proto.write_pkt_line("0000000000000000000000000000000000000000 capabilities^{} %s" % self.capabilities())
179
 
 
180
 
        self.proto.write("0000")
181
 
 
182
 
        client_refs = []
183
 
        ref = self.proto.read_pkt_line()
184
 
 
185
 
        # if ref is none then client doesnt want to send us anything..
186
 
        if ref is None:
187
 
            return
188
 
 
189
 
        ref, client_capabilities = extract_capabilities(ref)
190
 
 
191
 
        # client will now send us a list of (oldsha, newsha, ref)
192
 
        while ref:
193
 
            client_refs.append(ref.split())
194
 
            ref = self.proto.read_pkt_line()
195
 
 
196
 
        # backend can now deal with this refs and read a pack using self.read
197
 
        self.backend.apply_pack(client_refs, self.proto.read)
198
 
 
199
 
        # when we have read all the pack from the client, it assumes everything worked OK
200
 
        # there is NO ack from the server before it reports victory.
201
 
 
202
 
 
203
 
class TCPGitRequestHandler(SocketServer.StreamRequestHandler):
204
 
 
205
 
    def handle(self):
206
 
        proto = Protocol(self.rfile.read, self.wfile.write)
207
 
        command, args = proto.read_cmd()
208
 
 
209
 
        # switch case to handle the specific git command
210
 
        if command == 'git-upload-pack':
211
 
            cls = UploadPackHandler
212
 
        elif command == 'git-receive-pack':
213
 
            cls = ReceivePackHandler
214
 
        else:
215
 
            return
216
 
 
217
 
        h = cls(self.server.backend, self.rfile.read, self.wfile.write)
218
 
        h.handle()
219
 
 
220
 
 
221
 
class TCPGitServer(SocketServer.TCPServer):
222
 
 
223
 
    allow_reuse_address = True
224
 
    serve = SocketServer.TCPServer.serve_forever
225
 
 
226
 
    def __init__(self, backend, listen_addr, port=TCP_GIT_PORT):
227
 
        self.backend = backend
228
 
        SocketServer.TCPServer.__init__(self, (listen_addr, port), TCPGitRequestHandler)
229
 
 
230