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