merge with branch issue-970

This commit is contained in:
Sebastian Sdorra
2018-04-05 18:45:12 +02:00
17 changed files with 1623 additions and 36 deletions

View File

@@ -0,0 +1,76 @@
# Clone empty repository
```http
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
Accept-Encoding: identity.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1efk0qxy1dj5v133hev91zwsf4;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 130.
Server: Jetty(7.6.21.v20160908).
.
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=bookmarks.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1rsxj8u1rq9wizawhyyxok2p5;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 0.
Server: Jetty(7.6.21.v20160908).
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: cmds=heads+%3Bknown+nodes%3D.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=ewyx4m53d8dajjsob6gxobne;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 42.
Server: Jetty(7.6.21.v20160908).
0000000000000000000000000000000000000000
;
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1o0hou15jtiywsywutf30qwm8;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 15.
Server: Jetty(7.6.21.v20160908).
.
publishing.True
```

View File

@@ -0,0 +1,117 @@
# Push bookmark
```http
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
Accept-Encoding: identity.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=7rq9vpp9svfm1sicq7h9vetmv;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 130.
Server: Jetty(7.6.21.v20160908).
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
T 172.17.0.2:8080 -> 172.17.0.1:36576 [AP]
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1553csz4sf7scyvw8mqnqfirn;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 43.
Server: Jetty(7.6.21.v20160908).
ef5993bb4abb32a0565c347844c6d939fc4f4b98
;1
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=11xa5u3nrmx8k1nar3sazg6jzh;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 15.
Server: Jetty(7.6.21.v20160908).
publishing.True
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=bookmarks.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1p1uzcvfe1pvzh2buzo658rxw;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 0.
Server: Jetty(7.6.21.v20160908).
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1mhlj3ucfzdp6ifmzoua4zwit;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 15.
Server: Jetty(7.6.21.v20160908).
publishing.True
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
Accept-Encoding: identity.
content-type: application/mercurial-0.1.
vary: X-HgArg-1.
x-hgarg-1: key=markone&namespace=bookmarks&new=ef5993bb4abb32a0565c347844c6d939fc4f4b98&old=.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
content-length: 0.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=s4vtagb303dv1xg809wnp7e8z;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 2.
Server: Jetty(7.6.21.v20160908).
.
1
```

View File

@@ -0,0 +1,167 @@
# Push multiple branches to new repository
```http
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
Accept-Encoding: identity.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1wu06ykfd4bcv1uv731y4hss2m;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 130.
Server: Jetty(7.6.21.v20160908).
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1rajglvqx222g5nppcq3jdfk0;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 43.
Server: Jetty(7.6.21.v20160908).
0000000000000000000000000000000000000000
;0
GET /scm/hg/hgtest?cmd=known HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: nodes=c0ceccb3b2f0f5c977ff32b9337519e5f37942c2+187ddf37e237c370514487a0bb1a226f11a780b3+b5914611f84eae14543684b2721eec88b0edac12+8b63a323606f10c86b30465570c2574eb7a3a989.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=a5vykp1f0ga2186l8v3gu6lid;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 4.
Server: Jetty(7.6.21.v20160908).
0000
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=s8lpwqm4c2nqs9kwcg2ca6vm;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 15.
Server: Jetty(7.6.21.v20160908).
publishing.True
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=bookmarks.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1d2qj3kynxlhvk31oli4kk7vf;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 0.
Server: Jetty(7.6.21.v20160908).
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
Accept-Encoding: identity.
content-type: application/mercurial-0.1.
vary: X-HgArg-1.
x-hgarg-1: heads=686173686564+6768033e216468247bd031a0a2d9876d79818f8f.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
content-length: 913.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HG10GZx...oh.U......E.1.....2q.<...s.1.YK*e#..b..{....{..%A.....
,\.....Y.XV....Q/J......`Q/.z.{...<.7....r.s.~?.?..<o....O.]....?.}..m?]z..I..u..}.a..rg..R[i.,D ...!.1..h.r.....G...M.\...J[.....+{.k...u..bL.!....F('..=Q.'......W.>5.~`..?..........O.j.0.....Ih.....!@.P... ..a
;!y..cT...]q.8Zg=...<..,.tq.*.........l........';..w^...w...-......Co..Fs.HYg...
9.F#.P......1..;......D.H.9$@.^....r:E..18...H....3..h...-.=.6l......=q .)."Yg..p\...s@.#.H.*....c8&96..2.GjJ.`.J....r...=Q1..@R.3.o{q...|.......yq.k..,cY..:[... ...S.2...VYp..c5..&.SFR.............V.d..o..........,.. A..M....k...0_.LO1..1"4.;...B....5.9.".U.m.e......]\../p..;?C..<vW.....|......F.8,....s....2.T
N. .k..>W9.........n.~o..gW...Q;..$....S..X.CN.5I].H..!.@...U..J...L.lY.../.-...6.:.Q.'...>.e'..<#3........OL}.52ra[..g*Y:Y....w...=..Z\...S.......tz..;..mf...W......&yUN.r.......4...........`..F...nT..U9................_.~..?...BwzUN.r....B.
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=163487i0ayf9s1k2ng9e1azadj;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 102.
Server: Jetty(7.6.21.v20160908).
1
adding changesets
adding manifests
adding file changes
added 5 changesets with 3 changes to 3 files
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=a3i712yjss6t1xsxltnssq0tl;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 58.
Server: Jetty(7.6.21.v20160908).
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2.1
publishing.True
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
Accept-Encoding: identity.
content-type: application/mercurial-0.1.
vary: X-HgArg-1.
x-hgarg-1: key=ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
content-length: 0.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=g8cavdze42d83knmuasrlg10;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 2.
Server: Jetty(7.6.21.v20160908).
.
1
```

View File

@@ -0,0 +1,183 @@
# Push multiple branches
```http
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
Accept-Encoding: identity.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1mvm1rxg8333iib7754ksusxc;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 130.
Server: Jetty(7.6.21.v20160908).
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=58p9y9vcnz5cjs22dtw8mpwk;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 43.
Server: Jetty(7.6.21.v20160908).
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2
;0
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=v5wfwj8k4t261dp6808cdouoa;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 15.
Server: Jetty(7.6.21.v20160908).
publishing.True
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=bookmarks.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=3pgqytfhm4za1dco9p41j9yz5;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 0.
Server: Jetty(7.6.21.v20160908).
GET /scm/hg/hgtest?cmd=branchmap HTTP/1.1.
Accept-Encoding: identity.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
.
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1tiz6zf7ui54e1j3d4vouxig5m;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 48.
Server: Jetty(7.6.21.v20160908).
default c0ceccb3b2f0f5c977ff32b9337519e5f37942c2
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=bookmarks.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1augu4tc71xax1dit20dtxzkez;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 0.
Server: Jetty(7.6.21.v20160908).
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
Accept-Encoding: identity.
content-type: application/mercurial-0.1.
vary: X-HgArg-1.
x-hgarg-1: heads=686173686564+95373ca7cd5371cb6c49bb755ee451d9ec585845.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
content-length: 746.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HG10GZx...]H.Q...z..r.,.Y..Bw.~..c.Z&...hf.:......e.XK.X,...
,2.E1.B+...(.B"."*..z1.*......M...........93..k|..I..<...h..J_.L.9>.h..@.....op..^.....#....;.*..W....T@....!..dY....jT..A0O6.}..S.2..JPU.O6...aa...rY.VOf9.....7Ukj.&..<...z...j......%}..Jc.8c....k.."9.&".I.P.\..$.At......0..1..g.2.)<..$.. E..dn#....#.Y$3...n...5....J.e.......SNHN.q.MD..4..."I..`PF..?GH1..F..uES..Rl$47.....a........D.1...87.k.t..D..O_.3..6'cN.w.M..|@E.).X!.h*....U.B.X.....h..$.`4...
-..O.:./..oWN.....3...x.L......_[..../..k.R$.x.2..kkv.\2R....4...@.2...1Q..T
..(..m....s.Uo.......{.d.....Y....TYO...S.Pl`a5. ."N$.@...b...qJ.l.).n...1..F.Zy.....&>v;.q.....Jy..X.?.;....>U..|.....d.Y.*.q...NR.3...h.T..x..,.]...p{.^S.S...~..`..q.\j{.oCI.............K.....l9n.s......
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1e4fnqpncil9z1f7a2pya26nt7;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 102.
Server: Jetty(7.6.21.v20160908).
1
adding changesets
adding manifests
adding file changes
added 4 changesets with 2 changes to 2 files
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=f9hvrjssniym1qe33q0u8r2m8;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 101.
Server: Jetty(7.6.21.v20160908).
b5914611f84eae14543684b2721eec88b0edac12.1
187ddf37e237c370514487a0bb1a226f11a780b3.1
publishing.True
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
Accept-Encoding: identity.
content-type: application/mercurial-0.1.
vary: X-HgArg-1.
x-hgarg-1: key=ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
content-length: 0.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=z5lrut6940a650sw6x9bls8a;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 2.
Server: Jetty(7.6.21.v20160908).
1
```

View File

@@ -0,0 +1,147 @@
# Push single changeset
```http
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
Accept-Encoding: identity.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=18r2i2jsba46d14ncsmcjdhaem;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 130.
Server: Jetty(7.6.21.v20160908).
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Dc0ceccb3b2f0f5c977ff32b9337519e5f37942c2.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=1fw0i0c5zpy281gfgha0f26git;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 43.
Server: Jetty(7.6.21.v20160908).
0000000000000000000000000000000000000000
;0
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=dfa46uaqgf39w3jhk857oymu;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 15.
Server: Jetty(7.6.21.v20160908).
publishing.True
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=bookmarks.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=2sk1llvrsagg33xgmwyirfpi;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 0.
Server: Jetty(7.6.21.v20160908).
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
Accept-Encoding: identity.
content-type: application/mercurial-0.1.
vary: X-HgArg-1.
x-hgarg-1: heads=686173686564+6768033e216468247bd031a0a2d9876d79818f8f.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
content-length: 261.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HG10GZx.c``8w.....>|=Y..h.q.....N.......%......Z....&&&.&...YZ.&.&[$.........$.%q..&%..d&.).....%*.....Y.....9z...v\..FF......
..F..\.z%.%\\.)).)
.P[....D..[un..L).nc..q.m*.H.l#C...eZJ..YJ.Q.qR...e.aJ.EjjJ.AZ..A.Q..E.1.T.'D..C....7s.}..4G........3.S.mL.0.....zk
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=hlucs5utn1ifnpehqmjpt593;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 102.
Server: Jetty(7.6.21.v20160908).
1
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
T 172.17.0.1:33206 -> 172.17.0.2:8080 [AP]
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
Accept-Encoding: identity.
vary: X-HgArg-1.
x-hgarg-1: namespace=phases.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=15xomlrxl8qja1cj47rjpqda0y;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 58.
Server: Jetty(7.6.21.v20160908).
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2.1
publishing.True
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
Accept-Encoding: identity.
content-type: application/mercurial-0.1.
vary: X-HgArg-1.
x-hgarg-1: key=c0ceccb3b2f0f5c977ff32b9337519e5f37942c2&namespace=phases&new=0&old=1.
accept: application/mercurial-0.1.
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
content-length: 0.
host: localhost:8080.
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
HTTP/1.1 200 OK.
Set-Cookie: JSESSIONID=5zrop5v8e661ipk12tvru525;Path=/scm.
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
Content-Type: application/mercurial-0.1.
Content-Length: 2.
Server: Jetty(7.6.21.v20160908).
1
```

View File

@@ -127,6 +127,10 @@ public class HgConfig extends SimpleRepositoryConfig
return disableHookSSLValidation;
}
public boolean isEnableHttpPostArgs() {
return enableHttpPostArgs;
}
/**
* Method description
*
@@ -197,6 +201,10 @@ public class HgConfig extends SimpleRepositoryConfig
this.showRevisionInId = showRevisionInId;
}
public void setEnableHttpPostArgs(boolean enableHttpPostArgs) {
this.enableHttpPostArgs = enableHttpPostArgs;
}
/**
* Method description
*
@@ -232,6 +240,8 @@ public class HgConfig extends SimpleRepositoryConfig
/** Field description */
private boolean showRevisionInId = false;
private boolean enableHttpPostArgs = false;
/**
* disable validation of ssl certificates for mercurial hook
* @see <a href="https://goo.gl/zH5eY8">Issue 959</a>

View File

@@ -87,6 +87,8 @@ public class HgCGIServlet extends HttpServlet
/** Field description */
public static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
/** Field description */
public static final String ENV_SESSION_PREFIX = "SCM_";
@@ -278,11 +280,15 @@ public class HgCGIServlet extends HttpServlet
environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
}
// enable experimental httppostargs protocol of mercurial
// Issue 970: https://goo.gl/poascp
environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
//J-
HgEnvironment.prepareEnvironment(
environment,
handler,
hookManager,
hookManager,
request
);
//J+

View File

@@ -35,31 +35,35 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.RepositoryProvider;
import sonia.scm.web.filter.ProviderPermissionFilter;
//~--- JDK imports ------------------------------------------------------------
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* Permission filter for mercurial repositories.
*
*
* @author Sebastian Sdorra
*/
@Singleton
public class HgPermissionFilter extends ProviderPermissionFilter
{
private static final Set<String> READ_METHODS = ImmutableSet.of("GET", "HEAD", "OPTIONS", "TRACE");
private final HgRepositoryHandler repositoryHandler;
/**
* Constructs a new instance.
*
@@ -67,17 +71,49 @@ public class HgPermissionFilter extends ProviderPermissionFilter
* @param repositoryProvider repository provider
*/
@Inject
public HgPermissionFilter(ScmConfiguration configuration,
RepositoryProvider repositoryProvider)
public HgPermissionFilter(ScmConfiguration configuration, RepositoryProvider repositoryProvider, HgRepositoryHandler repositoryHandler)
{
super(configuration, repositoryProvider);
this.repositoryHandler = repositoryHandler;
}
//~--- get methods ----------------------------------------------------------
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
super.doFilter(wrapRequestIfRequired(request), response, chain);
}
@VisibleForTesting
HttpServletRequest wrapRequestIfRequired(HttpServletRequest request) {
if (isHttpPostArgsEnabled()) {
return new HgServletRequest(request);
}
return request;
}
@Override
protected boolean isWriteRequest(HttpServletRequest request)
{
return !READ_METHODS.contains(request.getMethod());
if (isHttpPostArgsEnabled()) {
return isHttpPostArgsWriteRequest(request);
}
return isDefaultWriteRequest(request);
}
private boolean isHttpPostArgsEnabled() {
return repositoryHandler.getConfig().isEnableHttpPostArgs();
}
private boolean isHttpPostArgsWriteRequest(HttpServletRequest request) {
return WireProtocol.isWriteRequest(request);
}
private boolean isDefaultWriteRequest(HttpServletRequest request) {
if (READ_METHODS.contains(request.getMethod())) {
return WireProtocol.isWriteRequest(request);
}
return true;
}
}

View File

@@ -0,0 +1,55 @@
package sonia.scm.web;
import com.google.common.base.Preconditions;
import javax.servlet.ServletInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* HgServletInputStream is a wrapper around the original {@link ServletInputStream} and provides some extra
* functionality to support the mercurial client.
*/
public class HgServletInputStream extends ServletInputStream {
private final ServletInputStream original;
private ByteArrayInputStream captured;
HgServletInputStream(ServletInputStream original) {
this.original = original;
}
/**
* Reads the given amount of bytes from the stream and captures them, if the {@link #read()} methods is called the
* captured bytes are returned before the rest of the stream.
*
* @param size amount of bytes to read
*
* @return byte array
*
* @throws IOException if the method is called twice
*/
public byte[] readAndCapture(int size) throws IOException {
Preconditions.checkState(captured == null, "readAndCapture can only be called once per request");
// TODO should we enforce a limit? to prevent OOM?
byte[] bytes = new byte[size];
original.read(bytes);
captured = new ByteArrayInputStream(bytes);
return bytes;
}
@Override
public int read() throws IOException {
if (captured != null && captured.available() > 0) {
return captured.read();
}
return original.read();
}
@Override
public void close() throws IOException {
original.close();
}
}

View File

@@ -0,0 +1,31 @@
package sonia.scm.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
/**
* {@link HttpServletRequestWrapper} which adds some functionality in order to support the mercurial client.
*/
public final class HgServletRequest extends HttpServletRequestWrapper {
private HgServletInputStream hgServletInputStream;
/**
* Constructs a request object wrapping the given request.
*
* @param request
* @throws IllegalArgumentException if the request is null
*/
public HgServletRequest(HttpServletRequest request) {
super(request);
}
@Override
public HgServletInputStream getInputStream() throws IOException {
if (hgServletInputStream == null) {
hgServletInputStream = new HgServletInputStream(super.getInputStream());
}
return hgServletInputStream;
}
}

View File

@@ -0,0 +1,236 @@
/**
* Copyright (c) 2018, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.web;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.util.HttpUtil;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
/**
* WireProtocol provides methods for handling the mercurial wire protocol.
*
* @see <a href="https://goo.gl/WaVJzw">Mercurial Wire Protocol</a>
*/
public final class WireProtocol {
private static final Logger LOG = LoggerFactory.getLogger(WireProtocol.class);
private static final Set<String> READ_COMMANDS = ImmutableSet.of(
"batch", "between", "branchmap", "branches", "capabilities", "changegroup", "changegroupsubset", "clonebundles",
"getbundle", "heads", "hello", "listkeys", "lookup", "known", "stream_out",
// could not find lheads in the wireprotocol description but mercurial 4.5.2 uses it for clone
"lheads"
);
private static final Set<String> WRITE_COMMANDS = ImmutableSet.of(
"pushkey", "unbundle"
);
private WireProtocol() {
}
/**
* Returns {@code true} if the request is a write request. The method will always return {@code true}, expect for the
* following cases:
*
* - no command was specified with the request (is required for the hgweb ui)
* - the command in the query string was found in the list of read request
* - if query string contains the batch command, then all commands specified in X-HgArg headers must be
* in the list of read requests
* - in case of enabled HttpPostArgs protocol and query string container the batch command, the header X-HgArgs-Post
* is read and the commands which are specified in the body from 0 to the value of X-HgArgs-Post must be in the list
* of read requests
*
* @param request http request
*
* @return {@code true} for write requests.
*/
public static boolean isWriteRequest(HttpServletRequest request) {
List<String> commands = commandsOf(request);
boolean write = isWriteRequest(commands);
LOG.trace("mercurial request {} is write: {}", commands, write);
return write;
}
@VisibleForTesting
static boolean isWriteRequest(List<String> commands) {
return !READ_COMMANDS.containsAll(commands);
}
@VisibleForTesting
static List<String> commandsOf(HttpServletRequest request) {
List<String> listOfCmds = Lists.newArrayList();
String cmd = getCommandFromQueryString(request);
if (cmd != null) {
listOfCmds.add(cmd);
if (isBatchCommand(cmd)) {
parseHgArgHeaders(request, listOfCmds);
handleHttpPostArgs(request, listOfCmds);
}
}
return Collections.unmodifiableList(listOfCmds);
}
private static void handleHttpPostArgs(HttpServletRequest request, List<String> listOfCmds) {
int hgArgsPostSize = request.getIntHeader("X-HgArgs-Post");
if (hgArgsPostSize > 0) {
if (request instanceof HgServletRequest) {
HgServletRequest hgRequest = (HgServletRequest) request;
parseHttpPostArgs(listOfCmds, hgArgsPostSize, hgRequest);
} else {
throw new IllegalArgumentException("could not process the httppostargs protocol without HgServletRequest");
}
}
}
private static void parseHttpPostArgs(List<String> listOfCmds, int hgArgsPostSize, HgServletRequest hgRequest) {
try {
byte[] bytes = hgRequest.getInputStream().readAndCapture(hgArgsPostSize);
// we use iso-8859-1 for encoding, because the post args are normally http headers which are using iso-8859-1
// see https://tools.ietf.org/html/rfc7230#section-3.2.4
String hgArgs = new String(bytes, Charsets.ISO_8859_1);
String decoded = decodeValue(hgArgs);
parseHgCommandHeader(listOfCmds, decoded);
} catch (IOException ex) {
throw Throwables.propagate(ex);
}
}
private static void parseHgArgHeaders(HttpServletRequest request, List<String> listOfCmds) {
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String header = (String) headerNames.nextElement();
parseHgArgHeader(request, listOfCmds, header);
}
}
private static void parseHgArgHeader(HttpServletRequest request, List<String> listOfCmds, String header) {
if (isHgArgHeader(header)) {
String value = getHeaderDecoded(request, header);
parseHgArgValue(listOfCmds, value);
}
}
private static void parseHgArgValue(List<String> listOfCmds, String value) {
if (isHgArgCommandHeader(value)) {
parseHgCommandHeader(listOfCmds, value);
}
}
private static void parseHgCommandHeader(List<String> listOfCmds, String value) {
String[] cmds = value.substring(5).split(";");
for (String cmd : cmds ) {
String normalizedCmd = normalize(cmd);
int index = normalizedCmd.indexOf(' ');
if (index > 0) {
listOfCmds.add(normalizedCmd.substring(0, index));
} else {
listOfCmds.add(normalizedCmd);
}
}
}
private static String normalize(String cmd) {
return cmd.trim().toLowerCase(Locale.ENGLISH);
}
private static boolean isHgArgCommandHeader(String value) {
return value.startsWith("cmds=");
}
private static String getHeaderDecoded(HttpServletRequest request, String header) {
return decodeValue(request.getHeader(header));
}
private static String decodeValue(String value) {
return HttpUtil.decode(Strings.nullToEmpty(value));
}
private static boolean isHgArgHeader(String header) {
return header.toLowerCase(Locale.ENGLISH).startsWith("x-hgarg-");
}
private static boolean isBatchCommand(String cmd) {
return "batch".equalsIgnoreCase(cmd);
}
private static String getCommandFromQueryString(HttpServletRequest request) {
// we can't use getParameter, because this would inspect the body for form parameters as well
Multimap<String, String> queryParameterMap = createQueryParameterMap(request);
Collection<String> cmd = queryParameterMap.get("cmd");
Preconditions.checkArgument(cmd.size() <= 1, "found more than one cmd query parameter");
Iterator<String> iterator = cmd.iterator();
String command = null;
if (iterator.hasNext()) {
command = iterator.next();
}
return command;
}
private static Multimap<String,String> createQueryParameterMap(HttpServletRequest request) {
Multimap<String,String> parameterMap = HashMultimap.create();
String queryString = request.getQueryString();
if (!Strings.isNullOrEmpty(queryString)) {
String[] parameters = queryString.split("&");
for (String parameter : parameters) {
int index = parameter.indexOf('=');
if (index > 0) {
parameterMap.put(parameter.substring(0, index), parameter.substring(index + 1));
} else {
parameterMap.put(parameter, "true");
}
}
}
return parameterMap;
}
}

View File

@@ -48,6 +48,7 @@ Sonia.hg.ConfigPanel = Ext.extend(Sonia.config.ConfigForm, {
showRevisionInIdText: 'Show Revision',
// TODO: i18n
disableHookSSLValidationText: 'Disable SSL Validation on Hooks',
enableHttpPostArgsText: 'Enable HttpPostArgs Protocol',
// helpText
hgBinaryHelpText: 'Location of Mercurial binary.',
@@ -63,6 +64,10 @@ Sonia.hg.ConfigPanel = Ext.extend(Sonia.config.ConfigForm, {
// TODO: i18n
disableHookSSLValidationHelpText: 'Disables the validation of ssl certificates for the mercurial hook, which forwards the repository changes back to scm-manager. \n\
This option should only be used, if SCM-Manager uses a self signed certificate.',
// TODO explain it
enableHttpPostArgsHelpText: 'Enables the experimental HttpPostArgs Protocol of mercurial.\n\
The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers.\
This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.',
initComponent: function(){
@@ -115,12 +120,18 @@ Sonia.hg.ConfigPanel = Ext.extend(Sonia.config.ConfigForm, {
fieldLabel: this.disableHookSSLValidationText,
inputValue: 'true',
helpText: this.disableHookSSLValidationHelpText
},{
xtype: 'checkbox',
name: 'enableHttpPostArgs',
fieldLabel: this.enableHttpPostArgsText,
inputValue: 'true',
helpText: this.enableHttpPostArgsHelpText
},{
xtype: 'checkbox',
name: 'disabled',
fieldLabel: this.disabledText,
inputValue: 'true',
helpText: this.disabledHelpText
helpText: this.disabledHelpText
},{
xtype: 'button',
text: this.configWizardText,
@@ -260,11 +271,11 @@ Sonia.repository.typeIcons['hg'] = 'resources/images/icons/16x16/mercurial.png';
// override ChangesetViewerGrid to render changeset id's with revisions
Ext.override(Sonia.repository.ChangesetViewerGrid, {
isMercurialRepository: function(){
return this.repository.type === 'hg';
},
getChangesetId: function(id, record){
if ( this.isMercurialRepository() ){
var rev = Sonia.util.getProperty(record.get('properties'), 'hg.rev');
@@ -274,7 +285,7 @@ Ext.override(Sonia.repository.ChangesetViewerGrid, {
}
return id;
},
getParentIds: function(id, record){
var parents = record.get('parents');
if ( this.isMercurialRepository() ){
@@ -285,7 +296,7 @@ Ext.override(Sonia.repository.ChangesetViewerGrid, {
parents[0] = rev + ':' + parents[0];
}
if ( parents.length > 1 ){
rev = Sonia.util.getProperty(properties, 'hg.p2.rev');
rev = Sonia.util.getProperty(properties, 'hg.p2.rev');
if (rev){
parents[1] = rev + ':' + parents[1];
}
@@ -294,5 +305,5 @@ Ext.override(Sonia.repository.ChangesetViewerGrid, {
}
return parents;
}
});

View File

@@ -31,12 +31,21 @@
import os
from mercurial import demandimport
from mercurial import demandimport, ui as uimod, hg
from mercurial.hgweb import hgweb, wsgicgi
repositoryPath = os.environ['SCM_REPOSITORY_PATH']
demandimport.enable()
application = hgweb(repositoryPath)
u = uimod.ui.load()
# pass SCM_HTTP_POST_ARGS to enable experimental httppostargs protocol of mercurial
# SCM_HTTP_POST_ARGS is set by HgCGIServlet
# Issue 970: https://goo.gl/poascp
u.setconfig('experimental', 'httppostargs', os.environ['SCM_HTTP_POST_ARGS'])
# open repository
# SCM_REPOSITORY_PATH contains the repository path and is set by HgCGIServlet
r = hg.repository(u, os.environ['SCM_REPOSITORY_PATH'])
application = hgweb(r)
wsgicgi.launch(application)

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
@@ -13,7 +13,7 @@
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,28 +24,38 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* http://bitbucket.org/sdorra/scm-manager
*
*
*/
package sonia.scm.web;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.RepositoryProvider;
import javax.servlet.http.HttpServletRequest;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static sonia.scm.web.WireProtocolRequestMockFactory.CMDS_HEADS_KNOWN_NODES;
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS;
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES;
/**
* Unit tests for {@link HgPermissionFilter}.
*
*
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
@@ -56,13 +66,37 @@ public class HgPermissionFilterTest {
@Mock
private ScmConfiguration configuration;
@Mock
private RepositoryProvider repositoryProvider;
@Mock
private HgRepositoryHandler hgRepositoryHandler;
private WireProtocolRequestMockFactory wireProtocol = new WireProtocolRequestMockFactory("/scm/hg/repo");
@InjectMocks
private HgPermissionFilter filter;
@Before
public void setUp() {
when(hgRepositoryHandler.getConfig()).thenReturn(new HgConfig());
}
/**
* Tests {@link HgPermissionFilter#wrapRequestIfRequired(HttpServletRequest)}.
*/
@Test
public void testWrapRequestIfRequired() {
assertSame(request, filter.wrapRequestIfRequired(request));
HgConfig hgConfig = new HgConfig();
hgConfig.setEnableHttpPostArgs(true);
when(hgRepositoryHandler.getConfig()).thenReturn(hgConfig);
assertThat(filter.wrapRequestIfRequired(request), is(instanceOf(HgServletRequest.class)));
}
/**
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)}.
*/
@@ -73,7 +107,7 @@ public class HgPermissionFilterTest {
assertFalse(isWriteRequest("HEAD"));
assertFalse(isWriteRequest("TRACE"));
assertFalse(isWriteRequest("OPTIONS"));
// write methods
assertTrue(isWriteRequest("POST"));
assertTrue(isWriteRequest("PUT"));
@@ -81,8 +115,121 @@ public class HgPermissionFilterTest {
assertTrue(isWriteRequest("KA"));
}
/**
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with enabled httppostargs option.
*/
@Test
public void testIsWriteRequestWithEnabledHttpPostArgs() {
HgConfig config = new HgConfig();
config.setEnableHttpPostArgs(true);
when(hgRepositoryHandler.getConfig()).thenReturn(config);
assertFalse(isWriteRequest("POST"));
assertFalse(isWriteRequest("POST", "heads"));
assertTrue(isWriteRequest("POST", "unbundle"));
}
private boolean isWriteRequest(String method) {
return isWriteRequest(method, "capabilities");
}
private boolean isWriteRequest(String method, String command) {
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getQueryString()).thenReturn("cmd=" + command);
when(request.getMethod()).thenReturn(method);
return filter.isWriteRequest(request);
}
}
/**
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
* fresh clone of a repository.
*/
@Test
public void testIsWriteRequestWithClone() {
assertIsReadRequest(wireProtocol.capabilities());
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
}
/**
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
* push of a single changeset.
*/
@Test
public void testIsWriteRequestWithSingleChangesetPush() {
assertIsReadRequest(wireProtocol.capabilities());
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2")));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
assertIsWriteRequest(wireProtocol.unbundle(261L, "686173686564+6768033e216468247bd031a0a2d9876d79818f8f"));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
assertIsWriteRequest(wireProtocol.pushkey("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2&namespace=phases&new=0&old=1"));
}
/**
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
* push to a single changeset.
*/
@Test
public void testIsWriteRequestWithMultipleChangesetsPush() {
assertIsReadRequest(wireProtocol.capabilities());
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
assertIsReadRequest(wireProtocol.branchmap());
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
assertIsWriteRequest(wireProtocol.unbundle(746L, "686173686564+95373ca7cd5371cb6c49bb755ee451d9ec585845"));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
assertIsWriteRequest(wireProtocol.pushkey("ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1"));
}
/**
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
* push of multiple branches to a new repository.
*/
@Test
public void testIsWriteRequestWithMutlipleBranchesToNewRepositoryPush() {
assertIsReadRequest(wireProtocol.capabilities());
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
assertIsReadRequest(wireProtocol.known("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2+187ddf37e237c370514487a0bb1a226f11a780b3+b5914611f84eae14543684b2721eec88b0edac12+8b63a323606f10c86b30465570c2574eb7a3a989"));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
assertIsWriteRequest(wireProtocol.unbundle(913L, "686173686564+6768033e216468247bd031a0a2d9876d79818f8f"));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
assertIsWriteRequest(wireProtocol.pushkey("ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1"));
}
/**
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
* push of a bookmark.
*/
@Test
public void testIsWriteRequestWithBookmarkPush() {
assertIsReadRequest(wireProtocol.capabilities());
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
assertIsReadRequest(wireProtocol.listkeys(PHASES));
assertIsWriteRequest(wireProtocol.pushkey("markone&namespace=bookmarks&new=ef5993bb4abb32a0565c347844c6d939fc4f4b98&old="));
}
/**
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a write request hidden in a batch GET
* request.
*
* @see <a href="https://goo.gl/poascp">Issue #970</a>
*/
@Test
public void testIsWriteRequestWithBookmarkPushInABatch() {
assertIsWriteRequest(wireProtocol.batch("pushkey key=markthree,namespace=bookmarks,new=187ddf37e237c370514487a0bb1a226f11a780b3,old="));
}
private void assertIsReadRequest(HttpServletRequest request) {
assertFalse(filter.isWriteRequest(request));
}
private void assertIsWriteRequest(HttpServletRequest request) {
assertTrue(filter.isWriteRequest(request));
}
}

View File

@@ -0,0 +1,50 @@
package sonia.scm.web;
import com.google.common.base.Charsets;
import com.google.common.io.ByteStreams;
import org.junit.Test;
import javax.servlet.ServletInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class HgServletInputStreamTest {
@Test
public void testReadAndCapture() throws IOException {
SampleServletInputStream original = new SampleServletInputStream("trillian.mcmillian@hitchhiker.com");
HgServletInputStream hgServletInputStream = new HgServletInputStream(original);
byte[] prefix = hgServletInputStream.readAndCapture(8);
assertEquals("trillian", new String(prefix, Charsets.US_ASCII));
byte[] wholeBytes = ByteStreams.toByteArray(hgServletInputStream);
assertEquals("trillian.mcmillian@hitchhiker.com", new String(wholeBytes, Charsets.US_ASCII));
}
@Test(expected = IllegalStateException.class)
public void testReadAndCaptureCalledTwice() throws IOException {
SampleServletInputStream original = new SampleServletInputStream("trillian.mcmillian@hitchhiker.com");
HgServletInputStream hgServletInputStream = new HgServletInputStream(original);
hgServletInputStream.readAndCapture(1);
hgServletInputStream.readAndCapture(1);
}
private static class SampleServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
private SampleServletInputStream(String data) {
input = new ByteArrayInputStream(data.getBytes());
}
@Override
public int read() {
return input.read();
}
}
}

View File

@@ -0,0 +1,114 @@
package sonia.scm.web;
import com.google.common.collect.Lists;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import static org.mockito.Mockito.*;
public class WireProtocolRequestMockFactory {
public enum Namespace {
PHASES, BOOKMARKS;
}
public static final String CMDS_HEADS_KNOWN_NODES = "heads+%3Bknown+nodes%3D";
private String repositoryPath;
public WireProtocolRequestMockFactory(String repositoryPath) {
this.repositoryPath = repositoryPath;
}
public HttpServletRequest capabilities() {
return base("GET", "cmd=capabilities");
}
public HttpServletRequest listkeys(Namespace namespace) {
HttpServletRequest request = base("GET", "cmd=capabilities");
header(request, "vary", "X-HgArg-1");
header(request, "x-hgarg-1", namespaceValue(namespace));
return request;
}
public HttpServletRequest branchmap() {
return base("GET", "cmd=branchmap");
}
public HttpServletRequest batch(String... args) {
HttpServletRequest request = base("GET", "cmd=batch");
args(request, "cmds", args);
return request;
}
public HttpServletRequest unbundle(long contentLength, String... heads) {
HttpServletRequest request = base("POST", "cmd=unbundle");
header(request, "Content-Length", String.valueOf(contentLength));
args(request, "heads", heads);
return request;
}
public HttpServletRequest pushkey(String... keys) {
HttpServletRequest request = base("POST", "cmd=pushkey");
args(request, "key", keys);
return request;
}
public HttpServletRequest known(String... nodes) {
HttpServletRequest request = base("GET", "cmd=known");
args(request, "nodes", nodes);
return request;
}
private void args(HttpServletRequest request, String prefix, String[] values) {
List<String> headers = Lists.newArrayList();
StringBuilder vary = new StringBuilder();
for ( int i=0; i<values.length; i++ ) {
String header = "X-HgArg-" + (i+1);
if (i>0) {
vary.append(",");
}
vary.append(header);
headers.add(header);
header(request, header, prefix + "=" + values[i]);
}
header(request, "Vary", vary.toString());
when(request.getHeaderNames()).thenReturn(Collections.enumeration(headers));
}
private HttpServletRequest base(String method, String queryStringValue) {
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getRequestURI()).thenReturn(repositoryPath);
when(request.getMethod()).thenReturn(method);
queryString(request, queryStringValue);
header(request, "Accept", "application/mercurial-0.1");
header(request, "Accept-Encoding", "identity");
header(request, "User-Agent", "mercurial/proto-1.0 (Mercurial 4.3.1)");
return request;
}
private void queryString(HttpServletRequest request, String queryString) {
when(request.getQueryString()).thenReturn(queryString);
}
private void header(HttpServletRequest request, String header, String value) {
when(request.getHeader(header)).thenReturn(value);
}
private String namespaceValue(Namespace namespace) {
return "namespace=" + namespace.toString().toLowerCase(Locale.ENGLISH);
}
}

View File

@@ -0,0 +1,192 @@
/**
* Copyright (c) 2018, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.web;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link WireProtocol}.
*/
@RunWith(MockitoJUnitRunner.class)
public class WireProtocolTest {
@Mock
private HttpServletRequest request;
@Test
public void testIsWriteRequestOnPost() {
assertIsWriteRequest("capabilities", "unbundle");
}
@Test
public void testIsWriteRequest() {
assertIsWriteRequest("unbundle");
assertIsWriteRequest("capabilities", "unbundle");
assertIsWriteRequest("capabilities", "postkeys");
assertIsReadRequest();
assertIsReadRequest("capabilities");
assertIsReadRequest("capabilities", "branches", "branchmap");
}
private void assertIsWriteRequest(String... commands) {
List<String> cmdList = Lists.newArrayList(commands);
assertTrue(WireProtocol.isWriteRequest(cmdList));
}
private void assertIsReadRequest(String... commands) {
List<String> cmdList = Lists.newArrayList(commands);
assertFalse(WireProtocol.isWriteRequest(cmdList));
}
@Test
public void testGetCommandsOf() {
expectQueryCommand("capabilities", "cmd=capabilities");
expectQueryCommand("unbundle", "cmd=unbundle");
expectQueryCommand("unbundle", "prefix=stuff&cmd=unbundle");
expectQueryCommand("unbundle", "cmd=unbundle&suffix=stuff");
expectQueryCommand("unbundle", "prefix=stuff&cmd=unbundle&suffix=stuff");
expectQueryCommand("unbundle", "bool=&cmd=unbundle");
expectQueryCommand("unbundle", "bool&cmd=unbundle");
expectQueryCommand("unbundle", "prefix=stu==ff&cmd=unbundle");
}
@Test
public void testGetCommandsOfWithHgArgsPost() throws IOException {
when(request.getMethod()).thenReturn("POST");
when(request.getQueryString()).thenReturn("cmd=batch");
when(request.getIntHeader("X-HgArgs-Post")).thenReturn(29);
when(request.getHeaderNames()).thenReturn(Collections.enumeration(Lists.newArrayList("X-HgArgs-Post")));
when(request.getInputStream()).thenReturn(new BufferedServletInputStream("cmds=lheads+%3Bknown+nodes%3D"));
List<String> commands = WireProtocol.commandsOf(new HgServletRequest(request));
assertThat(commands, contains("batch", "lheads", "known"));
}
@Test
public void testGetCommandsOfWithBatch() {
prepareBatch("cmds=heads ;known nodes,ef5993bb4abb32a0565c347844c6d939fc4f4b98");
List<String> commands = WireProtocol.commandsOf(request);
assertThat(commands, contains("batch", "heads", "known"));
}
@Test
public void testGetCommandsOfWithBatchEncoded() {
prepareBatch("cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98");
List<String> commands = WireProtocol.commandsOf(request);
assertThat(commands, contains("batch", "heads", "known"));
}
@Test
public void testGetCommandsOfWithBatchAndMutlipleLines() {
prepareBatch(
"cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98",
"cmds=unbundle; postkeys",
"cmds= branchmap p1=r2,p2=r4; listkeys"
);
List<String> commands = WireProtocol.commandsOf(request);
assertThat(commands, contains("batch", "heads", "known", "unbundle", "postkeys", "branchmap", "listkeys"));
}
private void prepareBatch(String... args) {
when(request.getQueryString()).thenReturn("cmd=batch");
List<String> headers = Lists.newArrayList();
for (int i=0; i<args.length; i++) {
String header = "X-HgArg-" + (i+1);
headers.add(header);
when(request.getHeader(header)).thenReturn(args[i]);
}
when(request.getHeaderNames()).thenReturn(Collections.enumeration(headers));
}
@Test(expected = IllegalArgumentException.class)
public void testGetCommandsOfWithMultipleCommandsInQueryString() {
when(request.getQueryString()).thenReturn("cmd=abc&cmd=def");
WireProtocol.commandsOf(request);
}
@Test
public void testGetCommandsOfWithoutCmdInQueryString() {
when(request.getQueryString()).thenReturn("abc=def&123=456");
assertTrue(WireProtocol.commandsOf(request).isEmpty());
}
@Test
public void testGetCommandsOfWithEmptyQueryString() {
when(request.getQueryString()).thenReturn("");
assertTrue(WireProtocol.commandsOf(request).isEmpty());
}
@Test
public void testGetCommandsOfWithNullQueryString() {
assertTrue(WireProtocol.commandsOf(request).isEmpty());
}
private void expectQueryCommand(String expected, String queryString) {
when(request.getQueryString()).thenReturn(queryString);
List<String> commands = WireProtocol.commandsOf(request);
assertEquals(1, commands.size());
assertTrue(commands.contains(expected));
}
private static class BufferedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
BufferedServletInputStream(String content) {
this.input = new ByteArrayInputStream(content.getBytes(Charsets.US_ASCII));
}
@Override
public int read() {
return input.read();
}
}
}