Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / tests / test_client.py @ 959

History | View | Annotate | Download (31 KB)

1
# test_client.py -- Tests for the git protocol, client side
2
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
3
#
4
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
5
# General Public License as public by the Free Software Foundation; version 2.0
6
# or (at your option) any later version. You can redistribute it and/or
7
# modify it under the terms of either of these two licenses.
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
#
15
# You should have received a copy of the licenses; if not, see
16
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
17
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
18
# License, Version 2.0.
19
#
20

    
21
from io import BytesIO
22
import sys
23
import shutil
24
import tempfile
25

    
26
try:
27
    from urllib import quote as urlquote
28
except ImportError:
29
    from urllib.parse import quote as urlquote
30

    
31
try:
32
    import urlparse
33
except ImportError:
34
    import urllib.parse as urlparse
35

    
36
import dulwich
37
from dulwich import (
38
    client,
39
    )
40
from dulwich.client import (
41
    LocalGitClient,
42
    TraditionalGitClient,
43
    TCPGitClient,
44
    SSHGitClient,
45
    HttpGitClient,
46
    ReportStatusParser,
47
    SendPackError,
48
    UpdateRefsError,
49
    get_transport_and_path,
50
    get_transport_and_path_from_url,
51
    )
52
from dulwich.tests import (
53
    TestCase,
54
    )
55
from dulwich.protocol import (
56
    TCP_GIT_PORT,
57
    Protocol,
58
    )
59
from dulwich.pack import (
60
    write_pack_objects,
61
    )
62
from dulwich.objects import (
63
    Commit,
64
    Tree
65
    )
66
from dulwich.repo import (
67
    MemoryRepo,
68
    Repo,
69
    )
70
from dulwich.tests import skipIf
71
from dulwich.tests.utils import (
72
    open_repo,
73
    tear_down_repo,
74
    )
75

    
76

    
77
class DummyClient(TraditionalGitClient):
78

    
79
    def __init__(self, can_read, read, write):
80
        self.can_read = can_read
81
        self.read = read
82
        self.write = write
83
        TraditionalGitClient.__init__(self)
84

    
85
    def _connect(self, service, path):
86
        return Protocol(self.read, self.write), self.can_read
87

    
88

    
89
# TODO(durin42): add unit-level tests of GitClient
90
class GitClientTests(TestCase):
91

    
92
    def setUp(self):
93
        super(GitClientTests, self).setUp()
94
        self.rout = BytesIO()
95
        self.rin = BytesIO()
96
        self.client = DummyClient(lambda x: True, self.rin.read,
97
                                  self.rout.write)
98

    
99
    def test_caps(self):
100
        agent_cap = ('agent=dulwich/%d.%d.%d' % dulwich.__version__).encode('ascii')
101
        self.assertEqual(set([b'multi_ack', b'side-band-64k', b'ofs-delta',
102
                               b'thin-pack', b'multi_ack_detailed',
103
                               agent_cap]),
104
                          set(self.client._fetch_capabilities))
105
        self.assertEqual(set([b'ofs-delta', b'report-status', b'side-band-64k',
106
                              agent_cap]),
107
                          set(self.client._send_capabilities))
108

    
109
    def test_archive_ack(self):
110
        self.rin.write(
111
            b'0009NACK\n'
112
            b'0000')
113
        self.rin.seek(0)
114
        self.client.archive(b'bla', b'HEAD', None, None)
115
        self.assertEqual(self.rout.getvalue(), b'0011argument HEAD0000')
116

    
117
    def test_fetch_empty(self):
118
        self.rin.write(b'0000')
119
        self.rin.seek(0)
120
        def check_heads(heads):
121
            self.assertIs(heads, None)
122
            return []
123
        ret = self.client.fetch_pack(b'/', check_heads, None, None)
124
        self.assertIs(None, ret)
125

    
126
    def test_fetch_pack_ignores_magic_ref(self):
127
        self.rin.write(
128
            b'00000000000000000000000000000000000000000000 capabilities^{}\x00 multi_ack '
129
            b'thin-pack side-band side-band-64k ofs-delta shallow no-progress '
130
            b'include-tag\n'
131
            b'0000')
132
        self.rin.seek(0)
133
        def check_heads(heads):
134
            self.assertEquals({}, heads)
135
            return []
136
        ret = self.client.fetch_pack(b'bla', check_heads, None, None, None)
137
        self.assertIs(None, ret)
138
        self.assertEqual(self.rout.getvalue(), b'0000')
139

    
140
    def test_fetch_pack_none(self):
141
        self.rin.write(
142
            b'008855dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 HEAD.multi_ack '
143
            b'thin-pack side-band side-band-64k ofs-delta shallow no-progress '
144
            b'include-tag\n'
145
            b'0000')
146
        self.rin.seek(0)
147
        self.client.fetch_pack(b'bla', lambda heads: [], None, None, None)
148
        self.assertEqual(self.rout.getvalue(), b'0000')
149

    
150
    def test_send_pack_no_sideband64k_with_update_ref_error(self):
151
        # No side-bank-64k reported by server shouldn't try to parse
152
        # side band data
153
        pkts = [b'55dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 capabilities^{}'
154
                b'\x00 report-status delete-refs ofs-delta\n',
155
                b'',
156
                b"unpack ok",
157
                b"ng refs/foo/bar pre-receive hook declined",
158
                b'']
159
        for pkt in pkts:
160
            if pkt ==  b'':
161
                self.rin.write(b"0000")
162
            else:
163
                self.rin.write(("%04x" % (len(pkt)+4)).encode('ascii') + pkt)
164
        self.rin.seek(0)
165

    
166
        tree = Tree()
167
        commit = Commit()
168
        commit.tree = tree
169
        commit.parents = []
170
        commit.author = commit.committer = b'test user'
171
        commit.commit_time = commit.author_time = 1174773719
172
        commit.commit_timezone = commit.author_timezone = 0
173
        commit.encoding = b'UTF-8'
174
        commit.message = b'test message'
175

    
176
        def determine_wants(refs):
177
            return {b'refs/foo/bar': commit.id, }
178

    
179
        def generate_pack_contents(have, want):
180
            return [(commit, None), (tree, ''), ]
181

    
182
        self.assertRaises(UpdateRefsError,
183
                          self.client.send_pack, "blah",
184
                          determine_wants, generate_pack_contents)
185

    
186
    def test_send_pack_none(self):
187
        self.rin.write(
188
            b'0078310ca9477129b8586fa2afc779c1f57cf64bba6c '
189
            b'refs/heads/master\x00 report-status delete-refs '
190
            b'side-band-64k quiet ofs-delta\n'
191
            b'0000')
192
        self.rin.seek(0)
193

    
194
        def determine_wants(refs):
195
            return {
196
                b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
197
            }
198

    
199
        def generate_pack_contents(have, want):
200
            return {}
201

    
202
        self.client.send_pack(b'/', determine_wants, generate_pack_contents)
203
        self.assertEqual(self.rout.getvalue(), b'0000')
204

    
205
    def test_send_pack_keep_and_delete(self):
206
        self.rin.write(
207
            b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
208
            b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
209
            b'003f310ca9477129b8586fa2afc779c1f57cf64bba6c refs/heads/keepme\n'
210
            b'0000000eunpack ok\n'
211
            b'0019ok refs/heads/master\n'
212
            b'0000')
213
        self.rin.seek(0)
214

    
215
        def determine_wants(refs):
216
            return {b'refs/heads/master': b'0' * 40}
217

    
218
        def generate_pack_contents(have, want):
219
            return {}
220

    
221
        self.client.send_pack(b'/', determine_wants, generate_pack_contents)
222
        self.assertIn(
223
            self.rout.getvalue(),
224
            [b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
225
             b'0000000000000000000000000000000000000000 '
226
             b'refs/heads/master\x00report-status ofs-delta0000',
227
             b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
228
             b'0000000000000000000000000000000000000000 '
229
             b'refs/heads/master\x00ofs-delta report-status0000'])
230

    
231
    def test_send_pack_delete_only(self):
232
        self.rin.write(
233
            b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
234
            b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
235
            b'0000000eunpack ok\n'
236
            b'0019ok refs/heads/master\n'
237
            b'0000')
238
        self.rin.seek(0)
239

    
240
        def determine_wants(refs):
241
            return {b'refs/heads/master': b'0' * 40}
242

    
243
        def generate_pack_contents(have, want):
244
            return {}
245

    
246
        self.client.send_pack(b'/', determine_wants, generate_pack_contents)
247
        self.assertIn(
248
            self.rout.getvalue(),
249
            [b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
250
             b'0000000000000000000000000000000000000000 '
251
             b'refs/heads/master\x00report-status ofs-delta0000',
252
             b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
253
             b'0000000000000000000000000000000000000000 '
254
             b'refs/heads/master\x00ofs-delta report-status0000'])
255

    
256
    def test_send_pack_new_ref_only(self):
257
        self.rin.write(
258
            b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
259
            b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
260
            b'0000000eunpack ok\n'
261
            b'0019ok refs/heads/blah12\n'
262
            b'0000')
263
        self.rin.seek(0)
264

    
265
        def determine_wants(refs):
266
            return {
267
                b'refs/heads/blah12':
268
                b'310ca9477129b8586fa2afc779c1f57cf64bba6c',
269
                b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
270
            }
271

    
272
        def generate_pack_contents(have, want):
273
            return {}
274

    
275
        f = BytesIO()
276
        write_pack_objects(f, {})
277
        self.client.send_pack('/', determine_wants, generate_pack_contents)
278
        self.assertIn(
279
            self.rout.getvalue(),
280
            [b'007f0000000000000000000000000000000000000000 '
281
             b'310ca9477129b8586fa2afc779c1f57cf64bba6c '
282
             b'refs/heads/blah12\x00report-status ofs-delta0000' +
283
             f.getvalue(),
284
             b'007f0000000000000000000000000000000000000000 '
285
             b'310ca9477129b8586fa2afc779c1f57cf64bba6c '
286
             b'refs/heads/blah12\x00ofs-delta report-status0000' +
287
             f.getvalue()])
288

    
289
    def test_send_pack_new_ref(self):
290
        self.rin.write(
291
            b'0064310ca9477129b8586fa2afc779c1f57cf64bba6c '
292
            b'refs/heads/master\x00 report-status delete-refs ofs-delta\n'
293
            b'0000000eunpack ok\n'
294
            b'0019ok refs/heads/blah12\n'
295
            b'0000')
296
        self.rin.seek(0)
297

    
298
        tree = Tree()
299
        commit = Commit()
300
        commit.tree = tree
301
        commit.parents = []
302
        commit.author = commit.committer = b'test user'
303
        commit.commit_time = commit.author_time = 1174773719
304
        commit.commit_timezone = commit.author_timezone = 0
305
        commit.encoding = b'UTF-8'
306
        commit.message = b'test message'
307

    
308
        def determine_wants(refs):
309
            return {
310
                b'refs/heads/blah12': commit.id,
311
                b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
312
            }
313

    
314
        def generate_pack_contents(have, want):
315
            return [(commit, None), (tree, b''), ]
316

    
317
        f = BytesIO()
318
        write_pack_objects(f, generate_pack_contents(None, None))
319
        self.client.send_pack(b'/', determine_wants, generate_pack_contents)
320
        self.assertIn(
321
            self.rout.getvalue(),
322
            [b'007f0000000000000000000000000000000000000000 ' + commit.id +
323
             b' refs/heads/blah12\x00report-status ofs-delta0000' + f.getvalue(),
324
             b'007f0000000000000000000000000000000000000000 ' + commit.id +
325
             b' refs/heads/blah12\x00ofs-delta report-status0000' + f.getvalue()])
326

    
327
    def test_send_pack_no_deleteref_delete_only(self):
328
        pkts = [b'310ca9477129b8586fa2afc779c1f57cf64bba6c refs/heads/master'
329
                b'\x00 report-status ofs-delta\n',
330
                b'',
331
                b'']
332
        for pkt in pkts:
333
            if pkt == b'':
334
                self.rin.write(b"0000")
335
            else:
336
                self.rin.write(("%04x" % (len(pkt)+4)).encode('ascii') + pkt)
337
        self.rin.seek(0)
338

    
339
        def determine_wants(refs):
340
            return {b'refs/heads/master': b'0' * 40}
341

    
342
        def generate_pack_contents(have, want):
343
            return {}
344

    
345
        self.assertRaises(UpdateRefsError,
346
                          self.client.send_pack, b"/",
347
                          determine_wants, generate_pack_contents)
348
        self.assertEqual(self.rout.getvalue(), b'0000')
349

    
350

    
351
class TestGetTransportAndPath(TestCase):
352

    
353
    def test_tcp(self):
354
        c, path = get_transport_and_path('git://foo.com/bar/baz')
355
        self.assertTrue(isinstance(c, TCPGitClient))
356
        self.assertEqual('foo.com', c._host)
357
        self.assertEqual(TCP_GIT_PORT, c._port)
358
        self.assertEqual('/bar/baz', path)
359

    
360
    def test_tcp_port(self):
361
        c, path = get_transport_and_path('git://foo.com:1234/bar/baz')
362
        self.assertTrue(isinstance(c, TCPGitClient))
363
        self.assertEqual('foo.com', c._host)
364
        self.assertEqual(1234, c._port)
365
        self.assertEqual('/bar/baz', path)
366

    
367
    def test_git_ssh_explicit(self):
368
        c, path = get_transport_and_path('git+ssh://foo.com/bar/baz')
369
        self.assertTrue(isinstance(c, SSHGitClient))
370
        self.assertEqual('foo.com', c.host)
371
        self.assertEqual(None, c.port)
372
        self.assertEqual(None, c.username)
373
        self.assertEqual('bar/baz', path)
374

    
375
    def test_ssh_explicit(self):
376
        c, path = get_transport_and_path('ssh://foo.com/bar/baz')
377
        self.assertTrue(isinstance(c, SSHGitClient))
378
        self.assertEqual('foo.com', c.host)
379
        self.assertEqual(None, c.port)
380
        self.assertEqual(None, c.username)
381
        self.assertEqual('bar/baz', path)
382

    
383
    def test_ssh_port_explicit(self):
384
        c, path = get_transport_and_path(
385
            'git+ssh://foo.com:1234/bar/baz')
386
        self.assertTrue(isinstance(c, SSHGitClient))
387
        self.assertEqual('foo.com', c.host)
388
        self.assertEqual(1234, c.port)
389
        self.assertEqual('bar/baz', path)
390

    
391
    def test_username_and_port_explicit_unknown_scheme(self):
392
        c, path = get_transport_and_path(
393
            'unknown://git@server:7999/dply/stuff.git')
394
        self.assertTrue(isinstance(c, SSHGitClient))
395
        self.assertEqual('unknown', c.host)
396
        self.assertEqual('//git@server:7999/dply/stuff.git', path)
397

    
398
    def test_username_and_port_explicit(self):
399
        c, path = get_transport_and_path(
400
            'ssh://git@server:7999/dply/stuff.git')
401
        self.assertTrue(isinstance(c, SSHGitClient))
402
        self.assertEqual('git', c.username)
403
        self.assertEqual('server', c.host)
404
        self.assertEqual(7999, c.port)
405
        self.assertEqual('dply/stuff.git', path)
406

    
407
    def test_ssh_abspath_explicit(self):
408
        c, path = get_transport_and_path('git+ssh://foo.com//bar/baz')
409
        self.assertTrue(isinstance(c, SSHGitClient))
410
        self.assertEqual('foo.com', c.host)
411
        self.assertEqual(None, c.port)
412
        self.assertEqual(None, c.username)
413
        self.assertEqual('/bar/baz', path)
414

    
415
    def test_ssh_port_abspath_explicit(self):
416
        c, path = get_transport_and_path(
417
            'git+ssh://foo.com:1234//bar/baz')
418
        self.assertTrue(isinstance(c, SSHGitClient))
419
        self.assertEqual('foo.com', c.host)
420
        self.assertEqual(1234, c.port)
421
        self.assertEqual('/bar/baz', path)
422

    
423
    def test_ssh_implicit(self):
424
        c, path = get_transport_and_path('foo:/bar/baz')
425
        self.assertTrue(isinstance(c, SSHGitClient))
426
        self.assertEqual('foo', c.host)
427
        self.assertEqual(None, c.port)
428
        self.assertEqual(None, c.username)
429
        self.assertEqual('/bar/baz', path)
430

    
431
    def test_ssh_host(self):
432
        c, path = get_transport_and_path('foo.com:/bar/baz')
433
        self.assertTrue(isinstance(c, SSHGitClient))
434
        self.assertEqual('foo.com', c.host)
435
        self.assertEqual(None, c.port)
436
        self.assertEqual(None, c.username)
437
        self.assertEqual('/bar/baz', path)
438

    
439
    def test_ssh_user_host(self):
440
        c, path = get_transport_and_path('user@foo.com:/bar/baz')
441
        self.assertTrue(isinstance(c, SSHGitClient))
442
        self.assertEqual('foo.com', c.host)
443
        self.assertEqual(None, c.port)
444
        self.assertEqual('user', c.username)
445
        self.assertEqual('/bar/baz', path)
446

    
447
    def test_ssh_relpath(self):
448
        c, path = get_transport_and_path('foo:bar/baz')
449
        self.assertTrue(isinstance(c, SSHGitClient))
450
        self.assertEqual('foo', c.host)
451
        self.assertEqual(None, c.port)
452
        self.assertEqual(None, c.username)
453
        self.assertEqual('bar/baz', path)
454

    
455
    def test_ssh_host_relpath(self):
456
        c, path = get_transport_and_path('foo.com:bar/baz')
457
        self.assertTrue(isinstance(c, SSHGitClient))
458
        self.assertEqual('foo.com', c.host)
459
        self.assertEqual(None, c.port)
460
        self.assertEqual(None, c.username)
461
        self.assertEqual('bar/baz', path)
462

    
463
    def test_ssh_user_host_relpath(self):
464
        c, path = get_transport_and_path('user@foo.com:bar/baz')
465
        self.assertTrue(isinstance(c, SSHGitClient))
466
        self.assertEqual('foo.com', c.host)
467
        self.assertEqual(None, c.port)
468
        self.assertEqual('user', c.username)
469
        self.assertEqual('bar/baz', path)
470

    
471
    def test_local(self):
472
        c, path = get_transport_and_path('foo.bar/baz')
473
        self.assertTrue(isinstance(c, LocalGitClient))
474
        self.assertEqual('foo.bar/baz', path)
475

    
476
    @skipIf(sys.platform != 'win32', 'Behaviour only happens on windows.')
477
    def test_local_abs_windows_path(self):
478
        c, path = get_transport_and_path('C:\\foo.bar\\baz')
479
        self.assertTrue(isinstance(c, LocalGitClient))
480
        self.assertEqual('C:\\foo.bar\\baz', path)
481

    
482
    def test_error(self):
483
        # Need to use a known urlparse.uses_netloc URL scheme to get the
484
        # expected parsing of the URL on Python versions less than 2.6.5
485
        c, path = get_transport_and_path('prospero://bar/baz')
486
        self.assertTrue(isinstance(c, SSHGitClient))
487

    
488
    def test_http(self):
489
        url = 'https://github.com/jelmer/dulwich'
490
        c, path = get_transport_and_path(url)
491
        self.assertTrue(isinstance(c, HttpGitClient))
492
        self.assertEqual('/jelmer/dulwich', path)
493

    
494
    def test_http_auth(self):
495
        url = 'https://user:passwd@github.com/jelmer/dulwich'
496

    
497
        c, path = get_transport_and_path(url)
498

    
499
        self.assertTrue(isinstance(c, HttpGitClient))
500
        self.assertEqual('/jelmer/dulwich', path)
501
        self.assertEqual('user', c._username)
502
        self.assertEqual('passwd', c._password)
503

    
504
    def test_http_no_auth(self):
505
        url = 'https://github.com/jelmer/dulwich'
506

    
507
        c, path = get_transport_and_path(url)
508

    
509
        self.assertTrue(isinstance(c, HttpGitClient))
510
        self.assertEqual('/jelmer/dulwich', path)
511
        self.assertIs(None, c._username)
512
        self.assertIs(None, c._password)
513

    
514

    
515
class TestGetTransportAndPathFromUrl(TestCase):
516

    
517
    def test_tcp(self):
518
        c, path = get_transport_and_path_from_url('git://foo.com/bar/baz')
519
        self.assertTrue(isinstance(c, TCPGitClient))
520
        self.assertEqual('foo.com', c._host)
521
        self.assertEqual(TCP_GIT_PORT, c._port)
522
        self.assertEqual('/bar/baz', path)
523

    
524
    def test_tcp_port(self):
525
        c, path = get_transport_and_path_from_url('git://foo.com:1234/bar/baz')
526
        self.assertTrue(isinstance(c, TCPGitClient))
527
        self.assertEqual('foo.com', c._host)
528
        self.assertEqual(1234, c._port)
529
        self.assertEqual('/bar/baz', path)
530

    
531
    def test_ssh_explicit(self):
532
        c, path = get_transport_and_path_from_url('git+ssh://foo.com/bar/baz')
533
        self.assertTrue(isinstance(c, SSHGitClient))
534
        self.assertEqual('foo.com', c.host)
535
        self.assertEqual(None, c.port)
536
        self.assertEqual(None, c.username)
537
        self.assertEqual('bar/baz', path)
538

    
539
    def test_ssh_port_explicit(self):
540
        c, path = get_transport_and_path_from_url(
541
            'git+ssh://foo.com:1234/bar/baz')
542
        self.assertTrue(isinstance(c, SSHGitClient))
543
        self.assertEqual('foo.com', c.host)
544
        self.assertEqual(1234, c.port)
545
        self.assertEqual('bar/baz', path)
546

    
547
    def test_ssh_abspath_explicit(self):
548
        c, path = get_transport_and_path_from_url('git+ssh://foo.com//bar/baz')
549
        self.assertTrue(isinstance(c, SSHGitClient))
550
        self.assertEqual('foo.com', c.host)
551
        self.assertEqual(None, c.port)
552
        self.assertEqual(None, c.username)
553
        self.assertEqual('/bar/baz', path)
554

    
555
    def test_ssh_port_abspath_explicit(self):
556
        c, path = get_transport_and_path_from_url(
557
            'git+ssh://foo.com:1234//bar/baz')
558
        self.assertTrue(isinstance(c, SSHGitClient))
559
        self.assertEqual('foo.com', c.host)
560
        self.assertEqual(1234, c.port)
561
        self.assertEqual('/bar/baz', path)
562

    
563
    def test_ssh_host_relpath(self):
564
        self.assertRaises(ValueError, get_transport_and_path_from_url,
565
            'foo.com:bar/baz')
566

    
567
    def test_ssh_user_host_relpath(self):
568
        self.assertRaises(ValueError, get_transport_and_path_from_url,
569
            'user@foo.com:bar/baz')
570

    
571
    def test_local_path(self):
572
        self.assertRaises(ValueError, get_transport_and_path_from_url,
573
            'foo.bar/baz')
574

    
575
    def test_error(self):
576
        # Need to use a known urlparse.uses_netloc URL scheme to get the
577
        # expected parsing of the URL on Python versions less than 2.6.5
578
        self.assertRaises(ValueError, get_transport_and_path_from_url,
579
            'prospero://bar/baz')
580

    
581
    def test_http(self):
582
        url = 'https://github.com/jelmer/dulwich'
583
        c, path = get_transport_and_path_from_url(url)
584
        self.assertTrue(isinstance(c, HttpGitClient))
585
        self.assertEqual('/jelmer/dulwich', path)
586

    
587
    def test_file(self):
588
        c, path = get_transport_and_path_from_url('file:///home/jelmer/foo')
589
        self.assertTrue(isinstance(c, LocalGitClient))
590
        self.assertEqual('/home/jelmer/foo', path)
591

    
592

    
593
class TestSSHVendor(object):
594

    
595
    def __init__(self):
596
        self.host = None
597
        self.command = ""
598
        self.username = None
599
        self.port = None
600

    
601
    def run_command(self, host, command, username=None, port=None):
602
        if not isinstance(command, bytes):
603
            raise TypeError(command)
604

    
605
        self.host = host
606
        self.command = command
607
        self.username = username
608
        self.port = port
609

    
610
        class Subprocess: pass
611
        setattr(Subprocess, 'read', lambda: None)
612
        setattr(Subprocess, 'write', lambda: None)
613
        setattr(Subprocess, 'close', lambda: None)
614
        setattr(Subprocess, 'can_read', lambda: None)
615
        return Subprocess()
616

    
617

    
618
class SSHGitClientTests(TestCase):
619

    
620
    def setUp(self):
621
        super(SSHGitClientTests, self).setUp()
622

    
623
        self.server = TestSSHVendor()
624
        self.real_vendor = client.get_ssh_vendor
625
        client.get_ssh_vendor = lambda: self.server
626

    
627
        self.client = SSHGitClient('git.samba.org')
628

    
629
    def tearDown(self):
630
        super(SSHGitClientTests, self).tearDown()
631
        client.get_ssh_vendor = self.real_vendor
632

    
633
    def test_get_url(self):
634
        path = '/tmp/repo.git'
635
        c = SSHGitClient('git.samba.org')
636

    
637
        url = c.get_url(path)
638
        self.assertEqual('ssh://git.samba.org/tmp/repo.git', url)
639

    
640
    def test_get_url_with_username_and_port(self):
641
        path = '/tmp/repo.git'
642
        c = SSHGitClient('git.samba.org', port=2222, username='user')
643

    
644
        url = c.get_url(path)
645
        self.assertEqual('ssh://user@git.samba.org:2222/tmp/repo.git', url)
646

    
647
    def test_default_command(self):
648
        self.assertEqual(b'git-upload-pack',
649
                self.client._get_cmd_path(b'upload-pack'))
650

    
651
    def test_alternative_command_path(self):
652
        self.client.alternative_paths[b'upload-pack'] = (
653
            b'/usr/lib/git/git-upload-pack')
654
        self.assertEqual(b'/usr/lib/git/git-upload-pack',
655
            self.client._get_cmd_path(b'upload-pack'))
656

    
657
    def test_alternative_command_path_spaces(self):
658
        self.client.alternative_paths[b'upload-pack'] = (
659
            b'/usr/lib/git/git-upload-pack -ibla')
660
        self.assertEqual(b"/usr/lib/git/git-upload-pack -ibla",
661
            self.client._get_cmd_path(b'upload-pack'))
662

    
663
    def test_connect(self):
664
        server = self.server
665
        client = self.client
666

    
667
        client.username = b"username"
668
        client.port = 1337
669

    
670
        client._connect(b"command", b"/path/to/repo")
671
        self.assertEqual(b"username", server.username)
672
        self.assertEqual(1337, server.port)
673
        self.assertEqual(b"git-command '/path/to/repo'", server.command)
674

    
675
        client._connect(b"relative-command", b"/~/path/to/repo")
676
        self.assertEqual(b"git-relative-command '~/path/to/repo'",
677
                          server.command)
678

    
679

    
680
class ReportStatusParserTests(TestCase):
681

    
682
    def test_invalid_pack(self):
683
        parser = ReportStatusParser()
684
        parser.handle_packet(b"unpack error - foo bar")
685
        parser.handle_packet(b"ok refs/foo/bar")
686
        parser.handle_packet(None)
687
        self.assertRaises(SendPackError, parser.check)
688

    
689
    def test_update_refs_error(self):
690
        parser = ReportStatusParser()
691
        parser.handle_packet(b"unpack ok")
692
        parser.handle_packet(b"ng refs/foo/bar need to pull")
693
        parser.handle_packet(None)
694
        self.assertRaises(UpdateRefsError, parser.check)
695

    
696
    def test_ok(self):
697
        parser = ReportStatusParser()
698
        parser.handle_packet(b"unpack ok")
699
        parser.handle_packet(b"ok refs/foo/bar")
700
        parser.handle_packet(None)
701
        parser.check()
702

    
703

    
704
class LocalGitClientTests(TestCase):
705

    
706
    def test_get_url(self):
707
        path = "/tmp/repo.git"
708
        c = LocalGitClient()
709

    
710
        url = c.get_url(path)
711
        self.assertEqual('file:///tmp/repo.git', url)
712

    
713
    def test_fetch_into_empty(self):
714
        c = LocalGitClient()
715
        t = MemoryRepo()
716
        s = open_repo('a.git')
717
        self.addCleanup(tear_down_repo, s)
718
        self.assertEqual(s.get_refs(), c.fetch(s.path, t))
719

    
720
    def test_fetch_empty(self):
721
        c = LocalGitClient()
722
        s = open_repo('a.git')
723
        self.addCleanup(tear_down_repo, s)
724
        out = BytesIO()
725
        walker = {}
726
        ret = c.fetch_pack(s.path, lambda heads: [], graph_walker=walker,
727
            pack_data=out.write)
728
        self.assertEqual({
729
            b'HEAD': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
730
            b'refs/heads/master': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
731
            b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
732
            b'refs/tags/mytag-packed': b'b0931cadc54336e78a1d980420e3268903b57a50'
733
            }, ret)
734
        self.assertEqual(b"PACK\x00\x00\x00\x02\x00\x00\x00\x00\x02\x9d\x08"
735
            b"\x82;\xd8\xa8\xea\xb5\x10\xadj\xc7\\\x82<\xfd>\xd3\x1e", out.getvalue())
736

    
737
    def test_fetch_pack_none(self):
738
        c = LocalGitClient()
739
        s = open_repo('a.git')
740
        self.addCleanup(tear_down_repo, s)
741
        out = BytesIO()
742
        walker = MemoryRepo().get_graph_walker()
743
        c.fetch_pack(s.path,
744
            lambda heads: [b"a90fa2d900a17e99b433217e988c4eb4a2e9a097"],
745
            graph_walker=walker, pack_data=out.write)
746
        # Hardcoding is not ideal, but we'll fix that some other day..
747
        self.assertTrue(out.getvalue().startswith(b'PACK\x00\x00\x00\x02\x00\x00\x00\x07'))
748

    
749
    def test_send_pack_without_changes(self):
750
        local = open_repo('a.git')
751
        self.addCleanup(tear_down_repo, local)
752

    
753
        target = open_repo('a.git')
754
        self.addCleanup(tear_down_repo, target)
755

    
756
        self.send_and_verify(b"master", local, target)
757

    
758
    def test_send_pack_with_changes(self):
759
        local = open_repo('a.git')
760
        self.addCleanup(tear_down_repo, local)
761

    
762
        target_path = tempfile.mkdtemp()
763
        self.addCleanup(shutil.rmtree, target_path)
764
        with Repo.init_bare(target_path) as target:
765
            self.send_and_verify(b"master", local, target)
766

    
767
    def test_get_refs(self):
768
        local = open_repo('refs.git')
769
        self.addCleanup(tear_down_repo, local)
770

    
771
        client = LocalGitClient()
772
        refs = client.get_refs(local.path)
773
        self.assertDictEqual(local.refs.as_dict(), refs)
774

    
775
    def send_and_verify(self, branch, local, target):
776
        """Send a branch from local to remote repository and verify it worked."""
777
        client = LocalGitClient()
778
        ref_name = b"refs/heads/" + branch
779
        new_refs = client.send_pack(target.path,
780
                                    lambda _: { ref_name: local.refs[ref_name] },
781
                                    local.object_store.generate_pack_contents)
782

    
783
        self.assertEqual(local.refs[ref_name], new_refs[ref_name])
784

    
785
        obj_local = local.get_object(new_refs[ref_name])
786
        obj_target = target.get_object(new_refs[ref_name])
787
        self.assertEqual(obj_local, obj_target)
788

    
789

    
790
class HttpGitClientTests(TestCase):
791

    
792
    def test_get_url(self):
793
        base_url = 'https://github.com/jelmer/dulwich'
794
        path = '/jelmer/dulwich'
795
        c = HttpGitClient(base_url)
796

    
797
        url = c.get_url(path)
798
        self.assertEqual('https://github.com/jelmer/dulwich', url)
799

    
800
    def test_get_url_with_username_and_passwd(self):
801
        base_url = 'https://github.com/jelmer/dulwich'
802
        path = '/jelmer/dulwich'
803
        c = HttpGitClient(base_url, username='USERNAME', password='PASSWD')
804

    
805
        url = c.get_url(path)
806
        self.assertEqual('https://github.com/jelmer/dulwich', url)
807

    
808
    def test_init_username_passwd_set(self):
809
        url = 'https://github.com/jelmer/dulwich'
810

    
811
        c = HttpGitClient(url, config=None, username='user', password='passwd')
812
        self.assertEqual('user', c._username)
813
        self.assertEqual('passwd', c._password)
814
        [pw_handler] = [
815
            h for h in c.opener.handlers if getattr(h, 'passwd', None) is not None]
816
        self.assertEqual(
817
            ('user', 'passwd'),
818
            pw_handler.passwd.find_user_password(
819
                None, 'https://github.com/jelmer/dulwich'))
820

    
821
    def test_init_no_username_passwd(self):
822
        url = 'https://github.com/jelmer/dulwich'
823

    
824
        c = HttpGitClient(url, config=None)
825
        self.assertIs(None, c._username)
826
        self.assertIs(None, c._password)
827
        pw_handler = [
828
            h for h in c.opener.handlers if getattr(h, 'passwd', None) is not None]
829
        self.assertEqual(0, len(pw_handler))
830

    
831
    def test_from_parsedurl_on_url_with_quoted_credentials(self):
832
        original_username = 'john|the|first'
833
        quoted_username = urlquote(original_username)
834

    
835
        original_password = 'Ya#1$2%3'
836
        quoted_password = urlquote(original_password)
837

    
838
        url = 'https://{username}:{password}@github.com/jelmer/dulwich'.format(
839
            username=quoted_username,
840
            password=quoted_password
841
        )
842

    
843
        c = HttpGitClient.from_parsedurl(urlparse.urlparse(url))
844
        self.assertEqual(original_username, c._username)
845
        self.assertEqual(original_password, c._password)
846
        [pw_handler] = [
847
            h for h in c.opener.handlers if getattr(h, 'passwd', None) is not None]
848
        self.assertEqual(
849
            (original_username, original_password),
850
            pw_handler.passwd.find_user_password(
851
                None, 'https://github.com/jelmer/dulwich'))
852

    
853

    
854
class TCPGitClientTests(TestCase):
855

    
856
    def test_get_url(self):
857
        host = 'github.com'
858
        path = '/jelmer/dulwich'
859
        c = TCPGitClient(host)
860

    
861
        url = c.get_url(path)
862
        self.assertEqual('git://github.com/jelmer/dulwich', url)
863

    
864
    def test_get_url_with_port(self):
865
        host = 'github.com'
866
        path = '/jelmer/dulwich'
867
        port = 9090
868
        c = TCPGitClient(host, port=9090)
869

    
870
        url = c.get_url(path)
871
        self.assertEqual('git://github.com:9090/jelmer/dulwich', url)