##// END OF EJS Templates
make-release: cleanup and fix bitrot...
Mads Kiilerich -
r7116:dba4e770 default
parent child Browse files
Show More
@@ -1,132 +1,134 b''
1 List of contributors to Kallithea project:
1 List of contributors to Kallithea project:
2
2
3 Dominik Ruf <dominikruf@gmail.com> 2012 2014-2018
4 Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> 2014-2018
5 Branko Majic <branko@majic.rs> 2015 2018
6 Mads Kiilerich <mads@kiilerich.com> 2016-2018
3 Mads Kiilerich <madski@unity3d.com> 2012-2017
7 Mads Kiilerich <madski@unity3d.com> 2012-2017
4 Unity Technologies 2012-2017
8 Unity Technologies 2012-2017
5 Andrew Shadura <andrew@shadura.me> 2012 2014-2017
9 Andrew Shadura <andrew@shadura.me> 2012 2014-2017
6 Dominik Ruf <dominikruf@gmail.com> 2012 2014-2017
10 Γ‰tienne Gilli <etienne.gilli@gmail.com> 2015-2017
7 Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> 2014-2017
8 SΓΈren LΓΈvborg <sorenl@unity3d.com> 2015-2017
11 SΓΈren LΓΈvborg <sorenl@unity3d.com> 2015-2017
9 Sam Jaques <sam.jaques@me.com> 2015 2017
12 Sam Jaques <sam.jaques@me.com> 2015 2017
10 Asterios Dimitriou <steve@pci.gr> 2016-2017
13 Asterios Dimitriou <steve@pci.gr> 2016-2017
11 Mads Kiilerich <mads@kiilerich.com> 2016-2017
12 Alessandro Molina <alessandro.molina@axant.it> 2017
14 Alessandro Molina <alessandro.molina@axant.it> 2017
13 Anton Schur <tonich.sh@gmail.com> 2017
15 Anton Schur <tonich.sh@gmail.com> 2017
14 Ching-Chen Mao <mao@lins.fju.edu.tw> 2017
16 Ching-Chen Mao <mao@lins.fju.edu.tw> 2017
15 Eivind Tagseth <eivindt@gmail.com> 2017
17 Eivind Tagseth <eivindt@gmail.com> 2017
16 FUJIWARA Katsunori <foozy@lares.dti.ne.jp> 2017
18 FUJIWARA Katsunori <foozy@lares.dti.ne.jp> 2017
19 Holger Schramm <info@schramm.by> 2017
17 Karl Goetz <karl@kgoetz.id.au> 2017
20 Karl Goetz <karl@kgoetz.id.au> 2017
21 Lars Kruse <devel@sumpfralle.de> 2017
18 Marko Semet <markosemet@googlemail.com> 2017
22 Marko Semet <markosemet@googlemail.com> 2017
19 Viktar Vauchkevich <victorenator@gmail.com> 2017
23 Viktar Vauchkevich <victorenator@gmail.com> 2017
20 Takumi IINO <trot.thunder@gmail.com> 2012-2016
24 Takumi IINO <trot.thunder@gmail.com> 2012-2016
21 Γ‰tienne Gilli <etienne.gilli@gmail.com> 2015-2016
22 Jan Heylen <heyleke@gmail.com> 2015-2016
25 Jan Heylen <heyleke@gmail.com> 2015-2016
23 Robert Martinez <ntttq@inboxen.org> 2015-2016
26 Robert Martinez <ntttq@inboxen.org> 2015-2016
24 Robert Rauch <mail@robertrauch.de> 2015-2016
27 Robert Rauch <mail@robertrauch.de> 2015-2016
25 Angel Ezquerra <angel.ezquerra@gmail.com> 2016
28 Angel Ezquerra <angel.ezquerra@gmail.com> 2016
26 Anton Shestakov <av6@dwimlabs.net> 2016
29 Anton Shestakov <av6@dwimlabs.net> 2016
27 Brandon Jones <bjones14@gmail.com> 2016
30 Brandon Jones <bjones14@gmail.com> 2016
28 Kateryna Musina <kateryna@unity3d.com> 2016
31 Kateryna Musina <kateryna@unity3d.com> 2016
29 Konstantin Veretennicov <kveretennicov@gmail.com> 2016
32 Konstantin Veretennicov <kveretennicov@gmail.com> 2016
30 Oscar Curero <oscar@naiandei.net> 2016
33 Oscar Curero <oscar@naiandei.net> 2016
31 Robert James Dennington <tinytimrob@googlemail.com> 2016
34 Robert James Dennington <tinytimrob@googlemail.com> 2016
32 timeless@gmail.com 2016
35 timeless@gmail.com 2016
33 YFdyh000 <yfdyh000@gmail.com> 2016
36 YFdyh000 <yfdyh000@gmail.com> 2016
34 Aras Pranckevičius <aras@unity3d.com> 2012-2013 2015
37 Aras Pranckevičius <aras@unity3d.com> 2012-2013 2015
35 Sean Farley <sean.michael.farley@gmail.com> 2013-2015
38 Sean Farley <sean.michael.farley@gmail.com> 2013-2015
36 Christian Oyarzun <oyarzun@gmail.com> 2014-2015
39 Christian Oyarzun <oyarzun@gmail.com> 2014-2015
37 Joseph Rivera <rivera.d.joseph@gmail.com> 2014-2015
40 Joseph Rivera <rivera.d.joseph@gmail.com> 2014-2015
38 Michal ČihaΕ™ <michal@cihar.com> 2014-2015
41 Michal ČihaΕ™ <michal@cihar.com> 2014-2015
39 Anatoly Bubenkov <bubenkoff@gmail.com> 2015
42 Anatoly Bubenkov <bubenkoff@gmail.com> 2015
40 Andrew Bartlett <abartlet@catalyst.net.nz> 2015
43 Andrew Bartlett <abartlet@catalyst.net.nz> 2015
41 BalÑzs Úr <urbalazs@gmail.com> 2015
44 BalÑzs Úr <urbalazs@gmail.com> 2015
42 Ben Finney <ben@benfinney.id.au> 2015
45 Ben Finney <ben@benfinney.id.au> 2015
43 Branko Majic <branko@majic.rs> 2015
44 Daniel Hobley <danielh@unity3d.com> 2015
46 Daniel Hobley <danielh@unity3d.com> 2015
45 David Avigni <david.avigni@ankapi.com> 2015
47 David Avigni <david.avigni@ankapi.com> 2015
46 Denis Blanchette <dblanchette@coveo.com> 2015
48 Denis Blanchette <dblanchette@coveo.com> 2015
47 duanhongyi <duanhongyi@doopai.com> 2015
49 duanhongyi <duanhongyi@doopai.com> 2015
48 EriCSN Chang <ericsning@gmail.com> 2015
50 EriCSN Chang <ericsning@gmail.com> 2015
49 Grzegorz Krason <grzegorz.krason@gmail.com> 2015
51 Grzegorz Krason <grzegorz.krason@gmail.com> 2015
50 JiΕ™Γ­ Suchan <yed@vanyli.net> 2015
52 JiΕ™Γ­ Suchan <yed@vanyli.net> 2015
51 Kazunari Kobayashi <kobanari@nifty.com> 2015
53 Kazunari Kobayashi <kobanari@nifty.com> 2015
52 Kevin Bullock <kbullock@ringworld.org> 2015
54 Kevin Bullock <kbullock@ringworld.org> 2015
53 kobanari <kobanari@nifty.com> 2015
55 kobanari <kobanari@nifty.com> 2015
54 Marc Abramowitz <marc@marc-abramowitz.com> 2015
56 Marc Abramowitz <marc@marc-abramowitz.com> 2015
55 Marc Villetard <marc.villetard@gmail.com> 2015
57 Marc Villetard <marc.villetard@gmail.com> 2015
56 Matthias Zilk <matthias.zilk@gmail.com> 2015
58 Matthias Zilk <matthias.zilk@gmail.com> 2015
57 Michael Pohl <michael@mipapo.de> 2015
59 Michael Pohl <michael@mipapo.de> 2015
58 Michael V. DePalatis <mike@depalatis.net> 2015
60 Michael V. DePalatis <mike@depalatis.net> 2015
59 Morten Skaaning <mortens@unity3d.com> 2015
61 Morten Skaaning <mortens@unity3d.com> 2015
60 Nick High <nick@silverchip.org> 2015
62 Nick High <nick@silverchip.org> 2015
61 Niemand Jedermann <predatorix@web.de> 2015
63 Niemand Jedermann <predatorix@web.de> 2015
62 Peter Vitt <petervitt@web.de> 2015
64 Peter Vitt <petervitt@web.de> 2015
63 Ronny Pfannschmidt <opensource@ronnypfannschmidt.de> 2015
65 Ronny Pfannschmidt <opensource@ronnypfannschmidt.de> 2015
64 Tuux <tuxa@galaxie.eu.org> 2015
66 Tuux <tuxa@galaxie.eu.org> 2015
65 Viktar Palstsiuk <vipals@gmail.com> 2015
67 Viktar Palstsiuk <vipals@gmail.com> 2015
66 Ante Ilic <ante@unity3d.com> 2014
68 Ante Ilic <ante@unity3d.com> 2014
67 Bradley M. Kuhn <bkuhn@sfconservancy.org> 2014
69 Bradley M. Kuhn <bkuhn@sfconservancy.org> 2014
68 Calinou <calinou@opmbx.org> 2014
70 Calinou <calinou@opmbx.org> 2014
69 Daniel Anderson <daniel@dattrix.com> 2014
71 Daniel Anderson <daniel@dattrix.com> 2014
70 Henrik Stuart <hg@hstuart.dk> 2014
72 Henrik Stuart <hg@hstuart.dk> 2014
71 Ingo von Borstel <kallithea@planetmaker.de> 2014
73 Ingo von Borstel <kallithea@planetmaker.de> 2014
72 Jelmer VernooΔ³ <jelmer@samba.org> 2014
74 Jelmer VernooΔ³ <jelmer@samba.org> 2014
73 Jim Hague <jim.hague@acm.org> 2014
75 Jim Hague <jim.hague@acm.org> 2014
74 Matt Fellows <kallithea@matt-fellows.me.uk> 2014
76 Matt Fellows <kallithea@matt-fellows.me.uk> 2014
75 Max Roman <max@choloclos.se> 2014
77 Max Roman <max@choloclos.se> 2014
76 Na'Tosha Bard <natosha@unity3d.com> 2014
78 Na'Tosha Bard <natosha@unity3d.com> 2014
77 Rasmus Selsmark <rasmuss@unity3d.com> 2014
79 Rasmus Selsmark <rasmuss@unity3d.com> 2014
78 Tim Freund <tim@freunds.net> 2014
80 Tim Freund <tim@freunds.net> 2014
79 Travis Burtrum <android@moparisthebest.com> 2014
81 Travis Burtrum <android@moparisthebest.com> 2014
80 Zoltan Gyarmati <mr.zoltan.gyarmati@gmail.com> 2014
82 Zoltan Gyarmati <mr.zoltan.gyarmati@gmail.com> 2014
81 Marcin KuΕΊmiΕ„ski <marcin@python-works.com> 2010-2013
83 Marcin KuΕΊmiΕ„ski <marcin@python-works.com> 2010-2013
82 xpol <xpolife@gmail.com> 2012-2013
84 xpol <xpolife@gmail.com> 2012-2013
83 Aparkar <aparkar@icloud.com> 2013
85 Aparkar <aparkar@icloud.com> 2013
84 Dennis Brakhane <brakhane@googlemail.com> 2013
86 Dennis Brakhane <brakhane@googlemail.com> 2013
85 Grzegorz RoΕΌniecki <xaerxess@gmail.com> 2013
87 Grzegorz RoΕΌniecki <xaerxess@gmail.com> 2013
86 Jonathan Sternberg <jonathansternberg@gmail.com> 2013
88 Jonathan Sternberg <jonathansternberg@gmail.com> 2013
87 Leonardo Carneiro <leonardo@unity3d.com> 2013
89 Leonardo Carneiro <leonardo@unity3d.com> 2013
88 Magnus Ericmats <magnus.ericmats@gmail.com> 2013
90 Magnus Ericmats <magnus.ericmats@gmail.com> 2013
89 Martin Vium <martinv@unity3d.com> 2013
91 Martin Vium <martinv@unity3d.com> 2013
90 Simon Lopez <simon.lopez@slopez.org> 2013
92 Simon Lopez <simon.lopez@slopez.org> 2013
91 Ton Plomp <tcplomp@gmail.com> 2013
93 Ton Plomp <tcplomp@gmail.com> 2013
92 Augusto Herrmann <augusto.herrmann@planejamento.gov.br> 2011-2012
94 Augusto Herrmann <augusto.herrmann@planejamento.gov.br> 2011-2012
93 Dan Sheridan <djs@adelard.com> 2012
95 Dan Sheridan <djs@adelard.com> 2012
94 Dies Koper <diesk@fast.au.fujitsu.com> 2012
96 Dies Koper <diesk@fast.au.fujitsu.com> 2012
95 Erwin Kroon <e.kroon@smartmetersolutions.nl> 2012
97 Erwin Kroon <e.kroon@smartmetersolutions.nl> 2012
96 H Waldo G <gwaldo@gmail.com> 2012
98 H Waldo G <gwaldo@gmail.com> 2012
97 hppj <hppj@postmage.biz> 2012
99 hppj <hppj@postmage.biz> 2012
98 Indra Talip <indra.talip@gmail.com> 2012
100 Indra Talip <indra.talip@gmail.com> 2012
99 mikespook 2012
101 mikespook 2012
100 nansenat16 <nansenat16@null.tw> 2012
102 nansenat16 <nansenat16@null.tw> 2012
101 Philip Jameson <philip.j@hostdime.com> 2012
103 Philip Jameson <philip.j@hostdime.com> 2012
102 Raoul Thill <raoul.thill@gmail.com> 2012
104 Raoul Thill <raoul.thill@gmail.com> 2012
103 Stefan Engel <mail@engel-stefan.de> 2012
105 Stefan Engel <mail@engel-stefan.de> 2012
104 Tony Bussieres <t.bussieres@gmail.com> 2012
106 Tony Bussieres <t.bussieres@gmail.com> 2012
105 Vincent Caron <vcaron@bearstech.com> 2012
107 Vincent Caron <vcaron@bearstech.com> 2012
106 Vincent Duvert <vincent@duvert.net> 2012
108 Vincent Duvert <vincent@duvert.net> 2012
107 Vladislav Poluhin <nuklea@gmail.com> 2012
109 Vladislav Poluhin <nuklea@gmail.com> 2012
108 Zachary Auclair <zach101@gmail.com> 2012
110 Zachary Auclair <zach101@gmail.com> 2012
109 Ankit Solanki <ankit.solanki@gmail.com> 2011
111 Ankit Solanki <ankit.solanki@gmail.com> 2011
110 Dmitri Kuznetsov 2011
112 Dmitri Kuznetsov 2011
111 Jared Bunting <jared.bunting@peachjean.com> 2011
113 Jared Bunting <jared.bunting@peachjean.com> 2011
112 Jason Harris <jason@jasonfharris.com> 2011
114 Jason Harris <jason@jasonfharris.com> 2011
113 Les Peabody <lpeabody@gmail.com> 2011
115 Les Peabody <lpeabody@gmail.com> 2011
114 Liad Shani <liadff@gmail.com> 2011
116 Liad Shani <liadff@gmail.com> 2011
115 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it> 2011
117 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it> 2011
116 Matt Zuba <matt.zuba@goodwillaz.org> 2011
118 Matt Zuba <matt.zuba@goodwillaz.org> 2011
117 Nicolas VINOT <aeris@imirhil.fr> 2011
119 Nicolas VINOT <aeris@imirhil.fr> 2011
118 Shawn K. O'Shea <shawn@eth0.net> 2011
120 Shawn K. O'Shea <shawn@eth0.net> 2011
119 Thayne Harbaugh <thayne@fusionio.com> 2011
121 Thayne Harbaugh <thayne@fusionio.com> 2011
120 Łukasz Balcerzak <lukaszbalcerzak@gmail.com> 2010
122 Łukasz Balcerzak <lukaszbalcerzak@gmail.com> 2010
121 Andrew Kesterson <andrew@aklabs.net>
123 Andrew Kesterson <andrew@aklabs.net>
122 cejones
124 cejones
123 David A. SjΓΈen <david.sjoen@westcon.no>
125 David A. SjΓΈen <david.sjoen@westcon.no>
124 James Rhodes <jrhodes@redpointsoftware.com.au>
126 James Rhodes <jrhodes@redpointsoftware.com.au>
125 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
127 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
126 larikale
128 larikale
127 RhodeCode GmbH
129 RhodeCode GmbH
128 Sebastian Kreutzberger <sebastian@rhodecode.com>
130 Sebastian Kreutzberger <sebastian@rhodecode.com>
129 Steve Romanow <slestak989@gmail.com>
131 Steve Romanow <slestak989@gmail.com>
130 SteveCohen
132 SteveCohen
131 Thomas <thomas@rhodecode.com>
133 Thomas <thomas@rhodecode.com>
132 Thomas Waldmann <tw-public@gmx.de>
134 Thomas Waldmann <tw-public@gmx.de>
@@ -1,29 +1,30 b''
1 include .coveragerc
1 include .coveragerc
2 include Apache-License-2.0.txt
2 include Apache-License-2.0.txt
3 include CONTRIBUTORS
3 include CONTRIBUTORS
4 include COPYING
4 include COPYING
5 include Jenkinsfile
5 include Jenkinsfile
6 include LICENSE-MERGELY.html
6 include LICENSE-MERGELY.html
7 include LICENSE.md
7 include LICENSE.md
8 include MIT-Permissive-License.txt
8 include MIT-Permissive-License.txt
9 include README.rst
9 include README.rst
10 include dev_requirements.txt
10 include dev_requirements.txt
11 include development.ini
11 include development.ini
12 include pytest.ini
12 include pytest.ini
13 include requirements.txt
13 include requirements.txt
14 include tox.ini
14 include tox.ini
15 include package.json
15 recursive-include docs *
16 recursive-include docs *
16 recursive-include init.d *
17 recursive-include init.d *
17 recursive-include kallithea/alembic *
18 recursive-include kallithea/alembic *
18 include kallithea/bin/ldap_sync.conf
19 include kallithea/bin/ldap_sync.conf
19 include kallithea/lib/paster_commands/template.ini.mako
20 include kallithea/lib/paster_commands/template.ini.mako
20 recursive-include kallithea/i18n *
21 recursive-include kallithea/i18n *
21 recursive-include kallithea/public *
22 recursive-include kallithea/public *
22 recursive-include node_modules/bootstrap *
23 recursive-include node_modules/bootstrap *
23 recursive-include kallithea/templates *
24 recursive-include kallithea/templates *
24 recursive-include kallithea/tests/fixtures *
25 recursive-include kallithea/tests/fixtures *
25 recursive-include kallithea/tests/scripts *
26 recursive-include kallithea/tests/scripts *
26 include kallithea/tests/models/test_dump_html_mails.ref.html
27 include kallithea/tests/models/test_dump_html_mails.ref.html
27 include kallithea/tests/performance/test_vcs.py
28 include kallithea/tests/performance/test_vcs.py
28 include kallithea/tests/vcs/aconfig
29 include kallithea/tests/vcs/aconfig
29 recursive-include scripts *
30 recursive-include scripts *
@@ -1,166 +1,168 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3 <%block name="title">
3 <%block name="title">
4 ${_('About')}
4 ${_('About')}
5 </%block>
5 </%block>
6 <%block name="header_menu">
6 <%block name="header_menu">
7 ${self.menu('about')}
7 ${self.menu('about')}
8 </%block>
8 </%block>
9 <%def name="main()">
9 <%def name="main()">
10
10
11 <div class="panel panel-primary">
11 <div class="panel panel-primary">
12 <div class="panel-heading">
12 <div class="panel-heading">
13 <h5 class="panel-title">${_('About')} Kallithea</h5>
13 <h5 class="panel-title">${_('About')} Kallithea</h5>
14 </div>
14 </div>
15
15
16 <div class="panel-body panel-about">
16 <div class="panel-body panel-about">
17 <p><a href="https://kallithea-scm.org/">Kallithea</a> is a project of the
17 <p><a href="https://kallithea-scm.org/">Kallithea</a> is a project of the
18 <a href="http://sfconservancy.org/">Software Freedom Conservancy, Inc.</a>
18 <a href="http://sfconservancy.org/">Software Freedom Conservancy, Inc.</a>
19 and is released under the terms of the
19 and is released under the terms of the
20 <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License,
20 <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License,
21 v 3.0 (GPLv3)</a>.</p>
21 v 3.0 (GPLv3)</a>.</p>
22
22
23 <p>Kallithea is copyrighted by various authors, including but not
23 <p>Kallithea is copyrighted by various authors, including but not
24 necessarily limited to the following:</p>
24 necessarily limited to the following:</p>
25 <ul>
25 <ul>
26
26
27 <li>Copyright &copy; 2012&ndash;2017, Mads Kiilerich</li>
27 <li>Copyright &copy; 2012&ndash;2018, Mads Kiilerich</li>
28 <li>Copyright &copy; 2012, 2014&ndash;2018, Dominik Ruf</li>
29 <li>Copyright &copy; 2014&ndash;2018, Thomas De Schampheleire</li>
30 <li>Copyright &copy; 2015, 2018, Branko Majic</li>
28 <li>Copyright &copy; 2012&ndash;2017, Unity Technologies</li>
31 <li>Copyright &copy; 2012&ndash;2017, Unity Technologies</li>
29 <li>Copyright &copy; 2012, 2014&ndash;2017, Andrew Shadura</li>
32 <li>Copyright &copy; 2012, 2014&ndash;2017, Andrew Shadura</li>
30 <li>Copyright &copy; 2012, 2014&ndash;2017, Dominik Ruf</li>
33 <li>Copyright &copy; 2015&ndash;2017, Γ‰tienne Gilli</li>
31 <li>Copyright &copy; 2014&ndash;2017, Thomas De Schampheleire</li>
32 <li>Copyright &copy; 2015&ndash;2017, SΓΈren LΓΈvborg</li>
34 <li>Copyright &copy; 2015&ndash;2017, SΓΈren LΓΈvborg</li>
33 <li>Copyright &copy; 2015, 2017, Sam Jaques</li>
35 <li>Copyright &copy; 2015, 2017, Sam Jaques</li>
34 <li>Copyright &copy; 2016&ndash;2017, Asterios Dimitriou</li>
36 <li>Copyright &copy; 2016&ndash;2017, Asterios Dimitriou</li>
35 <li>Copyright &copy; 2017, Alessandro Molina</li>
37 <li>Copyright &copy; 2017, Alessandro Molina</li>
36 <li>Copyright &copy; 2017, Anton Schur</li>
38 <li>Copyright &copy; 2017, Anton Schur</li>
37 <li>Copyright &copy; 2017, Ching-Chen Mao</li>
39 <li>Copyright &copy; 2017, Ching-Chen Mao</li>
38 <li>Copyright &copy; 2017, Eivind Tagseth</li>
40 <li>Copyright &copy; 2017, Eivind Tagseth</li>
39 <li>Copyright &copy; 2017, FUJIWARA Katsunori</li>
41 <li>Copyright &copy; 2017, FUJIWARA Katsunori</li>
42 <li>Copyright &copy; 2017, Holger Schramm</li>
40 <li>Copyright &copy; 2017, Karl Goetz</li>
43 <li>Copyright &copy; 2017, Karl Goetz</li>
44 <li>Copyright &copy; 2017, Lars Kruse</li>
41 <li>Copyright &copy; 2017, Marko Semet</li>
45 <li>Copyright &copy; 2017, Marko Semet</li>
42 <li>Copyright &copy; 2017, Viktar Vauchkevich</li>
46 <li>Copyright &copy; 2017, Viktar Vauchkevich</li>
43 <li>Copyright &copy; 2012&ndash;2016, Takumi IINO</li>
47 <li>Copyright &copy; 2012&ndash;2016, Takumi IINO</li>
44 <li>Copyright &copy; 2015&ndash;2016, Γ‰tienne Gilli</li>
45 <li>Copyright &copy; 2015&ndash;2016, Jan Heylen</li>
48 <li>Copyright &copy; 2015&ndash;2016, Jan Heylen</li>
46 <li>Copyright &copy; 2015&ndash;2016, Robert Martinez</li>
49 <li>Copyright &copy; 2015&ndash;2016, Robert Martinez</li>
47 <li>Copyright &copy; 2015&ndash;2016, Robert Rauch</li>
50 <li>Copyright &copy; 2015&ndash;2016, Robert Rauch</li>
48 <li>Copyright &copy; 2016, Angel Ezquerra</li>
51 <li>Copyright &copy; 2016, Angel Ezquerra</li>
49 <li>Copyright &copy; 2016, Anton Shestakov</li>
52 <li>Copyright &copy; 2016, Anton Shestakov</li>
50 <li>Copyright &copy; 2016, Brandon Jones</li>
53 <li>Copyright &copy; 2016, Brandon Jones</li>
51 <li>Copyright &copy; 2016, Kateryna Musina</li>
54 <li>Copyright &copy; 2016, Kateryna Musina</li>
52 <li>Copyright &copy; 2016, Konstantin Veretennicov</li>
55 <li>Copyright &copy; 2016, Konstantin Veretennicov</li>
53 <li>Copyright &copy; 2016, Oscar Curero</li>
56 <li>Copyright &copy; 2016, Oscar Curero</li>
54 <li>Copyright &copy; 2016, Robert James Dennington</li>
57 <li>Copyright &copy; 2016, Robert James Dennington</li>
55 <li>Copyright &copy; 2016, timeless@gmail.com</li>
58 <li>Copyright &copy; 2016, timeless@gmail.com</li>
56 <li>Copyright &copy; 2016, YFdyh000</li>
59 <li>Copyright &copy; 2016, YFdyh000</li>
57 <li>Copyright &copy; 2012&ndash;2013, 2015, Aras Pranckevičius</li>
60 <li>Copyright &copy; 2012&ndash;2013, 2015, Aras Pranckevičius</li>
58 <li>Copyright &copy; 2014&ndash;2015, Christian Oyarzun</li>
61 <li>Copyright &copy; 2014&ndash;2015, Christian Oyarzun</li>
59 <li>Copyright &copy; 2014&ndash;2015, Joseph Rivera</li>
62 <li>Copyright &copy; 2014&ndash;2015, Joseph Rivera</li>
60 <li>Copyright &copy; 2014&ndash;2015, Michal ČihaΕ™</li>
63 <li>Copyright &copy; 2014&ndash;2015, Michal ČihaΕ™</li>
61 <li>Copyright &copy; 2014&ndash;2015, Sean Farley</li>
64 <li>Copyright &copy; 2014&ndash;2015, Sean Farley</li>
62 <li>Copyright &copy; 2015, Anatoly Bubenkov</li>
65 <li>Copyright &copy; 2015, Anatoly Bubenkov</li>
63 <li>Copyright &copy; 2015, Andrew Bartlett</li>
66 <li>Copyright &copy; 2015, Andrew Bartlett</li>
64 <li>Copyright &copy; 2015, BalÑzs Úr</li>
67 <li>Copyright &copy; 2015, BalÑzs Úr</li>
65 <li>Copyright &copy; 2015, Ben Finney</li>
68 <li>Copyright &copy; 2015, Ben Finney</li>
66 <li>Copyright &copy; 2015, Branko Majic</li>
67 <li>Copyright &copy; 2015, Daniel Hobley</li>
69 <li>Copyright &copy; 2015, Daniel Hobley</li>
68 <li>Copyright &copy; 2015, David Avigni</li>
70 <li>Copyright &copy; 2015, David Avigni</li>
69 <li>Copyright &copy; 2015, Denis Blanchette</li>
71 <li>Copyright &copy; 2015, Denis Blanchette</li>
70 <li>Copyright &copy; 2015, duanhongyi</li>
72 <li>Copyright &copy; 2015, duanhongyi</li>
71 <li>Copyright &copy; 2015, EriCSN Chang</li>
73 <li>Copyright &copy; 2015, EriCSN Chang</li>
72 <li>Copyright &copy; 2015, Grzegorz Krason</li>
74 <li>Copyright &copy; 2015, Grzegorz Krason</li>
73 <li>Copyright &copy; 2015, JiΕ™Γ­ Suchan</li>
75 <li>Copyright &copy; 2015, JiΕ™Γ­ Suchan</li>
74 <li>Copyright &copy; 2015, Kazunari Kobayashi</li>
76 <li>Copyright &copy; 2015, Kazunari Kobayashi</li>
75 <li>Copyright &copy; 2015, Kevin Bullock</li>
77 <li>Copyright &copy; 2015, Kevin Bullock</li>
76 <li>Copyright &copy; 2015, kobanari</li>
78 <li>Copyright &copy; 2015, kobanari</li>
77 <li>Copyright &copy; 2015, Marc Abramowitz</li>
79 <li>Copyright &copy; 2015, Marc Abramowitz</li>
78 <li>Copyright &copy; 2015, Marc Villetard</li>
80 <li>Copyright &copy; 2015, Marc Villetard</li>
79 <li>Copyright &copy; 2015, Matthias Zilk</li>
81 <li>Copyright &copy; 2015, Matthias Zilk</li>
80 <li>Copyright &copy; 2015, Michael Pohl</li>
82 <li>Copyright &copy; 2015, Michael Pohl</li>
81 <li>Copyright &copy; 2015, Michael V. DePalatis</li>
83 <li>Copyright &copy; 2015, Michael V. DePalatis</li>
82 <li>Copyright &copy; 2015, Morten Skaaning</li>
84 <li>Copyright &copy; 2015, Morten Skaaning</li>
83 <li>Copyright &copy; 2015, Nick High</li>
85 <li>Copyright &copy; 2015, Nick High</li>
84 <li>Copyright &copy; 2015, Niemand Jedermann</li>
86 <li>Copyright &copy; 2015, Niemand Jedermann</li>
85 <li>Copyright &copy; 2015, Peter Vitt</li>
87 <li>Copyright &copy; 2015, Peter Vitt</li>
86 <li>Copyright &copy; 2015, Ronny Pfannschmidt</li>
88 <li>Copyright &copy; 2015, Ronny Pfannschmidt</li>
87 <li>Copyright &copy; 2015, Tuux</li>
89 <li>Copyright &copy; 2015, Tuux</li>
88 <li>Copyright &copy; 2015, Viktar Palstsiuk</li>
90 <li>Copyright &copy; 2015, Viktar Palstsiuk</li>
89 <li>Copyright &copy; 2014, Ante Ilic</li>
91 <li>Copyright &copy; 2014, Ante Ilic</li>
90 <li>Copyright &copy; 2014, Bradley M. Kuhn</li>
92 <li>Copyright &copy; 2014, Bradley M. Kuhn</li>
91 <li>Copyright &copy; 2014, Calinou</li>
93 <li>Copyright &copy; 2014, Calinou</li>
92 <li>Copyright &copy; 2014, Daniel Anderson</li>
94 <li>Copyright &copy; 2014, Daniel Anderson</li>
93 <li>Copyright &copy; 2014, Henrik Stuart</li>
95 <li>Copyright &copy; 2014, Henrik Stuart</li>
94 <li>Copyright &copy; 2014, Ingo von Borstel</li>
96 <li>Copyright &copy; 2014, Ingo von Borstel</li>
95 <li>Copyright &copy; 2014, Jelmer VernooΔ³</li>
97 <li>Copyright &copy; 2014, Jelmer VernooΔ³</li>
96 <li>Copyright &copy; 2014, Jim Hague</li>
98 <li>Copyright &copy; 2014, Jim Hague</li>
97 <li>Copyright &copy; 2014, Matt Fellows</li>
99 <li>Copyright &copy; 2014, Matt Fellows</li>
98 <li>Copyright &copy; 2014, Max Roman</li>
100 <li>Copyright &copy; 2014, Max Roman</li>
99 <li>Copyright &copy; 2014, Na'Tosha Bard</li>
101 <li>Copyright &copy; 2014, Na'Tosha Bard</li>
100 <li>Copyright &copy; 2014, Rasmus Selsmark</li>
102 <li>Copyright &copy; 2014, Rasmus Selsmark</li>
101 <li>Copyright &copy; 2014, Tim Freund</li>
103 <li>Copyright &copy; 2014, Tim Freund</li>
102 <li>Copyright &copy; 2014, Travis Burtrum</li>
104 <li>Copyright &copy; 2014, Travis Burtrum</li>
103 <li>Copyright &copy; 2014, Zoltan Gyarmati</li>
105 <li>Copyright &copy; 2014, Zoltan Gyarmati</li>
104 <li>Copyright &copy; 2010&ndash;2013, Marcin KuΕΊmiΕ„ski</li>
106 <li>Copyright &copy; 2010&ndash;2013, Marcin KuΕΊmiΕ„ski</li>
105 <li>Copyright &copy; 2010&ndash;2013, RhodeCode GmbH</li>
107 <li>Copyright &copy; 2010&ndash;2013, RhodeCode GmbH</li>
106 <li>Copyright &copy; 2011, 2013, Aparkar</li>
108 <li>Copyright &copy; 2011, 2013, Aparkar</li>
107 <li>Copyright &copy; 2012&ndash;2013, xpol</li>
109 <li>Copyright &copy; 2012&ndash;2013, xpol</li>
108 <li>Copyright &copy; 2013, Dennis Brakhane</li>
110 <li>Copyright &copy; 2013, Dennis Brakhane</li>
109 <li>Copyright &copy; 2013, Grzegorz RoΕΌniecki</li>
111 <li>Copyright &copy; 2013, Grzegorz RoΕΌniecki</li>
110 <li>Copyright &copy; 2013, Jonathan Sternberg</li>
112 <li>Copyright &copy; 2013, Jonathan Sternberg</li>
111 <li>Copyright &copy; 2013, Leonardo Carneiro</li>
113 <li>Copyright &copy; 2013, Leonardo Carneiro</li>
112 <li>Copyright &copy; 2013, Magnus Ericmats</li>
114 <li>Copyright &copy; 2013, Magnus Ericmats</li>
113 <li>Copyright &copy; 2013, Martin Vium</li>
115 <li>Copyright &copy; 2013, Martin Vium</li>
114 <li>Copyright &copy; 2013, Simon Lopez</li>
116 <li>Copyright &copy; 2013, Simon Lopez</li>
115 <li>Copyright &copy; 2011&ndash;2012, Augusto Herrmann</li>
117 <li>Copyright &copy; 2011&ndash;2012, Augusto Herrmann</li>
116 <li>Copyright &copy; 2012, Dan Sheridan</li>
118 <li>Copyright &copy; 2012, Dan Sheridan</li>
117 <li>Copyright &copy; 2012, H Waldo G</li>
119 <li>Copyright &copy; 2012, H Waldo G</li>
118 <li>Copyright &copy; 2012, hppj</li>
120 <li>Copyright &copy; 2012, hppj</li>
119 <li>Copyright &copy; 2012, Indra Talip</li>
121 <li>Copyright &copy; 2012, Indra Talip</li>
120 <li>Copyright &copy; 2012, mikespook</li>
122 <li>Copyright &copy; 2012, mikespook</li>
121 <li>Copyright &copy; 2012, nansenat16</li>
123 <li>Copyright &copy; 2012, nansenat16</li>
122 <li>Copyright &copy; 2012, Philip Jameson</li>
124 <li>Copyright &copy; 2012, Philip Jameson</li>
123 <li>Copyright &copy; 2012, Raoul Thill</li>
125 <li>Copyright &copy; 2012, Raoul Thill</li>
124 <li>Copyright &copy; 2012, Tony Bussieres</li>
126 <li>Copyright &copy; 2012, Tony Bussieres</li>
125 <li>Copyright &copy; 2012, Vincent Duvert</li>
127 <li>Copyright &copy; 2012, Vincent Duvert</li>
126 <li>Copyright &copy; 2012, Vladislav Poluhin</li>
128 <li>Copyright &copy; 2012, Vladislav Poluhin</li>
127 <li>Copyright &copy; 2012, Zachary Auclair</li>
129 <li>Copyright &copy; 2012, Zachary Auclair</li>
128 <li>Copyright &copy; 2011, Ankit Solanki</li>
130 <li>Copyright &copy; 2011, Ankit Solanki</li>
129 <li>Copyright &copy; 2011, Dmitri Kuznetsov</li>
131 <li>Copyright &copy; 2011, Dmitri Kuznetsov</li>
130 <li>Copyright &copy; 2011, Jared Bunting</li>
132 <li>Copyright &copy; 2011, Jared Bunting</li>
131 <li>Copyright &copy; 2011, Jason Harris</li>
133 <li>Copyright &copy; 2011, Jason Harris</li>
132 <li>Copyright &copy; 2011, Les Peabody</li>
134 <li>Copyright &copy; 2011, Les Peabody</li>
133 <li>Copyright &copy; 2011, Liad Shani</li>
135 <li>Copyright &copy; 2011, Liad Shani</li>
134 <li>Copyright &copy; 2011, Lorenzo M. Catucci</li>
136 <li>Copyright &copy; 2011, Lorenzo M. Catucci</li>
135 <li>Copyright &copy; 2011, Matt Zuba</li>
137 <li>Copyright &copy; 2011, Matt Zuba</li>
136 <li>Copyright &copy; 2011, Nicolas VINOT</li>
138 <li>Copyright &copy; 2011, Nicolas VINOT</li>
137 <li>Copyright &copy; 2011, Shawn K. O'Shea</li>
139 <li>Copyright &copy; 2011, Shawn K. O'Shea</li>
138 <li>Copyright &copy; 2010, Łukasz Balcerzak</li>
140 <li>Copyright &copy; 2010, Łukasz Balcerzak</li>
139
141
140 ## We did not list the following copyright holders, given that they appeared
142 ## We did not list the following copyright holders, given that they appeared
141 ## to use for-profit company affiliations in their contribution in the
143 ## to use for-profit company affiliations in their contribution in the
142 ## Mercurial log and therefore I didn't know if copyright was theirs or
144 ## Mercurial log and therefore I didn't know if copyright was theirs or
143 ## their company's.
145 ## their company's.
144 ## Copyright &copy; 2011 Thayne Harbaugh <thayne@fusionio.com>
146 ## Copyright &copy; 2011 Thayne Harbaugh <thayne@fusionio.com>
145 ## Copyright &copy; 2012 Dies Koper <diesk@fast.au.fujitsu.com>
147 ## Copyright &copy; 2012 Dies Koper <diesk@fast.au.fujitsu.com>
146 ## Copyright &copy; 2012 Erwin Kroon <e.kroon@smartmetersolutions.nl>
148 ## Copyright &copy; 2012 Erwin Kroon <e.kroon@smartmetersolutions.nl>
147 ## Copyright &copy; 2012 Vincent Caron <vcaron@bearstech.com>
149 ## Copyright &copy; 2012 Vincent Caron <vcaron@bearstech.com>
148 ##
150 ##
149 ## These contributors' contributions may not be copyrightable:
151 ## These contributors' contributions may not be copyrightable:
150 ## philip.j@hostdime.com in 2012
152 ## philip.j@hostdime.com in 2012
151 ## Stefan Engel <mail@engel-stefan.de> in 2012
153 ## Stefan Engel <mail@engel-stefan.de> in 2012
152 ## Ton Plomp <tcplomp@gmail.com> in 2013
154 ## Ton Plomp <tcplomp@gmail.com> in 2013
153 ##
155 ##
154 </ul>
156 </ul>
155
157
156 <p>The above are the copyright holders who have submitted direct
158 <p>The above are the copyright holders who have submitted direct
157 contributions to the Kallithea repository. In
159 contributions to the Kallithea repository. In
158 the <a href="https://kallithea-scm.org/repos/kallithea">Kallithea source
160 the <a href="https://kallithea-scm.org/repos/kallithea">Kallithea source
159 code</a>, there is
161 code</a>, there is
160 a <a href="https://kallithea-scm.org/repos/kallithea/files/tip/LICENSE.md">list
162 a <a href="https://kallithea-scm.org/repos/kallithea/files/tip/LICENSE.md">list
161 of third-party libraries and code that Kallithea incorporates</a>.</p>
163 of third-party libraries and code that Kallithea incorporates</a>.</p>
162
164
163 </div>
165 </div>
164 </div>
166 </div>
165
167
166 </%def>
168 </%def>
@@ -1,556 +1,556 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <!-- CONTENT -->
4 <!-- CONTENT -->
5 <div id="content" class="container-fluid">
5 <div id="content" class="container-fluid">
6 ${self.flash_msg()}
6 ${self.flash_msg()}
7 <div id="main">
7 <div id="main">
8 ${next.main()}
8 ${next.main()}
9 </div>
9 </div>
10 </div>
10 </div>
11 <!-- END CONTENT -->
11 <!-- END CONTENT -->
12
12
13 <!-- FOOTER -->
13 <!-- FOOTER -->
14 <div class="footer navbar navbar-inverse">
14 <div class="footer navbar navbar-inverse">
15 <span class="navbar-text pull-left">
15 <span class="navbar-text pull-left">
16 ${_('Server instance: %s') % c.instance_id if c.instance_id else ''}
16 ${_('Server instance: %s') % c.instance_id if c.instance_id else ''}
17 </span>
17 </span>
18 <span class="navbar-text pull-right">
18 <span class="navbar-text pull-right">
19 This site is powered by
19 This site is powered by
20 %if c.visual.show_version:
20 %if c.visual.show_version:
21 <a class="navbar-link" href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a> ${c.kallithea_version},
21 <a class="navbar-link" href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a> ${c.kallithea_version},
22 %else:
22 %else:
23 <a class="navbar-link" href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a>,
23 <a class="navbar-link" href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a>,
24 %endif
24 %endif
25 which is
25 which is
26 <a class="navbar-link" href="${h.canonical_url('about')}#copyright">&copy; 2010&ndash;2017 by various authors &amp; licensed under GPLv3</a>.
26 <a class="navbar-link" href="${h.canonical_url('about')}#copyright">&copy; 2010&ndash;2018 by various authors &amp; licensed under GPLv3</a>.
27 %if c.issues_url:
27 %if c.issues_url:
28 &ndash; <a class="navbar-link" href="${c.issues_url}" target="_blank">${_('Support')}</a>
28 &ndash; <a class="navbar-link" href="${c.issues_url}" target="_blank">${_('Support')}</a>
29 %endif
29 %endif
30 </span>
30 </span>
31 </div>
31 </div>
32
32
33 <!-- END FOOTER -->
33 <!-- END FOOTER -->
34
34
35 ### MAKO DEFS ###
35 ### MAKO DEFS ###
36
36
37 <%block name="branding_title">
37 <%block name="branding_title">
38 %if c.site_name:
38 %if c.site_name:
39 &middot; ${c.site_name}
39 &middot; ${c.site_name}
40 %endif
40 %endif
41 </%block>
41 </%block>
42
42
43 <%def name="flash_msg()">
43 <%def name="flash_msg()">
44 <%include file="/base/flash_msg.html"/>
44 <%include file="/base/flash_msg.html"/>
45 </%def>
45 </%def>
46
46
47 <%def name="breadcrumbs()">
47 <%def name="breadcrumbs()">
48 <div class="breadcrumbs panel-title">
48 <div class="breadcrumbs panel-title">
49 ${self.breadcrumbs_links()}
49 ${self.breadcrumbs_links()}
50 </div>
50 </div>
51 </%def>
51 </%def>
52
52
53 <%def name="admin_menu()">
53 <%def name="admin_menu()">
54 <ul class="dropdown-menu" role="menu">
54 <ul class="dropdown-menu" role="menu">
55 <li><a href="${h.url('admin_home')}"><i class="icon-book"></i>${_('Admin Journal')}</a></li>
55 <li><a href="${h.url('admin_home')}"><i class="icon-book"></i>${_('Admin Journal')}</a></li>
56 <li><a href="${h.url('repos')}"><i class="icon-database"></i>${_('Repositories')}</a></li>
56 <li><a href="${h.url('repos')}"><i class="icon-database"></i>${_('Repositories')}</a></li>
57 <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i>${_('Repository Groups')}</a></li>
57 <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i>${_('Repository Groups')}</a></li>
58 <li><a href="${h.url('users')}"><i class="icon-user"></i>${_('Users')}</a></li>
58 <li><a href="${h.url('users')}"><i class="icon-user"></i>${_('Users')}</a></li>
59 <li><a href="${h.url('users_groups')}"><i class="icon-users"></i>${_('User Groups')}</a></li>
59 <li><a href="${h.url('users_groups')}"><i class="icon-users"></i>${_('User Groups')}</a></li>
60 <li><a href="${h.url('admin_permissions')}"><i class="icon-block"></i>${_('Default Permissions')}</a></li>
60 <li><a href="${h.url('admin_permissions')}"><i class="icon-block"></i>${_('Default Permissions')}</a></li>
61 <li><a href="${h.url('auth_home')}"><i class="icon-key"></i>${_('Authentication')}</a></li>
61 <li><a href="${h.url('auth_home')}"><i class="icon-key"></i>${_('Authentication')}</a></li>
62 <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i>${_('Repository Defaults')}</a></li>
62 <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i>${_('Repository Defaults')}</a></li>
63 <li class="last"><a href="${h.url('admin_settings')}"><i class="icon-gear"></i>${_('Settings')}</a></li>
63 <li class="last"><a href="${h.url('admin_settings')}"><i class="icon-gear"></i>${_('Settings')}</a></li>
64 </ul>
64 </ul>
65
65
66 </%def>
66 </%def>
67
67
68
68
69 ## admin menu used for people that have some admin resources
69 ## admin menu used for people that have some admin resources
70 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
70 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
71 <ul class="dropdown-menu" role="menu">
71 <ul class="dropdown-menu" role="menu">
72 %if repositories:
72 %if repositories:
73 <li><a href="${h.url('repos')}"><i class="icon-database"></i>${_('Repositories')}</a></li>
73 <li><a href="${h.url('repos')}"><i class="icon-database"></i>${_('Repositories')}</a></li>
74 %endif
74 %endif
75 %if repository_groups:
75 %if repository_groups:
76 <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i>${_('Repository Groups')}</a></li>
76 <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i>${_('Repository Groups')}</a></li>
77 %endif
77 %endif
78 %if user_groups:
78 %if user_groups:
79 <li><a href="${h.url('users_groups')}"><i class="icon-users"></i>${_('User Groups')}</a></li>
79 <li><a href="${h.url('users_groups')}"><i class="icon-users"></i>${_('User Groups')}</a></li>
80 %endif
80 %endif
81 </ul>
81 </ul>
82 </%def>
82 </%def>
83
83
84 <%def name="repolabel(repo)">
84 <%def name="repolabel(repo)">
85 %if h.is_hg(repo):
85 %if h.is_hg(repo):
86 <span class="label label-repo" title="${_('Mercurial repository')}">hg</span>
86 <span class="label label-repo" title="${_('Mercurial repository')}">hg</span>
87 %endif
87 %endif
88 %if h.is_git(repo):
88 %if h.is_git(repo):
89 <span class="label label-repo" title="${_('Git repository')}">git</span>
89 <span class="label label-repo" title="${_('Git repository')}">git</span>
90 %endif
90 %endif
91 </%def>
91 </%def>
92
92
93 <%def name="repo_context_bar(current=None, rev=None)">
93 <%def name="repo_context_bar(current=None, rev=None)">
94 <% rev = None if rev == 'tip' else rev %>
94 <% rev = None if rev == 'tip' else rev %>
95 <!--- CONTEXT BAR -->
95 <!--- CONTEXT BAR -->
96 <nav id="context-bar" class="navbar navbar-inverse">
96 <nav id="context-bar" class="navbar navbar-inverse">
97 <div class="container-fluid">
97 <div class="container-fluid">
98 <div class="navbar-header">
98 <div class="navbar-header">
99 <div class="navbar-brand">
99 <div class="navbar-brand">
100 ${repolabel(c.db_repo)}
100 ${repolabel(c.db_repo)}
101
101
102 ## public/private
102 ## public/private
103 %if c.db_repo.private:
103 %if c.db_repo.private:
104 <i class="icon-lock"></i>
104 <i class="icon-lock"></i>
105 %else:
105 %else:
106 <i class="icon-globe"></i>
106 <i class="icon-globe"></i>
107 %endif
107 %endif
108 %for group in c.db_repo.groups_with_parents:
108 %for group in c.db_repo.groups_with_parents:
109 ${h.link_to(group.name, url('repos_group_home', group_name=group.group_name), class_='navbar-link')}
109 ${h.link_to(group.name, url('repos_group_home', group_name=group.group_name), class_='navbar-link')}
110 &raquo;
110 &raquo;
111 %endfor
111 %endfor
112 ${h.link_to(c.db_repo.just_name, url('summary_home', repo_name=c.db_repo.repo_name), class_='navbar-link')}
112 ${h.link_to(c.db_repo.just_name, url('summary_home', repo_name=c.db_repo.repo_name), class_='navbar-link')}
113
113
114 %if current == 'createfork':
114 %if current == 'createfork':
115 - ${_('Create Fork')}
115 - ${_('Create Fork')}
116 %endif
116 %endif
117 </div>
117 </div>
118 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#context-pages" aria-expanded="false">
118 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#context-pages" aria-expanded="false">
119 <span class="sr-only">Toggle navigation</span>
119 <span class="sr-only">Toggle navigation</span>
120 <span class="icon-bar"></span>
120 <span class="icon-bar"></span>
121 <span class="icon-bar"></span>
121 <span class="icon-bar"></span>
122 <span class="icon-bar"></span>
122 <span class="icon-bar"></span>
123 </button>
123 </button>
124 </div>
124 </div>
125 <div id="context-pages" class="navbar-collapse collapse">
125 <div id="context-pages" class="navbar-collapse collapse">
126 <ul class="nav navbar-nav navbar-right">
126 <ul class="nav navbar-nav navbar-right">
127 <li class="${'active' if current == 'summary' else ''}" data-context="summary"><a href="${h.url('summary_home', repo_name=c.repo_name)}"><i class="icon-doc-text"></i>${_('Summary')}</a></li>
127 <li class="${'active' if current == 'summary' else ''}" data-context="summary"><a href="${h.url('summary_home', repo_name=c.repo_name)}"><i class="icon-doc-text"></i>${_('Summary')}</a></li>
128 %if rev:
128 %if rev:
129 <li class="${'active' if current == 'changelog' else ''}" data-context="changelog"><a href="${h.url('changelog_file_home', repo_name=c.repo_name, revision=rev, f_path='')}"><i class="icon-clock"></i>${_('Changelog')}</a></li>
129 <li class="${'active' if current == 'changelog' else ''}" data-context="changelog"><a href="${h.url('changelog_file_home', repo_name=c.repo_name, revision=rev, f_path='')}"><i class="icon-clock"></i>${_('Changelog')}</a></li>
130 %else:
130 %else:
131 <li class="${'active' if current == 'changelog' else ''}" data-context="changelog"><a href="${h.url('changelog_home', repo_name=c.repo_name)}"><i class="icon-clock"></i>${_('Changelog')}</a></li>
131 <li class="${'active' if current == 'changelog' else ''}" data-context="changelog"><a href="${h.url('changelog_home', repo_name=c.repo_name)}"><i class="icon-clock"></i>${_('Changelog')}</a></li>
132 %endif
132 %endif
133 <li class="${'active' if current == 'files' else ''}" data-context="files"><a href="${h.url('files_home', repo_name=c.repo_name, revision=rev or 'tip')}"><i class="icon-doc-inv"></i>${_('Files')}</a></li>
133 <li class="${'active' if current == 'files' else ''}" data-context="files"><a href="${h.url('files_home', repo_name=c.repo_name, revision=rev or 'tip')}"><i class="icon-doc-inv"></i>${_('Files')}</a></li>
134 <li class="${'active' if current == 'showpullrequest' else ''}" data-context="showpullrequest">
134 <li class="${'active' if current == 'showpullrequest' else ''}" data-context="showpullrequest">
135 <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}"> <i class="icon-git-pull-request"></i>${_('Pull Requests')}
135 <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}"> <i class="icon-git-pull-request"></i>${_('Pull Requests')}
136 %if c.repository_pull_requests:
136 %if c.repository_pull_requests:
137 <span class="badge">${c.repository_pull_requests}</span>
137 <span class="badge">${c.repository_pull_requests}</span>
138 %endif
138 %endif
139 </a>
139 </a>
140 </li>
140 </li>
141 <li class="${'active' if current == 'switch-to' else ''}" data-context="switch-to">
141 <li class="${'active' if current == 'switch-to' else ''}" data-context="switch-to">
142 <input id="branch_switcher" name="branch_switcher" type="hidden">
142 <input id="branch_switcher" name="branch_switcher" type="hidden">
143 </li>
143 </li>
144 <li class="${'active' if current == 'options' else ''} dropdown" data-context="options">
144 <li class="${'active' if current == 'options' else ''} dropdown" data-context="options">
145 %if h.HasRepoPermissionLevel('admin')(c.repo_name):
145 %if h.HasRepoPermissionLevel('admin')(c.repo_name):
146 <a href="${h.url('edit_repo',repo_name=c.repo_name)}" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true"><i class="icon-wrench"></i>${_('Options')} <i class="caret"></i></a>
146 <a href="${h.url('edit_repo',repo_name=c.repo_name)}" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true"><i class="icon-wrench"></i>${_('Options')} <i class="caret"></i></a>
147 %else:
147 %else:
148 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true"><i class="icon-wrench"></i>${_('Options')} <i class="caret"></i></a>
148 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true"><i class="icon-wrench"></i>${_('Options')} <i class="caret"></i></a>
149 %endif
149 %endif
150 <ul class="dropdown-menu" role="menu" aria-hidden="true">
150 <ul class="dropdown-menu" role="menu" aria-hidden="true">
151 %if h.HasRepoPermissionLevel('admin')(c.repo_name):
151 %if h.HasRepoPermissionLevel('admin')(c.repo_name):
152 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}"><i class="icon-gear"></i>${_('Settings')}</a></li>
152 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}"><i class="icon-gear"></i>${_('Settings')}</a></li>
153 %endif
153 %endif
154 %if c.db_repo.fork:
154 %if c.db_repo.fork:
155 <li><a href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1], other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}">
155 <li><a href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1], other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}">
156 <i class="icon-git-compare"></i>${_('Compare Fork')}</a></li>
156 <i class="icon-git-compare"></i>${_('Compare Fork')}</a></li>
157 %endif
157 %endif
158 <li><a href="${h.url('compare_home',repo_name=c.repo_name)}"><i class="icon-git-compare"></i>${_('Compare')}</a></li>
158 <li><a href="${h.url('compare_home',repo_name=c.repo_name)}"><i class="icon-git-compare"></i>${_('Compare')}</a></li>
159
159
160 <li><a href="${h.url('search_repo',repo_name=c.repo_name)}"><i class="icon-search"></i>${_('Search')}</a></li>
160 <li><a href="${h.url('search_repo',repo_name=c.repo_name)}"><i class="icon-search"></i>${_('Search')}</a></li>
161
161
162 %if h.HasRepoPermissionLevel('write')(c.repo_name) and c.db_repo.enable_locking:
162 %if h.HasRepoPermissionLevel('write')(c.repo_name) and c.db_repo.enable_locking:
163 %if c.db_repo.locked[0]:
163 %if c.db_repo.locked[0]:
164 <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock"></i>${_('Unlock')}</a></li>
164 <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock"></i>${_('Unlock')}</a></li>
165 %else:
165 %else:
166 <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock-open-alt"></i>${_('Lock')}</a></li>
166 <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock-open-alt"></i>${_('Lock')}</a></li>
167 %endif
167 %endif
168 %endif
168 %endif
169 ## TODO: this check feels wrong, it would be better to have a check for permissions
169 ## TODO: this check feels wrong, it would be better to have a check for permissions
170 ## also it feels like a job for the controller
170 ## also it feels like a job for the controller
171 %if request.authuser.username != 'default':
171 %if request.authuser.username != 'default':
172 <li>
172 <li>
173 <a href="#" class="${'following' if c.repository_following else 'follow'}" onclick="toggleFollowingRepo(this, ${c.db_repo.repo_id});">
173 <a href="#" class="${'following' if c.repository_following else 'follow'}" onclick="toggleFollowingRepo(this, ${c.db_repo.repo_id});">
174 <span class="show-follow"><i class="icon-heart-empty"></i>${_('Follow')}</span>
174 <span class="show-follow"><i class="icon-heart-empty"></i>${_('Follow')}</span>
175 <span class="show-following"><i class="icon-heart"></i>${_('Unfollow')}</span>
175 <span class="show-following"><i class="icon-heart"></i>${_('Unfollow')}</span>
176 </a>
176 </a>
177 </li>
177 </li>
178 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i>${_('Fork')}</a></li>
178 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i>${_('Fork')}</a></li>
179 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i>${_('Create Pull Request')}</a></li>
179 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i>${_('Create Pull Request')}</a></li>
180 %endif
180 %endif
181 </ul>
181 </ul>
182 </li>
182 </li>
183 </ul>
183 </ul>
184 </div>
184 </div>
185 </div>
185 </div>
186 </nav>
186 </nav>
187 <script type="text/javascript">
187 <script type="text/javascript">
188 $(document).ready(function() {
188 $(document).ready(function() {
189 var bcache = {};
189 var bcache = {};
190
190
191 $("#branch_switcher").select2({
191 $("#branch_switcher").select2({
192 placeholder: '<i class="icon-exchange"></i>' + ${h.jshtml(_('Switch To'))} + ' <span class="caret"></span>',
192 placeholder: '<i class="icon-exchange"></i>' + ${h.jshtml(_('Switch To'))} + ' <span class="caret"></span>',
193 dropdownAutoWidth: true,
193 dropdownAutoWidth: true,
194 sortResults: prefixFirstSort,
194 sortResults: prefixFirstSort,
195 formatResult: function(obj) {
195 formatResult: function(obj) {
196 return obj.text;
196 return obj.text;
197 },
197 },
198 formatSelection: function(obj) {
198 formatSelection: function(obj) {
199 return obj.text;
199 return obj.text;
200 },
200 },
201 formatNoMatches: function(term) {
201 formatNoMatches: function(term) {
202 return ${h.jshtml(_('No matches found'))};
202 return ${h.jshtml(_('No matches found'))};
203 },
203 },
204 escapeMarkup: function(m) {
204 escapeMarkup: function(m) {
205 // don't escape our custom placeholder
205 // don't escape our custom placeholder
206 if (m.substr(0, 25) == '<i class="icon-exchange">') {
206 if (m.substr(0, 25) == '<i class="icon-exchange">') {
207 return m;
207 return m;
208 }
208 }
209
209
210 return Select2.util.escapeMarkup(m);
210 return Select2.util.escapeMarkup(m);
211 },
211 },
212 containerCssClass: "branch-switcher",
212 containerCssClass: "branch-switcher",
213 dropdownCssClass: "repo-switcher-dropdown",
213 dropdownCssClass: "repo-switcher-dropdown",
214 query: function(query) {
214 query: function(query) {
215 var key = 'cache';
215 var key = 'cache';
216 var cached = bcache[key];
216 var cached = bcache[key];
217 if (cached) {
217 if (cached) {
218 var data = {
218 var data = {
219 results: []
219 results: []
220 };
220 };
221 // filter results
221 // filter results
222 $.each(cached.results, function() {
222 $.each(cached.results, function() {
223 var section = this.text;
223 var section = this.text;
224 var children = [];
224 var children = [];
225 $.each(this.children, function() {
225 $.each(this.children, function() {
226 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
226 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
227 children.push({
227 children.push({
228 'id': this.id,
228 'id': this.id,
229 'text': this.text,
229 'text': this.text,
230 'type': this.type,
230 'type': this.type,
231 'obj': this.obj
231 'obj': this.obj
232 });
232 });
233 }
233 }
234 });
234 });
235 if (children.length !== 0) {
235 if (children.length !== 0) {
236 data.results.push({
236 data.results.push({
237 'text': section,
237 'text': section,
238 'children': children
238 'children': children
239 });
239 });
240 }
240 }
241
241
242 });
242 });
243 query.callback(data);
243 query.callback(data);
244 } else {
244 } else {
245 $.ajax({
245 $.ajax({
246 url: pyroutes.url('repo_refs_data', {
246 url: pyroutes.url('repo_refs_data', {
247 'repo_name': ${h.js(c.repo_name)}
247 'repo_name': ${h.js(c.repo_name)}
248 }),
248 }),
249 data: {},
249 data: {},
250 dataType: 'json',
250 dataType: 'json',
251 type: 'GET',
251 type: 'GET',
252 success: function(data) {
252 success: function(data) {
253 bcache[key] = data;
253 bcache[key] = data;
254 query.callback(data);
254 query.callback(data);
255 }
255 }
256 });
256 });
257 }
257 }
258 }
258 }
259 });
259 });
260
260
261 $("#branch_switcher").on('select2-selecting', function(e) {
261 $("#branch_switcher").on('select2-selecting', function(e) {
262 e.preventDefault();
262 e.preventDefault();
263 var context = $('#context-bar .active').data('context');
263 var context = $('#context-bar .active').data('context');
264 if (context == 'files') {
264 if (context == 'files') {
265 window.location = pyroutes.url('files_home', {
265 window.location = pyroutes.url('files_home', {
266 'repo_name': REPO_NAME,
266 'repo_name': REPO_NAME,
267 'revision': e.choice.id,
267 'revision': e.choice.id,
268 'f_path': '',
268 'f_path': '',
269 'at': e.choice.text
269 'at': e.choice.text
270 });
270 });
271 } else if (context == 'changelog') {
271 } else if (context == 'changelog') {
272 if (e.choice.type == 'tag' || e.choice.type == 'book') {
272 if (e.choice.type == 'tag' || e.choice.type == 'book') {
273 $("#branch_filter").append($('<'+'option/>').val(e.choice.text));
273 $("#branch_filter").append($('<'+'option/>').val(e.choice.text));
274 }
274 }
275 $("#branch_filter").val(e.choice.text).change();
275 $("#branch_filter").val(e.choice.text).change();
276 } else {
276 } else {
277 window.location = pyroutes.url('changelog_home', {
277 window.location = pyroutes.url('changelog_home', {
278 'repo_name': ${h.js(c.repo_name)},
278 'repo_name': ${h.js(c.repo_name)},
279 'branch': e.choice.text
279 'branch': e.choice.text
280 });
280 });
281 }
281 }
282 });
282 });
283 });
283 });
284 </script>
284 </script>
285 <!--- END CONTEXT BAR -->
285 <!--- END CONTEXT BAR -->
286 </%def>
286 </%def>
287
287
288 <%def name="menu(current=None)">
288 <%def name="menu(current=None)">
289 <ul id="quick" class="nav navbar-nav navbar-right">
289 <ul id="quick" class="nav navbar-nav navbar-right">
290 <!-- repo switcher -->
290 <!-- repo switcher -->
291 <li class="${'active' if current == 'repositories' else ''}">
291 <li class="${'active' if current == 'repositories' else ''}">
292 <input id="repo_switcher" name="repo_switcher" type="hidden">
292 <input id="repo_switcher" name="repo_switcher" type="hidden">
293 </li>
293 </li>
294
294
295 ##ROOT MENU
295 ##ROOT MENU
296 %if request.authuser.username != 'default':
296 %if request.authuser.username != 'default':
297 <li class="${'active' if current == 'journal' else ''}">
297 <li class="${'active' if current == 'journal' else ''}">
298 <a class="menu_link" title="${_('Show recent activity')}" href="${h.url('journal')}">
298 <a class="menu_link" title="${_('Show recent activity')}" href="${h.url('journal')}">
299 <i class="icon-book"></i>${_('Journal')}
299 <i class="icon-book"></i>${_('Journal')}
300 </a>
300 </a>
301 </li>
301 </li>
302 %else:
302 %else:
303 <li class="${'active' if current == 'journal' else ''}">
303 <li class="${'active' if current == 'journal' else ''}">
304 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
304 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
305 <i class="icon-book"></i>${_('Public journal')}
305 <i class="icon-book"></i>${_('Public journal')}
306 </a>
306 </a>
307 </li>
307 </li>
308 %endif
308 %endif
309 <li class="${'active' if current == 'gists' else ''} dropdown">
309 <li class="${'active' if current == 'gists' else ''} dropdown">
310 <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Show public gists')}" href="${h.url('gists')}">
310 <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Show public gists')}" href="${h.url('gists')}">
311 <i class="icon-clippy"></i>${_('Gists')} <span class="caret"></span>
311 <i class="icon-clippy"></i>${_('Gists')} <span class="caret"></span>
312 </a>
312 </a>
313 <ul class="dropdown-menu" role="menu">
313 <ul class="dropdown-menu" role="menu">
314 <li><a href="${h.url('new_gist', public=1)}"><i class="icon-paste"></i>${_('Create New Gist')}</a></li>
314 <li><a href="${h.url('new_gist', public=1)}"><i class="icon-paste"></i>${_('Create New Gist')}</a></li>
315 <li><a href="${h.url('gists')}"><i class="icon-globe"></i>${_('All Public Gists')}</a></li>
315 <li><a href="${h.url('gists')}"><i class="icon-globe"></i>${_('All Public Gists')}</a></li>
316 %if request.authuser.username != 'default':
316 %if request.authuser.username != 'default':
317 <li><a href="${h.url('gists', public=1)}"><i class="icon-user"></i>${_('My Public Gists')}</a></li>
317 <li><a href="${h.url('gists', public=1)}"><i class="icon-user"></i>${_('My Public Gists')}</a></li>
318 <li><a href="${h.url('gists', private=1)}"><i class="icon-lock"></i>${_('My Private Gists')}</a></li>
318 <li><a href="${h.url('gists', private=1)}"><i class="icon-lock"></i>${_('My Private Gists')}</a></li>
319 %endif
319 %endif
320 </ul>
320 </ul>
321 </li>
321 </li>
322 <li class="${'active' if current == 'search' else ''}">
322 <li class="${'active' if current == 'search' else ''}">
323 <a class="menu_link" title="${_('Search in repositories')}" href="${h.url('search')}">
323 <a class="menu_link" title="${_('Search in repositories')}" href="${h.url('search')}">
324 <i class="icon-search"></i>${_('Search')}
324 <i class="icon-search"></i>${_('Search')}
325 </a>
325 </a>
326 </li>
326 </li>
327 % if h.HasPermissionAny('hg.admin')('access admin main page'):
327 % if h.HasPermissionAny('hg.admin')('access admin main page'):
328 <li class="${'active' if current == 'admin' else ''} dropdown">
328 <li class="${'active' if current == 'admin' else ''} dropdown">
329 <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Admin')}" href="${h.url('admin_home')}">
329 <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Admin')}" href="${h.url('admin_home')}">
330 <i class="icon-gear"></i>${_('Admin')} <span class="caret"></span>
330 <i class="icon-gear"></i>${_('Admin')} <span class="caret"></span>
331 </a>
331 </a>
332 ${admin_menu()}
332 ${admin_menu()}
333 </li>
333 </li>
334 % elif request.authuser.repositories_admin or request.authuser.repository_groups_admin or request.authuser.user_groups_admin:
334 % elif request.authuser.repositories_admin or request.authuser.repository_groups_admin or request.authuser.user_groups_admin:
335 <li class="${'active' if current == 'admin' else ''} dropdown">
335 <li class="${'active' if current == 'admin' else ''} dropdown">
336 <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Admin')}" href="">
336 <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Admin')}" href="">
337 <i class="icon-gear"></i>${_('Admin')}
337 <i class="icon-gear"></i>${_('Admin')}
338 </a>
338 </a>
339 ${admin_menu_simple(request.authuser.repositories_admin,
339 ${admin_menu_simple(request.authuser.repositories_admin,
340 request.authuser.repository_groups_admin,
340 request.authuser.repository_groups_admin,
341 request.authuser.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
341 request.authuser.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
342 </li>
342 </li>
343 % endif
343 % endif
344
344
345 <li class="${'active' if current == 'my_pullrequests' else ''}">
345 <li class="${'active' if current == 'my_pullrequests' else ''}">
346 <a class="menu_link" title="${_('My Pull Requests')}" href="${h.url('my_pullrequests')}">
346 <a class="menu_link" title="${_('My Pull Requests')}" href="${h.url('my_pullrequests')}">
347 <i class="icon-git-pull-request"></i>${_('My Pull Requests')}
347 <i class="icon-git-pull-request"></i>${_('My Pull Requests')}
348 %if c.my_pr_count != 0:
348 %if c.my_pr_count != 0:
349 <span class="badge">${c.my_pr_count}</span>
349 <span class="badge">${c.my_pr_count}</span>
350 %endif
350 %endif
351 </a>
351 </a>
352 </li>
352 </li>
353
353
354 ## USER MENU
354 ## USER MENU
355 <li class="dropdown">
355 <li class="dropdown">
356 <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" id="quick_login_link"
356 <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" id="quick_login_link"
357 aria-expanded="false" aria-controls="quick_login"
357 aria-expanded="false" aria-controls="quick_login"
358 %if request.authuser.username != 'default':
358 %if request.authuser.username != 'default':
359 href="${h.url('notifications')}"
359 href="${h.url('notifications')}"
360 %else:
360 %else:
361 href="#"
361 href="#"
362 %endif
362 %endif
363 >
363 >
364 ${h.gravatar_div(request.authuser.email, size=20, div_class="icon")}
364 ${h.gravatar_div(request.authuser.email, size=20, div_class="icon")}
365 %if request.authuser.username != 'default':
365 %if request.authuser.username != 'default':
366 <span class="menu_link_user">${request.authuser.username}</span>
366 <span class="menu_link_user">${request.authuser.username}</span>
367 %if c.unread_notifications != 0:
367 %if c.unread_notifications != 0:
368 <span class="badge">${c.unread_notifications}</span>
368 <span class="badge">${c.unread_notifications}</span>
369 %endif
369 %endif
370 %else:
370 %else:
371 <span>${_('Not Logged In')}</span>
371 <span>${_('Not Logged In')}</span>
372 %endif
372 %endif
373 <i class="caret"></i>
373 <i class="caret"></i>
374 </a>
374 </a>
375
375
376 <div class="dropdown-menu user-menu" role="menu">
376 <div class="dropdown-menu user-menu" role="menu">
377 <div id="quick_login" role="form" aria-describedby="quick_login_h" aria-hidden="true" class="container-fluid">
377 <div id="quick_login" role="form" aria-describedby="quick_login_h" aria-hidden="true" class="container-fluid">
378 %if request.authuser.username == 'default' or request.authuser.user_id is None:
378 %if request.authuser.username == 'default' or request.authuser.user_id is None:
379 ${h.form(h.url('login_home', came_from=request.path_qs), class_='form clearfix')}
379 ${h.form(h.url('login_home', came_from=request.path_qs), class_='form clearfix')}
380 <h4 id="quick_login_h">${_('Login to Your Account')}</h4>
380 <h4 id="quick_login_h">${_('Login to Your Account')}</h4>
381 <label>
381 <label>
382 ${_('Username')}:
382 ${_('Username')}:
383 ${h.text('username',class_='form-control')}
383 ${h.text('username',class_='form-control')}
384 </label>
384 </label>
385 <label>
385 <label>
386 ${_('Password')}:
386 ${_('Password')}:
387 ${h.password('password',class_='form-control')}
387 ${h.password('password',class_='form-control')}
388 </label>
388 </label>
389 <div class="password_forgotten">
389 <div class="password_forgotten">
390 ${h.link_to(_('Forgot password?'),h.url('reset_password'))}
390 ${h.link_to(_('Forgot password?'),h.url('reset_password'))}
391 </div>
391 </div>
392 <div class="register">
392 <div class="register">
393 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
393 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
394 ${h.link_to(_("Don't have an account?"),h.url('register'))}
394 ${h.link_to(_("Don't have an account?"),h.url('register'))}
395 %endif
395 %endif
396 </div>
396 </div>
397 <div class="submit">
397 <div class="submit">
398 ${h.submit('sign_in',_('Log In'),class_="btn btn-default btn-xs")}
398 ${h.submit('sign_in',_('Log In'),class_="btn btn-default btn-xs")}
399 </div>
399 </div>
400 ${h.end_form()}
400 ${h.end_form()}
401 %else:
401 %else:
402 <div class="pull-left">
402 <div class="pull-left">
403 ${h.gravatar_div(request.authuser.email, size=48, div_class="big_gravatar")}
403 ${h.gravatar_div(request.authuser.email, size=48, div_class="big_gravatar")}
404 <b class="full_name">${request.authuser.full_name_or_username}</b>
404 <b class="full_name">${request.authuser.full_name_or_username}</b>
405 <div class="email">${request.authuser.email}</div>
405 <div class="email">${request.authuser.email}</div>
406 </div>
406 </div>
407 <div id="quick_login_h" class="pull-right list-group text-right">
407 <div id="quick_login_h" class="pull-right list-group text-right">
408 <a class="list-group-item" href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a>
408 <a class="list-group-item" href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a>
409 ${h.link_to(_('My Account'),h.url('my_account'),class_='list-group-item')}
409 ${h.link_to(_('My Account'),h.url('my_account'),class_='list-group-item')}
410 %if not request.authuser.is_external_auth:
410 %if not request.authuser.is_external_auth:
411 ## Cannot log out if using external (container) authentication.
411 ## Cannot log out if using external (container) authentication.
412 ${h.link_to(_('Log Out'), h.url('logout_home'),class_='list-group-item')}
412 ${h.link_to(_('Log Out'), h.url('logout_home'),class_='list-group-item')}
413 %endif
413 %endif
414 </div>
414 </div>
415 %endif
415 %endif
416 </div>
416 </div>
417 </div>
417 </div>
418 </li>
418 </li>
419 </ul>
419 </ul>
420
420
421 <script type="text/javascript">
421 <script type="text/javascript">
422 $(document).ready(function(){
422 $(document).ready(function(){
423 var visual_show_public_icon = ${h.js(c.visual.show_public_icon)};
423 var visual_show_public_icon = ${h.js(c.visual.show_public_icon)};
424 var cache = {}
424 var cache = {}
425 /*format the look of items in the list*/
425 /*format the look of items in the list*/
426 var format = function(state){
426 var format = function(state){
427 if (!state.id){
427 if (!state.id){
428 return state.text; // optgroup
428 return state.text; // optgroup
429 }
429 }
430 var obj_dict = state.obj;
430 var obj_dict = state.obj;
431 var tmpl = '';
431 var tmpl = '';
432
432
433 if(obj_dict && state.type == 'repo'){
433 if(obj_dict && state.type == 'repo'){
434 tmpl += '<span class="repo-icons">';
434 tmpl += '<span class="repo-icons">';
435 if(obj_dict['repo_type'] === 'hg'){
435 if(obj_dict['repo_type'] === 'hg'){
436 tmpl += '<span class="label label-repo" title="${_('Mercurial repository')}">hg</span> ';
436 tmpl += '<span class="label label-repo" title="${_('Mercurial repository')}">hg</span> ';
437 }
437 }
438 else if(obj_dict['repo_type'] === 'git'){
438 else if(obj_dict['repo_type'] === 'git'){
439 tmpl += '<span class="label label-repo" title="${_('Git repository')}">git</span> ';
439 tmpl += '<span class="label label-repo" title="${_('Git repository')}">git</span> ';
440 }
440 }
441 if(obj_dict['private']){
441 if(obj_dict['private']){
442 tmpl += '<i class="icon-lock"></i>';
442 tmpl += '<i class="icon-lock"></i>';
443 }
443 }
444 else if(visual_show_public_icon){
444 else if(visual_show_public_icon){
445 tmpl += '<i class="icon-globe"></i>';
445 tmpl += '<i class="icon-globe"></i>';
446 }
446 }
447 tmpl += '</span>';
447 tmpl += '</span>';
448 }
448 }
449 if(obj_dict && state.type == 'group'){
449 if(obj_dict && state.type == 'group'){
450 tmpl += '<i class="icon-folder"></i>';
450 tmpl += '<i class="icon-folder"></i>';
451 }
451 }
452 tmpl += state.text;
452 tmpl += state.text;
453 return tmpl;
453 return tmpl;
454 }
454 }
455
455
456 $("#repo_switcher").select2({
456 $("#repo_switcher").select2({
457 placeholder: '<i class="icon-database"></i>' + ${h.jshtml(_('Repositories'))} + ' <span class="caret"></span>',
457 placeholder: '<i class="icon-database"></i>' + ${h.jshtml(_('Repositories'))} + ' <span class="caret"></span>',
458 dropdownAutoWidth: true,
458 dropdownAutoWidth: true,
459 sortResults: prefixFirstSort,
459 sortResults: prefixFirstSort,
460 formatResult: format,
460 formatResult: format,
461 formatSelection: format,
461 formatSelection: format,
462 formatNoMatches: function(term){
462 formatNoMatches: function(term){
463 return ${h.jshtml(_('No matches found'))};
463 return ${h.jshtml(_('No matches found'))};
464 },
464 },
465 containerCssClass: "repo-switcher",
465 containerCssClass: "repo-switcher",
466 dropdownCssClass: "repo-switcher-dropdown",
466 dropdownCssClass: "repo-switcher-dropdown",
467 escapeMarkup: function(m){
467 escapeMarkup: function(m){
468 // don't escape our custom placeholder
468 // don't escape our custom placeholder
469 if(m.substr(0,29) == '<i class="icon-database"></i>'){
469 if(m.substr(0,29) == '<i class="icon-database"></i>'){
470 return m;
470 return m;
471 }
471 }
472
472
473 return Select2.util.escapeMarkup(m);
473 return Select2.util.escapeMarkup(m);
474 },
474 },
475 query: function(query){
475 query: function(query){
476 var key = 'cache';
476 var key = 'cache';
477 var cached = cache[key] ;
477 var cached = cache[key] ;
478 if(cached) {
478 if(cached) {
479 var data = {results: []};
479 var data = {results: []};
480 //filter results
480 //filter results
481 $.each(cached.results, function(){
481 $.each(cached.results, function(){
482 var section = this.text;
482 var section = this.text;
483 var children = [];
483 var children = [];
484 $.each(this.children, function(){
484 $.each(this.children, function(){
485 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
485 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
486 children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj});
486 children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj});
487 }
487 }
488 });
488 });
489 if(children.length !== 0){
489 if(children.length !== 0){
490 data.results.push({'text': section, 'children': children});
490 data.results.push({'text': section, 'children': children});
491 }
491 }
492
492
493 });
493 });
494 query.callback(data);
494 query.callback(data);
495 }else{
495 }else{
496 $.ajax({
496 $.ajax({
497 url: ${h.js(h.url('repo_switcher_data'))},
497 url: ${h.js(h.url('repo_switcher_data'))},
498 data: {},
498 data: {},
499 dataType: 'json',
499 dataType: 'json',
500 type: 'GET',
500 type: 'GET',
501 success: function(data) {
501 success: function(data) {
502 cache[key] = data;
502 cache[key] = data;
503 query.callback({results: data.results});
503 query.callback({results: data.results});
504 }
504 }
505 });
505 });
506 }
506 }
507 }
507 }
508 });
508 });
509
509
510 $("#repo_switcher").on('select2-selecting', function(e){
510 $("#repo_switcher").on('select2-selecting', function(e){
511 e.preventDefault();
511 e.preventDefault();
512 window.location = pyroutes.url('summary_home', {'repo_name': e.val});
512 window.location = pyroutes.url('summary_home', {'repo_name': e.val});
513 });
513 });
514
514
515 $(document).on('shown.bs.dropdown', function(event) {
515 $(document).on('shown.bs.dropdown', function(event) {
516 var dropdown = $(event.target);
516 var dropdown = $(event.target);
517
517
518 dropdown.attr('aria-expanded', true);
518 dropdown.attr('aria-expanded', true);
519 dropdown.find('.dropdown-menu').attr('aria-hidden', false);
519 dropdown.find('.dropdown-menu').attr('aria-hidden', false);
520 });
520 });
521
521
522 $(document).on('hidden.bs.dropdown', function(event) {
522 $(document).on('hidden.bs.dropdown', function(event) {
523 var dropdown = $(event.target);
523 var dropdown = $(event.target);
524
524
525 dropdown.attr('aria-expanded', false);
525 dropdown.attr('aria-expanded', false);
526 dropdown.find('.dropdown-menu').attr('aria-hidden', true);
526 dropdown.find('.dropdown-menu').attr('aria-hidden', true);
527 });
527 });
528 });
528 });
529 </script>
529 </script>
530 </%def>
530 </%def>
531
531
532 <%def name="parent_child_navigation()">
532 <%def name="parent_child_navigation()">
533 <div class="pull-left">
533 <div class="pull-left">
534 <div class="parent-child-link"
534 <div class="parent-child-link"
535 data-ajax-url="${h.url('changeset_parents',repo_name=c.repo_name, revision=c.changeset.raw_id)}"
535 data-ajax-url="${h.url('changeset_parents',repo_name=c.repo_name, revision=c.changeset.raw_id)}"
536 data-linktype="parent"
536 data-linktype="parent"
537 data-reponame="${c.repo_name}">
537 data-reponame="${c.repo_name}">
538 <i class="icon-left-open"></i><a href="#">${_('Parent rev.')}</a>
538 <i class="icon-left-open"></i><a href="#">${_('Parent rev.')}</a>
539 </div>
539 </div>
540 </div>
540 </div>
541
541
542 <div class="pull-right">
542 <div class="pull-right">
543 <div class="parent-child-link"
543 <div class="parent-child-link"
544 data-ajax-url="${h.url('changeset_children',repo_name=c.repo_name, revision=c.changeset.raw_id)}"
544 data-ajax-url="${h.url('changeset_children',repo_name=c.repo_name, revision=c.changeset.raw_id)}"
545 data-linktype="child"
545 data-linktype="child"
546 data-reponame="${c.repo_name}">
546 data-reponame="${c.repo_name}">
547 <a href="#">${_('Child rev.')}</a><i class="icon-right-open"></i>
547 <a href="#">${_('Child rev.')}</a><i class="icon-right-open"></i>
548 </div>
548 </div>
549 </div>
549 </div>
550
550
551 <script type="text/javascript">
551 <script type="text/javascript">
552 $(document).ready(function(){
552 $(document).ready(function(){
553 activate_parent_child_links();
553 activate_parent_child_links();
554 });
554 });
555 </script>
555 </script>
556 </%def>
556 </%def>
@@ -1,669 +1,669 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 Test suite for vcs push/pull operations.
15 Test suite for vcs push/pull operations.
16
16
17 The tests need Git > 1.8.1.
17 The tests need Git > 1.8.1.
18
18
19 This file was forked by the Kallithea project in July 2014.
19 This file was forked by the Kallithea project in July 2014.
20 Original author and date, and relevant copyright and licensing information is below:
20 Original author and date, and relevant copyright and licensing information is below:
21 :created_on: Dec 30, 2010
21 :created_on: Dec 30, 2010
22 :author: marcink
22 :author: marcink
23 :copyright: (c) 2013 RhodeCode GmbH, and others.
23 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :license: GPLv3, see LICENSE.md for more details.
24 :license: GPLv3, see LICENSE.md for more details.
25
25
26 """
26 """
27
27
28 import os
28 import os
29 import re
29 import re
30 import tempfile
30 import tempfile
31 import time
31 import time
32 import pytest
32 import pytest
33
33
34 from tempfile import _RandomNameSequence
34 from tempfile import _RandomNameSequence
35 from subprocess import Popen, PIPE
35 from subprocess import Popen, PIPE
36
36
37 from kallithea.tests.base import *
37 from kallithea.tests.base import *
38 from kallithea.tests.fixture import Fixture
38 from kallithea.tests.fixture import Fixture
39 from kallithea.model.db import User, Repository, UserIpMap, CacheInvalidation, Ui
39 from kallithea.model.db import User, Repository, UserIpMap, CacheInvalidation, Ui
40 from kallithea.model.meta import Session
40 from kallithea.model.meta import Session
41 from kallithea.model.repo import RepoModel
41 from kallithea.model.repo import RepoModel
42 from kallithea.model.user import UserModel
42 from kallithea.model.user import UserModel
43
43
44 DEBUG = True
44 DEBUG = True
45 HOST = '127.0.0.1:4999' # test host
45 HOST = '127.0.0.1:4999' # test host
46
46
47 fixture = Fixture()
47 fixture = Fixture()
48
48
49
49
50 class Command(object):
50 class Command(object):
51
51
52 def __init__(self, cwd):
52 def __init__(self, cwd):
53 self.cwd = cwd
53 self.cwd = cwd
54
54
55 def execute(self, cmd, *args, **environ):
55 def execute(self, cmd, *args, **environ):
56 """
56 """
57 Runs command on the system with given ``args``.
57 Runs command on the system with given ``args``.
58 """
58 """
59
59
60 command = cmd + ' ' + ' '.join(args)
60 command = cmd + ' ' + ' '.join(args)
61 ignoreReturnCode = environ.pop('ignoreReturnCode', False)
61 ignoreReturnCode = environ.pop('ignoreReturnCode', False)
62 if DEBUG:
62 if DEBUG:
63 print '*** CMD %s ***' % command
63 print '*** CMD %s ***' % command
64 testenv = dict(os.environ)
64 testenv = dict(os.environ)
65 testenv['LANG'] = 'en_US.UTF-8'
65 testenv['LANG'] = 'en_US.UTF-8'
66 testenv['LANGUAGE'] = 'en_US:en'
66 testenv['LANGUAGE'] = 'en_US:en'
67 testenv['HGPLAIN'] = ''
67 testenv['HGPLAIN'] = ''
68 testenv['HGRCPATH'] = ''
68 testenv['HGRCPATH'] = ''
69 testenv.update(environ)
69 testenv.update(environ)
70 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd, env=testenv)
70 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd, env=testenv)
71 stdout, stderr = p.communicate()
71 stdout, stderr = p.communicate()
72 if DEBUG:
72 if DEBUG:
73 if stdout:
73 if stdout:
74 print 'stdout:', repr(stdout)
74 print 'stdout:', repr(stdout)
75 if stderr:
75 if stderr:
76 print 'stderr:', repr(stderr)
76 print 'stderr:', repr(stderr)
77 if not ignoreReturnCode:
77 if not ignoreReturnCode:
78 assert p.returncode == 0
78 assert p.returncode == 0
79 return stdout, stderr
79 return stdout, stderr
80
80
81
81
82 def _get_tmp_dir(prefix='vcs_operations-', suffix=''):
82 def _get_tmp_dir(prefix='vcs_operations-', suffix=''):
83 return tempfile.mkdtemp(dir=TESTS_TMP_PATH, prefix=prefix, suffix=suffix)
83 return tempfile.mkdtemp(dir=TESTS_TMP_PATH, prefix=prefix, suffix=suffix)
84
84
85
85
86 def _add_files(vcs, dest_dir, files_no=3):
86 def _add_files(vcs, dest_dir, files_no=3):
87 """
87 """
88 Generate some files, add it to dest_dir repo and push back
88 Generate some files, add it to dest_dir repo and push back
89 vcs is git or hg and defines what VCS we want to make those files for
89 vcs is git or hg and defines what VCS we want to make those files for
90
90
91 :param vcs:
91 :param vcs:
92 :param dest_dir:
92 :param dest_dir:
93 """
93 """
94 added_file = '%ssetup.py' % _RandomNameSequence().next()
94 added_file = '%ssetup.py' % _RandomNameSequence().next()
95 open(os.path.join(dest_dir, added_file), 'a').close()
95 open(os.path.join(dest_dir, added_file), 'a').close()
96 Command(dest_dir).execute('%s add %s' % (vcs, added_file))
96 Command(dest_dir).execute('%s add %s' % (vcs, added_file))
97
97
98 email = 'me@example.com'
98 email = 'me@example.com'
99 if os.name == 'nt':
99 if os.name == 'nt':
100 author_str = 'User <%s>' % email
100 author_str = 'User <%s>' % email
101 else:
101 else:
102 author_str = 'User ǝɯɐᴎ <%s>' % email
102 author_str = 'User ǝɯɐᴎ <%s>' % email
103 for i in xrange(files_no):
103 for i in xrange(files_no):
104 cmd = """echo "added_line%s" >> %s""" % (i, added_file)
104 cmd = """echo "added_line%s" >> %s""" % (i, added_file)
105 Command(dest_dir).execute(cmd)
105 Command(dest_dir).execute(cmd)
106 if vcs == 'hg':
106 if vcs == 'hg':
107 cmd = """hg commit -m "committed new %s" -u "%s" "%s" """ % (
107 cmd = """hg commit -m "committed new %s" -u "%s" "%s" """ % (
108 i, author_str, added_file
108 i, author_str, added_file
109 )
109 )
110 elif vcs == 'git':
110 elif vcs == 'git':
111 cmd = """git commit -m "committed new %s" --author "%s" "%s" """ % (
111 cmd = """git commit -m "committed new %s" --author "%s" "%s" """ % (
112 i, author_str, added_file
112 i, author_str, added_file
113 )
113 )
114 # git commit needs EMAIL on some machines
114 # git commit needs EMAIL on some machines
115 Command(dest_dir).execute(cmd, EMAIL=email)
115 Command(dest_dir).execute(cmd, EMAIL=email)
116
116
117 def _add_files_and_push(webserver, vcs, dest_dir, ignoreReturnCode=False, files_no=3,
117 def _add_files_and_push(webserver, vcs, dest_dir, ignoreReturnCode=False, files_no=3,
118 clone_url=None, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
118 clone_url=None, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
119 _add_files(vcs, dest_dir, files_no=files_no)
119 _add_files(vcs, dest_dir, files_no=files_no)
120 # PUSH it back
120 # PUSH it back
121 _REPO = None
121 _REPO = None
122 if vcs == 'hg':
122 if vcs == 'hg':
123 _REPO = HG_REPO
123 _REPO = HG_REPO
124 elif vcs == 'git':
124 elif vcs == 'git':
125 _REPO = GIT_REPO
125 _REPO = GIT_REPO
126
126
127 if clone_url is None:
127 if clone_url is None:
128 clone_url = webserver.repo_url(_REPO, username=username, password=password)
128 clone_url = webserver.repo_url(_REPO, username=username, password=password)
129
129
130 stdout = stderr = None
130 stdout = stderr = None
131 if vcs == 'hg':
131 if vcs == 'hg':
132 stdout, stderr = Command(dest_dir).execute('hg push --verbose', clone_url, ignoreReturnCode=ignoreReturnCode)
132 stdout, stderr = Command(dest_dir).execute('hg push --verbose', clone_url, ignoreReturnCode=ignoreReturnCode)
133 elif vcs == 'git':
133 elif vcs == 'git':
134 stdout, stderr = Command(dest_dir).execute('git push --verbose', clone_url, "master", ignoreReturnCode=ignoreReturnCode)
134 stdout, stderr = Command(dest_dir).execute('git push --verbose', clone_url, "master", ignoreReturnCode=ignoreReturnCode)
135
135
136 return stdout, stderr
136 return stdout, stderr
137
137
138
138
139 def _check_outgoing(vcs, cwd, clone_url=''):
139 def _check_outgoing(vcs, cwd, clone_url=''):
140 if vcs == 'hg':
140 if vcs == 'hg':
141 # hg removes the password from default URLs, so we have to provide it here via the clone_url
141 # hg removes the password from default URLs, so we have to provide it here via the clone_url
142 return Command(cwd).execute('hg -q outgoing', clone_url, ignoreReturnCode=True)
142 return Command(cwd).execute('hg -q outgoing', clone_url, ignoreReturnCode=True)
143 elif vcs == 'git':
143 elif vcs == 'git':
144 Command(cwd).execute('git remote update')
144 Command(cwd).execute('git remote update')
145 return Command(cwd).execute('git log origin/master..master')
145 return Command(cwd).execute('git log origin/master..master')
146
146
147
147
148 def set_anonymous_access(enable=True):
148 def set_anonymous_access(enable=True):
149 user = User.get_default_user()
149 user = User.get_default_user()
150 user.active = enable
150 user.active = enable
151 Session().commit()
151 Session().commit()
152 print '\tanonymous access is now:', enable
152 print '\tanonymous access is now:', enable
153 if enable != User.get_default_user().active:
153 if enable != User.get_default_user().active:
154 raise Exception('Cannot set anonymous access')
154 raise Exception('Cannot set anonymous access')
155
155
156
156
157 #==============================================================================
157 #==============================================================================
158 # TESTS
158 # TESTS
159 #==============================================================================
159 #==============================================================================
160
160
161
161
162 def _check_proper_git_push(stdout, stderr):
162 def _check_proper_git_push(stdout, stderr):
163 # WTF Git stderr is output ?!
163 # WTF Git stderr is output ?!
164 assert 'fatal' not in stderr
164 assert 'fatal' not in stderr
165 assert 'rejected' not in stderr
165 assert 'rejected' not in stderr
166 assert 'Pushing to' in stderr
166 assert 'Pushing to' in stderr
167 assert 'master -> master' in stderr
167 assert 'master -> master' in stderr
168
168
169
169
170 @pytest.mark.usefixtures("test_context_fixture")
170 @pytest.mark.usefixtures("test_context_fixture")
171 class TestVCSOperations(TestController):
171 class TestVCSOperations(TestController):
172
172
173 @classmethod
173 @classmethod
174 def setup_class(cls):
174 def setup_class(cls):
175 # DISABLE ANONYMOUS ACCESS
175 # DISABLE ANONYMOUS ACCESS
176 set_anonymous_access(False)
176 set_anonymous_access(False)
177
177
178 def setup_method(self, method):
178 def setup_method(self, method):
179 r = Repository.get_by_repo_name(GIT_REPO)
179 r = Repository.get_by_repo_name(GIT_REPO)
180 Repository.unlock(r)
180 Repository.unlock(r)
181 r.enable_locking = False
181 r.enable_locking = False
182 Session().commit()
182 Session().commit()
183
183
184 r = Repository.get_by_repo_name(HG_REPO)
184 r = Repository.get_by_repo_name(HG_REPO)
185 Repository.unlock(r)
185 Repository.unlock(r)
186 r.enable_locking = False
186 r.enable_locking = False
187 Session().commit()
187 Session().commit()
188
188
189 @pytest.fixture()
189 @pytest.fixture()
190 def testhook_cleanup(self):
190 def testhook_cleanup(self):
191 yield
191 yield
192 # remove hook
192 # remove hook
193 for hook in ['prechangegroup', 'pretxnchangegroup', 'preoutgoing', 'changegroup', 'outgoing', 'incoming']:
193 for hook in ['prechangegroup', 'pretxnchangegroup', 'preoutgoing', 'changegroup', 'outgoing', 'incoming']:
194 entry = Ui.get_by_key('hooks', '%s.testhook' % hook)
194 entry = Ui.get_by_key('hooks', '%s.testhook' % hook)
195 if entry:
195 if entry:
196 Session().delete(entry)
196 Session().delete(entry)
197 Session().commit()
197 Session().commit()
198
198
199 @pytest.fixture(scope="module")
199 @pytest.fixture(scope="module")
200 def testfork(self):
200 def testfork(self):
201 # create fork so the repo stays untouched
201 # create fork so the repo stays untouched
202 git_fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
202 git_fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
203 fixture.create_fork(GIT_REPO, git_fork_name)
203 fixture.create_fork(GIT_REPO, git_fork_name)
204 hg_fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
204 hg_fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
205 fixture.create_fork(HG_REPO, hg_fork_name)
205 fixture.create_fork(HG_REPO, hg_fork_name)
206 return {'git': git_fork_name, 'hg': hg_fork_name}
206 return {'git': git_fork_name, 'hg': hg_fork_name}
207
207
208 def test_clone_hg_repo_by_admin(self, webserver):
208 def test_clone_hg_repo_by_admin(self, webserver):
209 clone_url = webserver.repo_url(HG_REPO)
209 clone_url = webserver.repo_url(HG_REPO)
210 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
210 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
211
211
212 assert 'requesting all changes' in stdout
212 assert 'requesting all changes' in stdout
213 assert 'adding changesets' in stdout
213 assert 'adding changesets' in stdout
214 assert 'adding manifests' in stdout
214 assert 'adding manifests' in stdout
215 assert 'adding file changes' in stdout
215 assert 'adding file changes' in stdout
216
216
217 assert stderr == ''
217 assert stderr == ''
218
218
219 def test_clone_git_repo_by_admin(self, webserver):
219 def test_clone_git_repo_by_admin(self, webserver):
220 clone_url = webserver.repo_url(GIT_REPO)
220 clone_url = webserver.repo_url(GIT_REPO)
221 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
221 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
222
222
223 assert 'Cloning into' in stdout + stderr
223 assert 'Cloning into' in stdout + stderr
224 assert stderr == '' or stdout == ''
224 assert stderr == '' or stdout == ''
225
225
226 def test_clone_wrong_credentials_hg(self, webserver):
226 def test_clone_wrong_credentials_hg(self, webserver):
227 clone_url = webserver.repo_url(HG_REPO, password='bad!')
227 clone_url = webserver.repo_url(HG_REPO, password='bad!')
228 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
228 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
229 assert 'abort: authorization failed' in stderr
229 assert 'abort: authorization failed' in stderr
230
230
231 def test_clone_wrong_credentials_git(self, webserver):
231 def test_clone_wrong_credentials_git(self, webserver):
232 clone_url = webserver.repo_url(GIT_REPO, password='bad!')
232 clone_url = webserver.repo_url(GIT_REPO, password='bad!')
233 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
233 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
234 assert 'fatal: Authentication failed' in stderr
234 assert 'fatal: Authentication failed' in stderr
235
235
236 def test_clone_git_dir_as_hg(self, webserver):
236 def test_clone_git_dir_as_hg(self, webserver):
237 clone_url = webserver.repo_url(GIT_REPO)
237 clone_url = webserver.repo_url(GIT_REPO)
238 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
238 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
239 assert 'HTTP Error 404: Not Found' in stderr
239 assert 'HTTP Error 404: Not Found' in stderr
240
240
241 def test_clone_hg_repo_as_git(self, webserver):
241 def test_clone_hg_repo_as_git(self, webserver):
242 clone_url = webserver.repo_url(HG_REPO)
242 clone_url = webserver.repo_url(HG_REPO)
243 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
243 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
244 assert 'not found' in stderr
244 assert 'not found' in stderr
245
245
246 def test_clone_non_existing_path_hg(self, webserver):
246 def test_clone_non_existing_path_hg(self, webserver):
247 clone_url = webserver.repo_url('trololo')
247 clone_url = webserver.repo_url('trololo')
248 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
248 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
249 assert 'HTTP Error 404: Not Found' in stderr
249 assert 'HTTP Error 404: Not Found' in stderr
250
250
251 def test_clone_non_existing_path_git(self, webserver):
251 def test_clone_non_existing_path_git(self, webserver):
252 clone_url = webserver.repo_url('trololo')
252 clone_url = webserver.repo_url('trololo')
253 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
253 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
254 assert 'not found' in stderr
254 assert 'not found' in stderr
255
255
256 def test_push_new_file_hg(self, webserver):
256 def test_push_new_file_hg(self, webserver):
257 dest_dir = _get_tmp_dir()
257 dest_dir = _get_tmp_dir()
258 clone_url = webserver.repo_url(HG_REPO)
258 clone_url = webserver.repo_url(HG_REPO)
259 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
259 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
260
260
261 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
261 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
262 fixture.create_fork(HG_REPO, fork_name)
262 fixture.create_fork(HG_REPO, fork_name)
263 clone_url = webserver.repo_url(fork_name)
263 clone_url = webserver.repo_url(fork_name)
264 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, clone_url=clone_url)
264 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, clone_url=clone_url)
265
265
266 assert 'pushing to' in stdout
266 assert 'pushing to' in stdout
267 assert 'Repository size' in stdout
267 assert 'Repository size' in stdout
268 assert 'Last revision is now' in stdout
268 assert 'Last revision is now' in stdout
269
269
270 def test_push_new_file_git(self, webserver):
270 def test_push_new_file_git(self, webserver):
271 dest_dir = _get_tmp_dir()
271 dest_dir = _get_tmp_dir()
272 clone_url = webserver.repo_url(GIT_REPO)
272 clone_url = webserver.repo_url(GIT_REPO)
273 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
273 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
274
274
275 # commit some stuff into this repo
275 # commit some stuff into this repo
276 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
276 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
277 fixture.create_fork(GIT_REPO, fork_name)
277 fixture.create_fork(GIT_REPO, fork_name)
278 clone_url = webserver.repo_url(fork_name)
278 clone_url = webserver.repo_url(fork_name)
279 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, clone_url=clone_url)
279 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, clone_url=clone_url)
280 print [(x.repo_full_path,x.repo_path) for x in Repository.query()] # TODO: what is this for
280 print [(x.repo_full_path,x.repo_path) for x in Repository.query()] # TODO: what is this for
281 _check_proper_git_push(stdout, stderr)
281 _check_proper_git_push(stdout, stderr)
282
282
283 def test_push_invalidates_cache_hg(self, webserver):
283 def test_push_invalidates_cache_hg(self, webserver):
284 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
284 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
285 == HG_REPO).scalar()
285 == HG_REPO).scalar()
286 if not key:
286 if not key:
287 key = CacheInvalidation(HG_REPO, HG_REPO)
287 key = CacheInvalidation(HG_REPO, HG_REPO)
288 Session().add(key)
288 Session().add(key)
289
289
290 key.cache_active = True
290 key.cache_active = True
291 Session().commit()
291 Session().commit()
292
292
293 dest_dir = _get_tmp_dir()
293 dest_dir = _get_tmp_dir()
294 clone_url = webserver.repo_url(HG_REPO)
294 clone_url = webserver.repo_url(HG_REPO)
295 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
295 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
296
296
297 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
297 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
298 fixture.create_fork(HG_REPO, fork_name)
298 fixture.create_fork(HG_REPO, fork_name)
299 clone_url = webserver.repo_url(fork_name)
299 clone_url = webserver.repo_url(fork_name)
300 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, files_no=1, clone_url=clone_url)
300 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, files_no=1, clone_url=clone_url)
301
301
302 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
302 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
303 == fork_name).all()
303 == fork_name).all()
304 assert key == []
304 assert key == []
305
305
306 def test_push_invalidates_cache_git(self, webserver):
306 def test_push_invalidates_cache_git(self, webserver):
307 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
307 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
308 == GIT_REPO).scalar()
308 == GIT_REPO).scalar()
309 if not key:
309 if not key:
310 key = CacheInvalidation(GIT_REPO, GIT_REPO)
310 key = CacheInvalidation(GIT_REPO, GIT_REPO)
311 Session().add(key)
311 Session().add(key)
312
312
313 key.cache_active = True
313 key.cache_active = True
314 Session().commit()
314 Session().commit()
315
315
316 dest_dir = _get_tmp_dir()
316 dest_dir = _get_tmp_dir()
317 clone_url = webserver.repo_url(GIT_REPO)
317 clone_url = webserver.repo_url(GIT_REPO)
318 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
318 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
319
319
320 # commit some stuff into this repo
320 # commit some stuff into this repo
321 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
321 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
322 fixture.create_fork(GIT_REPO, fork_name)
322 fixture.create_fork(GIT_REPO, fork_name)
323 clone_url = webserver.repo_url(fork_name)
323 clone_url = webserver.repo_url(fork_name)
324 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, files_no=1, clone_url=clone_url)
324 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, files_no=1, clone_url=clone_url)
325 _check_proper_git_push(stdout, stderr)
325 _check_proper_git_push(stdout, stderr)
326
326
327 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
327 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
328 == fork_name).all()
328 == fork_name).all()
329 assert key == []
329 assert key == []
330
330
331 def test_push_wrong_credentials_hg(self, webserver):
331 def test_push_wrong_credentials_hg(self, webserver):
332 dest_dir = _get_tmp_dir()
332 dest_dir = _get_tmp_dir()
333 clone_url = webserver.repo_url(HG_REPO)
333 clone_url = webserver.repo_url(HG_REPO)
334 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
334 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
335
335
336 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, username='bad',
336 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, username='bad',
337 password='name', ignoreReturnCode=True)
337 password='name', ignoreReturnCode=True)
338
338
339 assert 'abort: authorization failed' in stderr
339 assert 'abort: authorization failed' in stderr
340
340
341 def test_push_wrong_credentials_git(self, webserver):
341 def test_push_wrong_credentials_git(self, webserver):
342 dest_dir = _get_tmp_dir()
342 dest_dir = _get_tmp_dir()
343 clone_url = webserver.repo_url(GIT_REPO)
343 clone_url = webserver.repo_url(GIT_REPO)
344 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
344 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
345
345
346 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, username='bad',
346 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, username='bad',
347 password='name', ignoreReturnCode=True)
347 password='name', ignoreReturnCode=True)
348
348
349 assert 'fatal: Authentication failed' in stderr
349 assert 'fatal: Authentication failed' in stderr
350
350
351 def test_push_with_readonly_credentials_hg(self, webserver):
351 def test_push_with_readonly_credentials_hg(self, webserver):
352 dest_dir = _get_tmp_dir()
352 dest_dir = _get_tmp_dir()
353 clone_url = webserver.repo_url(HG_REPO, username=TEST_USER_REGULAR_LOGIN, password=TEST_USER_REGULAR_PASS)
353 clone_url = webserver.repo_url(HG_REPO, username=TEST_USER_REGULAR_LOGIN, password=TEST_USER_REGULAR_PASS)
354 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
354 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
355
355
356 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, username=TEST_USER_REGULAR_LOGIN,
356 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, username=TEST_USER_REGULAR_LOGIN,
357 password=TEST_USER_REGULAR_PASS, ignoreReturnCode=True)
357 password=TEST_USER_REGULAR_PASS, ignoreReturnCode=True)
358
358
359 assert 'abort: HTTP Error 403: Forbidden' in stderr
359 assert 'abort: HTTP Error 403: Forbidden' in stderr
360
360
361 def test_push_with_readonly_credentials_git(self, webserver):
361 def test_push_with_readonly_credentials_git(self, webserver):
362 dest_dir = _get_tmp_dir()
362 dest_dir = _get_tmp_dir()
363 clone_url = webserver.repo_url(GIT_REPO, username=TEST_USER_REGULAR_LOGIN, password=TEST_USER_REGULAR_PASS)
363 clone_url = webserver.repo_url(GIT_REPO, username=TEST_USER_REGULAR_LOGIN, password=TEST_USER_REGULAR_PASS)
364 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
364 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
365
365
366 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, username=TEST_USER_REGULAR_LOGIN,
366 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, username=TEST_USER_REGULAR_LOGIN,
367 password=TEST_USER_REGULAR_PASS, ignoreReturnCode=True)
367 password=TEST_USER_REGULAR_PASS, ignoreReturnCode=True)
368
368
369 assert 'The requested URL returned error: 403' in stderr
369 assert 'The requested URL returned error: 403' in stderr
370
370
371 def test_push_back_to_wrong_url_hg(self, webserver):
371 def test_push_back_to_wrong_url_hg(self, webserver):
372 dest_dir = _get_tmp_dir()
372 dest_dir = _get_tmp_dir()
373 clone_url = webserver.repo_url(HG_REPO)
373 clone_url = webserver.repo_url(HG_REPO)
374 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
374 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
375
375
376 stdout, stderr = _add_files_and_push(
376 stdout, stderr = _add_files_and_push(
377 webserver, 'hg', dest_dir, clone_url='http://%s:%s/tmp' % (
377 webserver, 'hg', dest_dir, clone_url='http://%s:%s/tmp' % (
378 webserver.server_address[0], webserver.server_address[1]),
378 webserver.server_address[0], webserver.server_address[1]),
379 ignoreReturnCode=True)
379 ignoreReturnCode=True)
380
380
381 assert 'HTTP Error 404: Not Found' in stderr
381 assert 'HTTP Error 404: Not Found' in stderr
382
382
383 def test_push_back_to_wrong_url_git(self, webserver):
383 def test_push_back_to_wrong_url_git(self, webserver):
384 dest_dir = _get_tmp_dir()
384 dest_dir = _get_tmp_dir()
385 clone_url = webserver.repo_url(GIT_REPO)
385 clone_url = webserver.repo_url(GIT_REPO)
386 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
386 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
387
387
388 stdout, stderr = _add_files_and_push(
388 stdout, stderr = _add_files_and_push(
389 webserver, 'git', dest_dir, clone_url='http://%s:%s/tmp' % (
389 webserver, 'git', dest_dir, clone_url='http://%s:%s/tmp' % (
390 webserver.server_address[0], webserver.server_address[1]),
390 webserver.server_address[0], webserver.server_address[1]),
391 ignoreReturnCode=True)
391 ignoreReturnCode=True)
392
392
393 assert 'not found' in stderr
393 assert 'not found' in stderr
394
394
395 def test_clone_and_create_lock_hg(self, webserver):
395 def test_clone_and_create_lock_hg(self, webserver):
396 # enable locking
396 # enable locking
397 r = Repository.get_by_repo_name(HG_REPO)
397 r = Repository.get_by_repo_name(HG_REPO)
398 r.enable_locking = True
398 r.enable_locking = True
399 Session().commit()
399 Session().commit()
400 # clone
400 # clone
401 clone_url = webserver.repo_url(HG_REPO)
401 clone_url = webserver.repo_url(HG_REPO)
402 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
402 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
403
403
404 # check if lock was made
404 # check if lock was made
405 r = Repository.get_by_repo_name(HG_REPO)
405 r = Repository.get_by_repo_name(HG_REPO)
406 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
406 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
407
407
408 def test_clone_and_create_lock_git(self, webserver):
408 def test_clone_and_create_lock_git(self, webserver):
409 # enable locking
409 # enable locking
410 r = Repository.get_by_repo_name(GIT_REPO)
410 r = Repository.get_by_repo_name(GIT_REPO)
411 r.enable_locking = True
411 r.enable_locking = True
412 Session().commit()
412 Session().commit()
413 # clone
413 # clone
414 clone_url = webserver.repo_url(GIT_REPO)
414 clone_url = webserver.repo_url(GIT_REPO)
415 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
415 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
416
416
417 # check if lock was made
417 # check if lock was made
418 r = Repository.get_by_repo_name(GIT_REPO)
418 r = Repository.get_by_repo_name(GIT_REPO)
419 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
419 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
420
420
421 def test_clone_after_repo_was_locked_hg(self, webserver):
421 def test_clone_after_repo_was_locked_hg(self, webserver):
422 # lock repo
422 # lock repo
423 r = Repository.get_by_repo_name(HG_REPO)
423 r = Repository.get_by_repo_name(HG_REPO)
424 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
424 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
425 # pull fails since repo is locked
425 # pull fails since repo is locked
426 clone_url = webserver.repo_url(HG_REPO)
426 clone_url = webserver.repo_url(HG_REPO)
427 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
427 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
428 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
428 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
429 % (HG_REPO, TEST_USER_ADMIN_LOGIN))
429 % (HG_REPO, TEST_USER_ADMIN_LOGIN))
430 assert msg in stderr
430 assert msg in stderr
431
431
432 def test_clone_after_repo_was_locked_git(self, webserver):
432 def test_clone_after_repo_was_locked_git(self, webserver):
433 # lock repo
433 # lock repo
434 r = Repository.get_by_repo_name(GIT_REPO)
434 r = Repository.get_by_repo_name(GIT_REPO)
435 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
435 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
436 # pull fails since repo is locked
436 # pull fails since repo is locked
437 clone_url = webserver.repo_url(GIT_REPO)
437 clone_url = webserver.repo_url(GIT_REPO)
438 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
438 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
439 msg = ("""The requested URL returned error: 423""")
439 msg = ("""The requested URL returned error: 423""")
440 assert msg in stderr
440 assert msg in stderr
441
441
442 def test_push_on_locked_repo_by_other_user_hg(self, webserver):
442 def test_push_on_locked_repo_by_other_user_hg(self, webserver):
443 # clone some temp
443 # clone some temp
444 dest_dir = _get_tmp_dir()
444 dest_dir = _get_tmp_dir()
445 clone_url = webserver.repo_url(HG_REPO)
445 clone_url = webserver.repo_url(HG_REPO)
446 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
446 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
447
447
448 # lock repo
448 # lock repo
449 r = Repository.get_by_repo_name(HG_REPO)
449 r = Repository.get_by_repo_name(HG_REPO)
450 # let this user actually push !
450 # let this user actually push !
451 RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
451 RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
452 perm='repository.write')
452 perm='repository.write')
453 Session().commit()
453 Session().commit()
454 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
454 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
455
455
456 # push fails repo is locked by other user !
456 # push fails repo is locked by other user !
457 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir,
457 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir,
458 username=TEST_USER_REGULAR_LOGIN,
458 username=TEST_USER_REGULAR_LOGIN,
459 password=TEST_USER_REGULAR_PASS,
459 password=TEST_USER_REGULAR_PASS,
460 ignoreReturnCode=True)
460 ignoreReturnCode=True)
461 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
461 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
462 % (HG_REPO, TEST_USER_ADMIN_LOGIN))
462 % (HG_REPO, TEST_USER_ADMIN_LOGIN))
463 assert msg in stderr
463 assert msg in stderr
464
464
465 def test_push_on_locked_repo_by_other_user_git(self, webserver):
465 def test_push_on_locked_repo_by_other_user_git(self, webserver):
466 # Note: Git hooks must be executable on unix. This test will thus fail
466 # Note: Git hooks must be executable on unix. This test will thus fail
467 # for example on Linux if /tmp is mounted noexec.
467 # for example on Linux if /tmp is mounted noexec.
468
468
469 # clone some temp
469 # clone some temp
470 dest_dir = _get_tmp_dir()
470 dest_dir = _get_tmp_dir()
471 clone_url = webserver.repo_url(GIT_REPO)
471 clone_url = webserver.repo_url(GIT_REPO)
472 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
472 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
473
473
474 # lock repo
474 # lock repo
475 r = Repository.get_by_repo_name(GIT_REPO)
475 r = Repository.get_by_repo_name(GIT_REPO)
476 # let this user actually push !
476 # let this user actually push !
477 RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
477 RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
478 perm='repository.write')
478 perm='repository.write')
479 Session().commit()
479 Session().commit()
480 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
480 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
481
481
482 # push fails repo is locked by other user !
482 # push fails repo is locked by other user !
483 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir,
483 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir,
484 username=TEST_USER_REGULAR_LOGIN,
484 username=TEST_USER_REGULAR_LOGIN,
485 password=TEST_USER_REGULAR_PASS,
485 password=TEST_USER_REGULAR_PASS,
486 ignoreReturnCode=True)
486 ignoreReturnCode=True)
487 err = 'Repository `%s` locked by user `%s`' % (GIT_REPO, TEST_USER_ADMIN_LOGIN)
487 err = 'Repository `%s` locked by user `%s`' % (GIT_REPO, TEST_USER_ADMIN_LOGIN)
488 assert err in stderr
488 assert err in stderr
489
489
490 # TODO: fix this somehow later on Git, Git is stupid and even if we throw
490 # TODO: fix this somehow later on Git, Git is stupid and even if we throw
491 # back 423 to it, it makes ANOTHER request and we fail there with 405 :/
491 # back 423 to it, it makes ANOTHER request and we fail there with 405 :/
492
492
493 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
493 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
494 % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
494 % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
495 #msg = "405 Method Not Allowed"
495 #msg = "405 Method Not Allowed"
496 #assert msg in stderr
496 #assert msg in stderr
497
497
498 def test_push_unlocks_repository_hg(self, webserver):
498 def test_push_unlocks_repository_hg(self, webserver):
499 # enable locking
499 # enable locking
500 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
500 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
501 fixture.create_fork(HG_REPO, fork_name)
501 fixture.create_fork(HG_REPO, fork_name)
502 r = Repository.get_by_repo_name(fork_name)
502 r = Repository.get_by_repo_name(fork_name)
503 r.enable_locking = True
503 r.enable_locking = True
504 Session().commit()
504 Session().commit()
505 # clone some temp
505 # clone some temp
506 dest_dir = _get_tmp_dir()
506 dest_dir = _get_tmp_dir()
507 clone_url = webserver.repo_url(fork_name)
507 clone_url = webserver.repo_url(fork_name)
508 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
508 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
509
509
510 # check for lock repo after clone
510 # check for lock repo after clone
511 r = Repository.get_by_repo_name(fork_name)
511 r = Repository.get_by_repo_name(fork_name)
512 uid = User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
512 uid = User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
513 assert r.locked[0] == uid
513 assert r.locked[0] == uid
514
514
515 # push is ok and repo is now unlocked
515 # push is ok and repo is now unlocked
516 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, clone_url=clone_url)
516 stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, clone_url=clone_url)
517 assert str('remote: Released lock on repo `%s`' % fork_name) in stdout
517 assert str('remote: Released lock on repo `%s`' % fork_name) in stdout
518 # we need to cleanup the Session Here !
518 # we need to cleanup the Session Here !
519 Session.remove()
519 Session.remove()
520 r = Repository.get_by_repo_name(fork_name)
520 r = Repository.get_by_repo_name(fork_name)
521 assert r.locked == [None, None]
521 assert r.locked == [None, None]
522
522
523 # TODO: fix me ! somehow during tests hooks don't get called on Git
523 # TODO: fix me ! somehow during tests hooks don't get called on Git
524 def test_push_unlocks_repository_git(self, webserver):
524 def test_push_unlocks_repository_git(self, webserver):
525 # enable locking
525 # enable locking
526 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
526 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
527 fixture.create_fork(GIT_REPO, fork_name)
527 fixture.create_fork(GIT_REPO, fork_name)
528 r = Repository.get_by_repo_name(fork_name)
528 r = Repository.get_by_repo_name(fork_name)
529 r.enable_locking = True
529 r.enable_locking = True
530 Session().commit()
530 Session().commit()
531 # clone some temp
531 # clone some temp
532 dest_dir = _get_tmp_dir()
532 dest_dir = _get_tmp_dir()
533 clone_url = webserver.repo_url(fork_name)
533 clone_url = webserver.repo_url(fork_name)
534 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
534 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
535
535
536 # check for lock repo after clone
536 # check for lock repo after clone
537 r = Repository.get_by_repo_name(fork_name)
537 r = Repository.get_by_repo_name(fork_name)
538 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
538 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
539
539
540 # push is ok and repo is now unlocked
540 # push is ok and repo is now unlocked
541 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, clone_url=clone_url)
541 stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, clone_url=clone_url)
542 _check_proper_git_push(stdout, stderr)
542 _check_proper_git_push(stdout, stderr)
543
543
544 assert ('remote: Released lock on repo `%s`' % fork_name) in stderr
544 assert ('remote: Released lock on repo `%s`' % fork_name) in stderr
545 # we need to cleanup the Session Here !
545 # we need to cleanup the Session Here !
546 Session.remove()
546 Session.remove()
547 r = Repository.get_by_repo_name(fork_name)
547 r = Repository.get_by_repo_name(fork_name)
548 assert r.locked == [None, None]
548 assert r.locked == [None, None]
549
549
550 def test_ip_restriction_hg(self, webserver):
550 def test_ip_restriction_hg(self, webserver):
551 user_model = UserModel()
551 user_model = UserModel()
552 try:
552 try:
553 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
553 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
554 Session().commit()
554 Session().commit()
555 clone_url = webserver.repo_url(HG_REPO)
555 clone_url = webserver.repo_url(HG_REPO)
556 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
556 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
557 assert 'abort: HTTP Error 403: Forbidden' in stderr
557 assert 'abort: HTTP Error 403: Forbidden' in stderr
558 finally:
558 finally:
559 # release IP restrictions
559 # release IP restrictions
560 for ip in UserIpMap.query():
560 for ip in UserIpMap.query():
561 UserIpMap.delete(ip.ip_id)
561 UserIpMap.delete(ip.ip_id)
562 Session().commit()
562 Session().commit()
563
563
564 # IP permissions are cached, need to wait for the cache in the server process to expire
564 # IP permissions are cached, need to wait for the cache in the server process to expire
565 time.sleep(1.5)
565 time.sleep(1.5)
566
566
567 clone_url = webserver.repo_url(HG_REPO)
567 clone_url = webserver.repo_url(HG_REPO)
568 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
568 stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
569
569
570 assert 'requesting all changes' in stdout
570 assert 'requesting all changes' in stdout
571 assert 'adding changesets' in stdout
571 assert 'adding changesets' in stdout
572 assert 'adding manifests' in stdout
572 assert 'adding manifests' in stdout
573 assert 'adding file changes' in stdout
573 assert 'adding file changes' in stdout
574
574
575 assert stderr == ''
575 assert stderr == ''
576
576
577 def test_ip_restriction_git(self, webserver):
577 def test_ip_restriction_git(self, webserver):
578 user_model = UserModel()
578 user_model = UserModel()
579 try:
579 try:
580 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
580 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
581 Session().commit()
581 Session().commit()
582 clone_url = webserver.repo_url(GIT_REPO)
582 clone_url = webserver.repo_url(GIT_REPO)
583 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
583 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
584 # The message apparently changed in Git 1.8.3, so match it loosely.
584 # The message apparently changed in Git 1.8.3, so match it loosely.
585 assert re.search(r'\b403\b', stderr)
585 assert re.search(r'\b403\b', stderr)
586 finally:
586 finally:
587 # release IP restrictions
587 # release IP restrictions
588 for ip in UserIpMap.query():
588 for ip in UserIpMap.query():
589 UserIpMap.delete(ip.ip_id)
589 UserIpMap.delete(ip.ip_id)
590 Session().commit()
590 Session().commit()
591
591
592 # IP permissions are cached, need to wait for the cache in the server process to expire
592 # IP permissions are cached, need to wait for the cache in the server process to expire
593 time.sleep(1.5)
593 time.sleep(1.5)
594
594
595 clone_url = webserver.repo_url(GIT_REPO)
595 clone_url = webserver.repo_url(GIT_REPO)
596 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
596 stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
597
597
598 assert 'Cloning into' in stdout + stderr
598 assert 'Cloning into' in stdout + stderr
599 assert stderr == '' or stdout == ''
599 assert stderr == '' or stdout == ''
600
600
601 @parametrize('repo_type, repo_name', [
601 @parametrize('repo_type, repo_name', [
602 #('git', GIT_REPO), # git hooks doesn't work like hg hooks
602 #('git', GIT_REPO), # git hooks doesn't work like hg hooks
603 ('hg', HG_REPO),
603 ('hg', HG_REPO),
604 ])
604 ])
605 def test_custom_hooks_preoutgoing(self, testhook_cleanup, webserver, testfork, repo_type, repo_name):
605 def test_custom_hooks_preoutgoing(self, testhook_cleanup, webserver, testfork, repo_type, repo_name):
606 # set prechangegroup to failing hook (returns True)
606 # set prechangegroup to failing hook (returns True)
607 Ui.create_or_update_hook('preoutgoing.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
607 Ui.create_or_update_hook('preoutgoing.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
608 Session().commit()
608 Session().commit()
609 # clone repo
609 # clone repo
610 clone_url = webserver.repo_url(testfork[repo_type], username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS)
610 clone_url = webserver.repo_url(testfork[repo_type], username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS)
611 dest_dir = _get_tmp_dir()
611 dest_dir = _get_tmp_dir()
612 stdout, stderr = Command(TESTS_TMP_PATH)\
612 stdout, stderr = Command(TESTS_TMP_PATH) \
613 .execute('%s clone' % repo_type, clone_url, dest_dir, ignoreReturnCode=True)
613 .execute('%s clone' % repo_type, clone_url, dest_dir, ignoreReturnCode=True)
614 if repo_type == 'hg':
614 if repo_type == 'hg':
615 assert 'preoutgoing.testhook hook failed' in stdout
615 assert 'preoutgoing.testhook hook failed' in stdout
616 elif repo_type == 'git':
616 elif repo_type == 'git':
617 assert 'error: 406' in stderr
617 assert 'error: 406' in stderr
618
618
619 @parametrize('repo_type, repo_name', [
619 @parametrize('repo_type, repo_name', [
620 #('git', GIT_REPO), # git hooks doesn't work like hg hooks
620 #('git', GIT_REPO), # git hooks doesn't work like hg hooks
621 ('hg', HG_REPO),
621 ('hg', HG_REPO),
622 ])
622 ])
623 def test_custom_hooks_prechangegroup(self, testhook_cleanup, webserver, testfork, repo_type, repo_name):
623 def test_custom_hooks_prechangegroup(self, testhook_cleanup, webserver, testfork, repo_type, repo_name):
624
624
625 # set prechangegroup to failing hook (returns True)
625 # set prechangegroup to failing hook (returns True)
626 Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
626 Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
627 Session().commit()
627 Session().commit()
628 # clone repo
628 # clone repo
629 clone_url = webserver.repo_url(testfork[repo_type], username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS)
629 clone_url = webserver.repo_url(testfork[repo_type], username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS)
630 dest_dir = _get_tmp_dir()
630 dest_dir = _get_tmp_dir()
631 stdout, stderr = Command(TESTS_TMP_PATH).execute('%s clone' % repo_type, clone_url, dest_dir)
631 stdout, stderr = Command(TESTS_TMP_PATH).execute('%s clone' % repo_type, clone_url, dest_dir)
632
632
633 stdout, stderr = _add_files_and_push(webserver, repo_type, dest_dir,
633 stdout, stderr = _add_files_and_push(webserver, repo_type, dest_dir,
634 username=TEST_USER_ADMIN_LOGIN,
634 username=TEST_USER_ADMIN_LOGIN,
635 password=TEST_USER_ADMIN_PASS,
635 password=TEST_USER_ADMIN_PASS,
636 ignoreReturnCode=True)
636 ignoreReturnCode=True)
637 assert 'failing_test_hook failed' in stdout + stderr
637 assert 'failing_test_hook failed' in stdout + stderr
638 assert 'Traceback' not in stdout + stderr
638 assert 'Traceback' not in stdout + stderr
639 assert 'prechangegroup.testhook hook failed' in stdout + stderr
639 assert 'prechangegroup.testhook hook failed' in stdout + stderr
640 # there are still outgoing changesets
640 # there are still outgoing changesets
641 stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
641 stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
642 assert stdout != ''
642 assert stdout != ''
643
643
644 # set prechangegroup hook to exception throwing method
644 # set prechangegroup hook to exception throwing method
645 Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.exception_test_hook')
645 Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.exception_test_hook')
646 Session().commit()
646 Session().commit()
647 # re-try to push
647 # re-try to push
648 stdout, stderr = Command(dest_dir).execute('%s push' % repo_type, clone_url, ignoreReturnCode=True)
648 stdout, stderr = Command(dest_dir).execute('%s push' % repo_type, clone_url, ignoreReturnCode=True)
649 if repo_type == 'hg':
649 if repo_type == 'hg':
650 # like with 'hg serve...' 'HTTP Error 500: INTERNAL SERVER ERROR' should be returned
650 # like with 'hg serve...' 'HTTP Error 500: INTERNAL SERVER ERROR' should be returned
651 assert 'HTTP Error 500: INTERNAL SERVER ERROR' in stderr
651 assert 'HTTP Error 500: INTERNAL SERVER ERROR' in stderr
652 elif repo_type == 'git':
652 elif repo_type == 'git':
653 assert 'exception_test_hook threw an exception' in stderr
653 assert 'exception_test_hook threw an exception' in stderr
654 # there are still outgoing changesets
654 # there are still outgoing changesets
655 stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
655 stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
656 assert stdout != ''
656 assert stdout != ''
657
657
658 # set prechangegroup hook to method that returns False
658 # set prechangegroup hook to method that returns False
659 Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.passing_test_hook')
659 Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.passing_test_hook')
660 Session().commit()
660 Session().commit()
661 # re-try to push
661 # re-try to push
662 stdout, stderr = Command(dest_dir).execute('%s push' % repo_type, clone_url, ignoreReturnCode=True)
662 stdout, stderr = Command(dest_dir).execute('%s push' % repo_type, clone_url, ignoreReturnCode=True)
663 assert 'passing_test_hook succeeded' in stdout + stderr
663 assert 'passing_test_hook succeeded' in stdout + stderr
664 assert 'Traceback' not in stdout + stderr
664 assert 'Traceback' not in stdout + stderr
665 assert 'prechangegroup.testhook hook failed' not in stdout + stderr
665 assert 'prechangegroup.testhook hook failed' not in stdout + stderr
666 # no more outgoing changesets
666 # no more outgoing changesets
667 stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
667 stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
668 assert stdout == ''
668 assert stdout == ''
669 assert stderr == ''
669 assert stderr == ''
@@ -1,61 +1,72 b''
1 #!/bin/bash
1 #!/bin/bash
2 set -e
2 set -e
3 set -x
3 set -x
4
4
5 echo "Checking tools needed for uploading stuff"
5 echo "Install/verify tools needed for building and uploading stuff"
6 pip freeze | grep '^Sphinx==' || pip install Sphinx
6 pip install --upgrade -e .
7 pip freeze | grep '^Sphinx-PyPI-upload==' || pip install Sphinx-PyPI-upload
7 pip install --upgrade -r dev_requirements.txt Sphinx Sphinx-PyPI-upload
8
9 echo "Cleanup and update copyrights ... and clean checkout"
10 scripts/run-all-cleanup
11 scripts/update-copyrights.py
12 hg up -cr .
8
13
9 echo "Verifying everything can build"
14 echo "Make release build from clean checkout in build/"
10 hg purge --all dist
15 rm -rf build dist
11 python2 setup.py build_sphinx
16 hg archive build
12 python2 setup.py compile_catalog # TODO: check for errors
17 cd build
18
19 echo "Check MANIFEST.in"
20 sed -e 's/[^ ]*[ ]*\([^ ]*\).*/\1/g' MANIFEST.in | grep -v '^node_modules/bootstrap\|^kallithea/public/css/style.css' | xargs ls -lad
21
22 echo "Build dist"
23 python2 setup.py compile_catalog
13 python2 setup.py sdist
24 python2 setup.py sdist
14
25
15 echo "Verifying VERSION from kallithea/__init__.py"
26 echo "Verify VERSION from kallithea/__init__.py"
16 namerel=$(cd dist && echo Kallithea-*.tar.gz)
27 namerel=$(cd dist && echo Kallithea-*.tar.gz)
17 namerel=${namerel%.tar.gz}
28 namerel=${namerel%.tar.gz}
18 version=${namerel#Kallithea-}
29 version=${namerel#Kallithea-}
30 ls -l $(pwd)/dist/$namerel.tar.gz
19 echo "Releasing Kallithea $version in directory $namerel"
31 echo "Releasing Kallithea $version in directory $namerel"
20 echo "Verifying current revision is tagged for $version"
21 hg log -r "'$version'&." | grep .
22
32
23 echo "Cleaning before making release build"
33 echo "Verify dist file content"
24 hg up -c .
34 diff -u <((hg mani | grep -v '^\.hg') | LANG=C sort) <(tar tf dist/Kallithea-$version.tar.gz | sed "s|^$namerel/||" | grep . | grep -v '^kallithea/i18n/.*/LC_MESSAGES/kallithea.mo$\|^Kallithea.egg-info/\|^PKG-INFO$\|/$' | LANG=C sort)
25 hg revert -a -r null
35 ! tar tf dist/Kallithea-$version.tar.gz | grep "$namerel/node_modules/bootstrap/\$"
26 hg up -C "'$version'&."
36
27 hg purge --all
37 echo "Verify docs build"
38 python2 setup.py build_sphinx # not used yet ... but we want to make sure it builds
39
40 cat - << EOT
28
41
29 echo "Building dist file"
42 Now, make sure
30 python2 setup.py compile_catalog
43 * the copyright and contributor lists have been updated
31 python2 setup.py sdist
44 * all tests are passing
32
45 * release note is ready
33 echo "Verifying dist file content"
46 * announcement is ready
34 diff -u <(hg mani | grep -v '^\.hg' | LANG=C sort) <(tar tf dist/Kallithea-*.tar.gz | sed "s|^$namerel/||" | grep . | grep -v '^kallithea/i18n/.*/LC_MESSAGES/kallithea.mo$\|^Kallithea.egg-info/\|^PKG-INFO$\|/$' | LANG=C sort)
47 * source has been pushed to https://kallithea-scm.org/repos/kallithea
35
48
36 echo "Now, make sure"
49 EOT
37 echo "* the copyright and contributor lists have been updated"
50
38 echo "* all tests are passing"
51 echo "Verify current revision is tagged for $version"
39 echo "* release note is ready"
52 hg log -r "'$version'&." | grep .
40 echo "* announcement is ready"
41 echo "* source has been pushed to https://kallithea-scm.org/repos/kallithea"
42 echo
43
53
44 echo -n "Enter \"pypi\" to upload Kallithea $version to pypi: "
54 echo -n "Enter \"pypi\" to upload Kallithea $version to pypi: "
45 read answer
55 read answer
46 [ "$answer" = "pypi" ]
56 [ "$answer" = "pypi" ]
47 extraargs=${EMAIL:+--identity=$EMAIL}
48 python2 setup.py sdist upload --sign $extraargs
49 xdg-open https://pypi.python.org/pypi/Kallithea
50
57
51 echo "Uploading docs to pypi"
58 echo "Upload docs to pypi"
52 # See https://wiki.python.org/moin/PyPiDocumentationHosting
59 # See https://wiki.python.org/moin/PyPiDocumentationHosting
53 python2 setup.py build_sphinx upload_sphinx
60 python2 setup.py build_sphinx upload_sphinx
54 xdg-open https://pythonhosted.org/Kallithea/
61 xdg-open https://pythonhosted.org/Kallithea/
55 xdg-open http://packages.python.org/Kallithea/installation.html
62 xdg-open http://packages.python.org/Kallithea/installation.html
56
63
57 echo "Rebuilding readthedocs for docs.kallithea-scm.org"
64 echo "Rebuild readthedocs for docs.kallithea-scm.org"
58 xdg-open https://readthedocs.org/projects/kallithea/
65 xdg-open https://readthedocs.org/projects/kallithea/
59 curl -X POST http://readthedocs.org/build/kallithea
66 curl -X POST http://readthedocs.org/build/kallithea
60 xdg-open https://readthedocs.org/builds/kallithea/
67 xdg-open https://readthedocs.org/builds/kallithea/
61 xdg-open http://docs.kallithea-scm.org/en/latest/ # or whatever the branch is
68 xdg-open http://docs.kallithea-scm.org/en/latest/ # or whatever the branch is
69
70 extraargs=${EMAIL:+--identity=$EMAIL}
71 python2 setup.py sdist upload --sign $extraargs
72 xdg-open https://pypi.python.org/pypi/Kallithea
General Comments 0
You need to be logged in to leave comments. Login now