##// END OF EJS Templates
release: Merge default into stable for release preparation
marcink -
r306:08b6bdad merge stable
parent child Browse files
Show More
@@ -0,0 +1,137 b''
1 .. _checklist-tickets:
2
3 =================
4 Ticket Checklists
5 =================
6
7
8 Ticket Description
9 ==================
10
11 In general these things really matter in the description:
12
13 - Reasoning / Rationale. Explain "WHY" it makes sense and is important.
14
15 - How to reproduce. Easy to follow steps, that’s important.
16
17 - Observation: The problem (short)
18
19 - Expectation: How it should be (short)
20
21 - Specs: It is fine to draft them as good as it works.
22
23 If anything is unclear, please ask for a review or help on this via the
24 Community Portal or Slack channel.
25
26
27 Checklists for Tickets
28 ======================
29
30 BUG
31 ---
32
33 Definition: An existing function that does not work as expected for the user.
34
35 - Problem description
36 - Steps needed to recreate (gherkin)
37 - Link to the screen in question and/or description of how to find it via
38 navigation
39 - Explanation of what the expected outcome is
40 - Any hints into the source of the problem
41 - Information about platform/browser/db/etc. where applicable
42 - Examples of other similar cases which have different behaviour
43
44 DESIGN
45 ------
46
47 Definition: Styling and user interface issues, including cosmetic improvements
48 or appearance and behaviour of frontend functionality.
49
50 - Screenshot/animation of existing page/behaviour
51 - Sketches or wireframes if available
52 - Link to the screen in question and/or description of how to find it via
53 navigation
54 - Problem description
55 - Explanation of what the expected outcome is
56 - Since this may be examined by a designer; it should be written in a way that a
57 non-developer can understand
58
59 EPIC
60 ----
61
62 Definition: A collection of tickets which together complete a larger overall
63 project.
64
65 - Benefit explanation
66 - Clear objective - when is this complete?
67 - Explanations of exceptions/corner cases
68 - Documentation subtask
69 - Comprehensive wireframes and/or design subtasks
70 - Links to subtasks
71
72 FEATURE
73 -------
74
75 Definition: A new function in the software which previously did not exist.
76
77 - Benefit explanation
78 - Clear objective
79 - Explanations of exceptions/corner cases
80 - Documentation subtask
81 - Comprehensive wireframes and/or design subtasks
82
83 SUPPORT
84 -------
85
86 Definition: An issue related to a customer report.
87
88 - Link to support ticket, if available
89 - Problem description
90 - Steps needed to recreate (gherkin)
91 - Link to the screen in question and/or description of how to find it via
92 navigation
93 - Explanation of what the expected outcome is
94 - Any hints into the source of the problem
95 - Information about platform/browser/db/etc. where applicable
96 - Examples of other similar cases which have different behaviour
97
98 TASK
99 ----
100
101 Definition: An improvement or step towards implementing a feature or fixing
102 a bug. Includes refactoring and other tech debt.
103
104 - Clear objective
105 - Benefit explanation
106 - Links to parent/related tickets
107
108
109 All details below.
110
111
112 External links:
113
114 - Avoid linking to external images; they disappear over time. Please attach any
115 relevant images to the ticket itself.
116
117 - External links in general: They also disappear over time, consider copying the
118 relevant bit of information into a comment or write a paragraph to sum up the
119 general idea.
120
121
122 Hints
123 =====
124
125 Change Description
126 ------------------
127
128 It can be tricky to figure out how to change the description of a ticket. There
129 is a very small pencil which has to be clicked once you see the edit form of a
130 ticket.
131
132
133 .. figure:: images/redmine-description.png
134 :alt: Example of pencil to change the ticket description
135
136 Shows an example of the pencil which lets you change the description.
137
@@ -0,0 +1,153 b''
1
2 ==================================================
3 Code style and structure guide for frontend work
4 ==================================================
5
6 About: Outline of frontend development practices.
7
8
9
10
11 Templates
12 =========
13
14 - Indent with 4 spaces in general.
15 - Embedded Python code follows the same conventions as in the backend.
16
17 A common problem is missed spaces around operators.
18
19
20
21
22 Grunt
23 =====
24
25 We use Grunt to compile our JavaScript and LESS files. This is done automatically
26 when you start an instance. If you are changing these files, however, it is
27 recommended to amend `--reload` to the `runserver` command, or use `grunt watch`
28 - the Gruntfile is located in the base directory. For more info on Grunt, see
29 http://gruntjs.com/
30
31
32
33
34 LESS CSS
35 ========
36
37
38 Style
39 -----
40
41 - Use 4 spaces instead of tabs.
42 - Avoid ``!important``; it is very often an indicator for a problem.
43
44
45
46
47 Structure
48 ---------
49
50 It is important that we maintain consistency in the LESS files so that things
51 scale properly. CSS is organized using LESS and then compiled into a CSS file
52 to be used in production. Find the class you need to change and change it
53 there. Do not add overriding styles at the end of the file. The LESS file will
54 be minified; use plenty of spacing and comments for readability.
55
56 These will be kept in auxillary LESS files to be imported (in this order) at the top:
57
58 - `fonts.less` (font-face declarations)
59 - `mixins` (place all LESS mixins here)
60 - `helpers` (basic classes for hiding mobile elements, centering, etc)
61 - `variables` (theme-specific colors, spacing, and fonts which might change later)
62
63
64 Sections of the primary LESS file are as follows. Add comments describing
65 layout and modules.
66
67 .. code-block:: css
68
69 //--- BASE ------------------//
70 Very basic, sitewide styles.
71
72 //--- LAYOUT ------------------//
73 Essential layout, ex. containers and wrappers.
74 Do not put type styles in here.
75
76 //--- MODULES ------------------//
77 Reusable sections, such as sidebars and menus.
78
79 //--- THEME ------------------//
80 Theme styles, typography, etc.
81
82
83
84 Formatting rules
85 ~~~~~~~~~~~~~~~~
86
87 - Each rule should be indented on a separate line (this is helpful for diff
88 checking).
89
90 - Use a space after each colon and a semicolon after each last rule.
91
92 - Put a blank line between each class.
93
94 - Nested classes should be listed after the parent class' rules, separated with a
95 blank line, and indented.
96
97 - Using the below as a guide, place each rule in order of its effect on content,
98 layout, sizing, and last listing minor style changes such as font color and
99 backgrounds. Not every possible rule is listed here; when adding new ones,
100 judge where it should go in the list based on that hierarchy.
101
102 .. code-block:: scss
103
104 .class {
105 content
106 list-style-type
107 position
108 float
109 top
110 right
111 bottom
112 left
113 height
114 max-height
115 min-height
116 width
117 max-width
118 min-width
119 margin
120 padding
121 indent
122 vertical-align
123 text-align
124 border
125 border-radius
126 font-size
127 line-height
128 font
129 font-style
130 font-variant
131 font-weight
132 color
133 text-shadow
134 background
135 background-color
136 box-shadow
137 background-url
138 background-position
139 background-repeat
140 background-cover
141 transitions
142 cursor
143 pointer-events
144
145 .nested-class {
146 position
147 background-color
148
149 &:hover {
150 color
151 }
152 }
153 }
@@ -0,0 +1,111 b''
1
2 =======================
3 Contributing Overview
4 =======================
5
6
7 RhodeCode Community Edition is an open source code management platform. We
8 encourage contributions to our project from the community. This is a basic
9 overview of the procedures for adding your contribution to RhodeCode.
10
11
12
13 Check the Issue Tracker
14 =======================
15
16 Make an account at https://issues.rhodecode.com/account/register and browse the
17 current tickets for bugs to fix and tasks to do. Have a bug or feature that you
18 can't find in the tracker? Create a new issue for it. When you select a ticket,
19 make sure to assign it to yourself and mark it "in progress" to avoid duplicated
20 work.
21
22
23
24 Sign Up at code.rhodecode.com
25 =============================
26
27 Make an account at https://code.rhodecode.com/ using an email or your existing
28 GitHub, Bitbucket, Google, or Twitter account. Fork the repo you'd like to
29 contribute to; we suggest adding your username to the fork name. Clone your fork
30 to your computer. We use Mercurial for source control management; see
31 https://www.mercurial-scm.org/guide to get started quickly.
32
33
34
35 Set Up A Local Instance
36 =======================
37
38 You will need to set up an instance of RhodeCode CE using VCSServer so that you
39 can see your work locally as you make changes. We recommend using Linux for this
40 but it can also be built on OSX.
41
42 See :doc:`dev-setup` for instructions.
43
44
45
46 Code!
47 =====
48
49 You can now make, see, and test your changes locally. We are always improving to
50 keep our code clean and the cost of maintaining it low. This applies in the same
51 way for contributions. We run automated checks on our pull requests, and expect
52 understandable code. We also aim to provide test coverage for as much of our
53 codebase as possible; any new features should be augmented with tests.
54
55 Keep in mind that when we accept your contribution, we also take responsibility
56 for it; we must understand it to take on that responsibility.
57
58 See :doc:`standards` for more detailed information.
59
60
61
62 Commit And Push Your Changes
63 ============================
64
65 We highly recommend making a new bookmark for each feature, bug, or set of
66 commits you make so that you can point to it when creating your pull request.
67 Please also reference the ticket number in your commit messages. Don't forget to
68 push the bookmark!
69
70
71
72 Submit a Pull Request
73 =====================
74
75 Go to your fork, and choose "Create Pull Request" from the Options menu. Use
76 your bookmark as the source, and choose someone to review it. Don't worry about
77 chosing the right person; we'll assign the best contributor for the job. You'll
78 get feedback and an assigned status.
79
80 Be prepared to make updates to your pull request after some feedback.
81 Collaboration is part of the process and improvements can often be made.
82
83
84
85 Sign the Contributor License Agreement
86 ======================================
87
88 If your contribution is approved, you will need to virtually sign the license
89 agreement in order for it to be merged into the project's codebase. You can read
90 it on our website here: https://rhodecode.com/static/pdf/RhodeCode-CLA.pdf
91
92 To sign, go to code.rhodecode.com
93 and clone the CLA repository. Add your name and make a pull request to add it to
94 the contributor agreement; this serves as your virtual signature. Once your
95 signature is merged, add a link to the relevant commit to your contribution
96 pull request.
97
98
99
100 That's it! We'll take it from there. Thanks for your contribution!
101 ------------------------------------------------------------------
102
103 .. note:: If you have any questions or comments, feel free to contact us through
104 either the community portal(community.rhodecode.com), IRC
105 (irc.freenode.net), or Slack (rhodecode.com/join).
106
107
108
109
110
111
@@ -0,0 +1,177 b''
1
2 ======================
3 Contribution Standards
4 ======================
5
6 Standards help to improve the quality of our product and its development. Herein
7 we define our standards for processes and development to maintain consistency
8 and function well as a community. It is a work in progress; modifications to this
9 document should be discussed and agreed upon by the community.
10
11
12 --------------------------------------------------------------------------------
13
14 Code
15 ====
16
17 This provides an outline for standards we use in our codebase to keep our code
18 easy to read and easy to maintain. Much of our code guidelines are based on the
19 book `Clean Code <http://www.pearsonhighered.com/educator/product/Clean-Code-A-Handbook-of-Agile-Software-Craftsmanship/9780132350884.page>`_
20 by Robert Martin.
21
22 We maintain a Tech Glossary to provide consistency in terms and symbolic names
23 used for items and concepts within the application. This is found in the CE
24 project in /docs-internal/glossary.rst
25
26
27 Refactoring
28 -----------
29 Make it better than you found it!
30
31 Our codebase can always use improvement and often benefits from refactoring.
32 New code should be refactored as it is being written, and old code should be
33 treated with the same care as if it was new. Before doing any refactoring,
34 ensure that there is test coverage on the affected code; this will help
35 minimize issues.
36
37
38 Python
39 ------
40 For Python, we use `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_.
41 We adjust lines of code to under 80 characters and use 4 spaces for indentation.
42
43
44 JavaScript
45 ----------
46 This currently remains undefined. Suggestions welcome!
47
48
49 HTML
50 ----
51 Unfortunately, we currently have no strict HTML standards, but there are a few
52 guidelines we do follow. Templates must work in all modern browsers. HTML should
53 be clean and easy to read, and additionally should be free of inline CSS or
54 JavaScript. It is recommended to use data attributes for JS actions where
55 possible in order to separate it from styling and prevent unintentional changes.
56
57
58 LESS/CSS
59 --------
60 We use LESS for our CSS; see :doc:`frontend` for structure and formatting
61 guidelines.
62
63
64 Linters
65 -------
66 Currently, we have a linter for pull requests which checks code against PEP8.
67 We intend to add more in the future as we clarify standards.
68
69
70 --------------------------------------------------------------------------------
71
72 Naming Conventions
73 ==================
74
75 These still need to be defined for naming everything from Python variables to
76 HTML classes to files and folders.
77
78
79 --------------------------------------------------------------------------------
80
81 Testing
82 =======
83
84 Testing is a very important aspect of our process, especially as we are our own
85 quality control team. While it is of course unrealistic to hit every potential
86 combination, our goal is to cover every line of Python code with a test.
87
88 The following is a brief introduction to our test suite. Our tests are primarily
89 written using `py.test <http://pytest.org/>`_
90
91
92 Acceptance Tests
93 ----------------
94 Also known as "ac tests", these test from the user and business perspective to
95 check if the requirements of a feature are met. Scenarios are created at a
96 feature's inception and help to define its value.
97
98 py.test is used for ac tests; they are located in a repo separate from the
99 other tests which follow. Each feature has a .feature file which contains a
100 brief description and the scenarios to be tested.
101
102
103 Functional Tests
104 ----------------
105 These test specific functionality in the application which checks through the
106 entire stack. Typically these are user actions or permissions which go through
107 the web browser. They are located in rhodecode/tests.
108
109
110 Unit Tests
111 ----------
112 These test isolated, individual modules to ensure that they function correctly.
113 They are located in rhodecode/tests.
114
115
116 Integration Tests
117 -----------------
118 These are used for testing performance of larger systems than the unit tests.
119 They are located in rhodecode/tests.
120
121
122 JavaScript Testing
123 ------------------
124 Currently, we have not defined how to test our JavaScript code.
125
126
127 --------------------------------------------------------------------------------
128
129 Pull Requests
130 =============
131
132 Pull requests should generally contain only one thing: a single feature, one bug
133 fix, etc.. The commit history should be concise and clean, and the pull request
134 should contain the ticket number (also a good idea for the commits themselves)
135 to provide context for the reviewer.
136
137 See also: :doc:`checklist-pull-request`
138
139
140 Reviewers
141 ---------
142 Each pull request must be approved by at least one member of the RhodeCode
143 team. Assignments may be based on expertise or familiarity with a particular
144 area of code, or simply availability. Switching up or adding extra community
145 members for different pull requests helps to share knowledge as well as provide
146 other perspectives.
147
148
149 Responsibility
150 --------------
151 The community is responsible for maintaining features and this must be taken
152 into consideration. External contributions must be held to the same standards
153 as internal contributions.
154
155
156 Feature Switch
157 --------------
158 Experimental and work-in-progress features can be hidden behind one of two
159 switches:
160
161 * A setting can be added to the Labs page in the Admin section which may allow
162 customers to access and toggle additional features.
163 * For work-in-progress or other features where customer access is not desired,
164 use a custom setting in the .ini file as a trigger.
165
166
167 --------------------------------------------------------------------------------
168
169 Tickets
170 =======
171
172 Redmine tickets are a crucial part of our development process. Any code added or
173 changed in our codebase should have a corresponding ticket to document it. With
174 this in mind, it is important that tickets be as clear and concise as possible,
175 including what the expected outcome is.
176
177 See also: :doc:`checklist-tickets`
@@ -0,0 +1,70 b''
1 |RCE| 4.2.0 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2016-06-30
8
9
10 General
11 ^^^^^^^
12
13 - Autocomplete: add GET flag support to show/hide active users on autocomplete,
14 also display this information in autocomplete data. ref #3374
15 - Gravatar: add flag to show current gravatar + user as disabled user (non-active)
16 - Repos, repo groups, user groups: allow to use disabled users in owner field.
17 This fixes #3374.
18 - Repos, repo groups, user groups: visually show what user is an owner of a
19 resource, and if potentially he is disabled in the system.
20 - Pull requests: reorder navigation on repo pull requests, fixes #2995
21 - Dependencies: bump dulwich to 0.13.0
22
23 New Features
24 ^^^^^^^^^^^^
25
26 - My account: made pull requests aggregate view for users look like not
27 created in 1995. Now uses a consistent look with repo one.
28 - emails: expose profile link on registation email that super-admins receive.
29 Implements #3999.
30 - Social auth: move the buttons to the top of nav so they are easier to reach.
31
32
33 Security
34 ^^^^^^^^
35
36 - Encryption: allow to pass in alternative key for encryption values. Now
37 users can use `rhodecode.encrypted_values.secret` that is alternative key,
38 de-coupled from `beaker.session` key.
39 - Encryption: Implement a slightly improved AesCipher encryption.
40 This addresses issues from #4036.
41 - Encryption: encrypted values now by default uses HMAC signatures to detect
42 if data or secret key is incorrect. The strict checks can be disabled using
43 `rhodecode.encrypted_values.strict = false` .ini setting
44
45
46 Performance
47 ^^^^^^^^^^^
48
49 - Sql: use smarter JOINs when fetching repository information
50 - Helpers: optimize slight calls for link_to_user to save some intense queries.
51 - App settings: use computed caches for repository settings, this in some cases
52 brings almost 4x performance increase for large repos with a lot of issue
53 tracker patterns.
54
55
56 Fixes
57 ^^^^^
58
59 - Fixed events on user pre/post create actions
60 - Authentication: fixed problem with saving forms with errors on auth plugins
61 - Svn: Avoid chunked transfer for Subversion that caused checkout issues in some cases.
62 - Users: fix generate new user password helper.
63 - Celery: fixed problem with workers running action in sync mode in some cases.
64 - Setup-db: fix redundant question on writable dir. The question needs to be
65 asked only when the dir is actually not writable.
66 - Elasticsearch: fixed issues when searching single repo using elastic search
67 - Social auth: fix issues with non-active users using social authentication
68 causing a 500 error.
69 - Fixed problem with largefiles extensions on per-repo settings using local
70 .hgrc files present inside the repo directory.
@@ -0,0 +1,40 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 from rhodecode.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
25
26
27 def includeme(config):
28 settings = config.get_settings()
29
30 # Create admin navigation registry and add it to the pyramid registry.
31 labs_active = str2bool(settings.get('labs_settings_active', False))
32 navigation_registry = NavigationRegistry(labs_active=labs_active)
33 config.registry.registerUtility(navigation_registry)
34
35 config.add_route(
36 name='admin_settings_open_source',
37 pattern=ADMIN_PREFIX + '/settings/open_source')
38
39 # Scan module for configuration decorators.
40 config.scan()
@@ -0,0 +1,29 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from zope.interface import Interface
22
23
24 class IAdminNavigationRegistry(Interface):
25 """
26 Interface for the admin navigation registry. Currently this is only
27 used to register and retrieve it via pyramids registry.
28 """
29 pass
@@ -0,0 +1,124 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import logging
23 import collections
24
25 from pylons import url
26 from zope.interface import implementer
27
28 from rhodecode.admin.interfaces import IAdminNavigationRegistry
29 from rhodecode.lib.utils import get_registry
30 from rhodecode.translation import _
31
32
33 log = logging.getLogger(__name__)
34
35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
36
37
38 class NavEntry(object):
39 """
40 Represents an entry in the admin navigation.
41
42 :param key: Unique identifier used to store reference in an OrderedDict.
43 :param name: Display name, usually a translation string.
44 :param view_name: Name of the view, used generate the URL.
45 :param pyramid: Indicator to use pyramid for URL generation. This should
46 be removed as soon as we are fully migrated to pyramid.
47 """
48
49 def __init__(self, key, name, view_name, pyramid=False):
50 self.key = key
51 self.name = name
52 self.view_name = view_name
53 self.pyramid = pyramid
54
55 def generate_url(self, request):
56 if self.pyramid:
57 if hasattr(request, 'route_path'):
58 return request.route_path(self.view_name)
59 else:
60 # TODO: johbo: Remove this after migrating to pyramid.
61 # We need the pyramid request here to generate URLs to pyramid
62 # views from within pylons views.
63 from pyramid.threadlocal import get_current_request
64 pyramid_request = get_current_request()
65 return pyramid_request.route_path(self.view_name)
66 else:
67 return url(self.view_name)
68
69
70 @implementer(IAdminNavigationRegistry)
71 class NavigationRegistry(object):
72
73 _base_entries = [
74 NavEntry('global', _('Global'), 'admin_settings_global'),
75 NavEntry('vcs', _('VCS'), 'admin_settings_vcs'),
76 NavEntry('visual', _('Visual'), 'admin_settings_visual'),
77 NavEntry('mapping', _('Remap and Rescan'), 'admin_settings_mapping'),
78 NavEntry('issuetracker', _('Issue Tracker'),
79 'admin_settings_issuetracker'),
80 NavEntry('email', _('Email'), 'admin_settings_email'),
81 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
82 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
83 NavEntry('system', _('System Info'), 'admin_settings_system'),
84 NavEntry('open_source', _('Open Source Licenses'),
85 'admin_settings_open_source', pyramid=True),
86 # TODO: marcink: we disable supervisor now until the supervisor stats
87 # page is fixed in the nix configuration
88 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
89 ]
90
91 _labs_entry = NavEntry('labs', _('Labs'),
92 'admin_settings_labs')
93
94 def __init__(self, labs_active=False):
95 self._registered_entries = collections.OrderedDict([
96 (item.key, item) for item in self.__class__._base_entries
97 ])
98
99 if labs_active:
100 self.add_entry(self._labs_entry)
101
102 def add_entry(self, entry):
103 self._registered_entries[entry.key] = entry
104
105 def get_navlist(self, request):
106 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
107 for i in self._registered_entries.values()]
108 return navlist
109
110
111 def navigation_registry(request):
112 """
113 Helper that returns the admin navigation registry.
114 """
115 pyramid_registry = get_registry(request)
116 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
117 return nav_registry
118
119
120 def navigation_list(request):
121 """
122 Helper that returns the admin navigation as list of NavListEntry objects.
123 """
124 return navigation_registry(request).get_navlist(request)
@@ -0,0 +1,55 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import collections
22 import logging
23
24 from pylons import tmpl_context as c
25 from pyramid.view import view_config
26
27 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
28 from rhodecode.lib.utils import read_opensource_licenses
29
30 from .navigation import navigation_list
31
32
33 log = logging.getLogger(__name__)
34
35
36 class AdminSettingsView(object):
37
38 def __init__(self, context, request):
39 self.request = request
40 self.context = context
41 self.session = request.session
42 self._rhodecode_user = request.user
43
44 @LoginRequired()
45 @HasPermissionAllDecorator('hg.admin')
46 @view_config(
47 route_name='admin_settings_open_source', request_method='GET',
48 renderer='rhodecode:templates/admin/settings/settings.html')
49 def open_source_licenses(self):
50 c.active = 'open_source'
51 c.navlist = navigation_list(self.request)
52 c.opensource_licenses = collections.OrderedDict(
53 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
54
55 return {}
1 NO CONTENT: new file 100644
@@ -0,0 +1,92 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import pytest
23
24
25 class EnabledAuthPlugin():
26 """
27 Context manager that updates the 'auth_plugins' setting in DB to enable
28 a plugin. Previous setting is restored on exit. The rhodecode auth plugin
29 is also enabled because it is needed to login the test users.
30 """
31
32 def __init__(self, plugin):
33 self.new_value = set([
34 'egg:rhodecode-enterprise-ce#rhodecode',
35 plugin.get_id()
36 ])
37
38 def __enter__(self):
39 from rhodecode.model.settings import SettingsModel
40 self._old_value = SettingsModel().get_auth_plugins()
41 SettingsModel().create_or_update_setting(
42 'auth_plugins', ','.join(self.new_value))
43
44 def __exit__(self, type, value, traceback):
45 from rhodecode.model.settings import SettingsModel
46 SettingsModel().create_or_update_setting(
47 'auth_plugins', ','.join(self._old_value))
48
49
50 class DisabledAuthPlugin():
51 """
52 Context manager that updates the 'auth_plugins' setting in DB to disable
53 a plugin. Previous setting is restored on exit.
54 """
55
56 def __init__(self, plugin):
57 self.plugin_id = plugin.get_id()
58
59 def __enter__(self):
60 from rhodecode.model.settings import SettingsModel
61 self._old_value = SettingsModel().get_auth_plugins()
62 new_value = [id_ for id_ in self._old_value if id_ != self.plugin_id]
63 SettingsModel().create_or_update_setting(
64 'auth_plugins', ','.join(new_value))
65
66 def __exit__(self, type, value, traceback):
67 from rhodecode.model.settings import SettingsModel
68 SettingsModel().create_or_update_setting(
69 'auth_plugins', ','.join(self._old_value))
70
71
72 @pytest.fixture(params=[
73 ('rhodecode.authentication.plugins.auth_crowd', 'egg:rhodecode-enterprise-ce#crowd'),
74 ('rhodecode.authentication.plugins.auth_headers', 'egg:rhodecode-enterprise-ce#headers'),
75 ('rhodecode.authentication.plugins.auth_jasig_cas', 'egg:rhodecode-enterprise-ce#jasig_cas'),
76 ('rhodecode.authentication.plugins.auth_ldap', 'egg:rhodecode-enterprise-ce#ldap'),
77 ('rhodecode.authentication.plugins.auth_pam', 'egg:rhodecode-enterprise-ce#pam'),
78 ('rhodecode.authentication.plugins.auth_rhodecode', 'egg:rhodecode-enterprise-ce#rhodecode'),
79 ('rhodecode.authentication.plugins.auth_token', 'egg:rhodecode-enterprise-ce#token'),
80 ])
81 def auth_plugin(request):
82 """
83 Fixture that provides instance for each authentication plugin. These
84 instances are NOT the instances which are registered to the authentication
85 registry.
86 """
87 from importlib import import_module
88
89 # Create plugin instance.
90 module, plugin_id = request.param
91 plugin_module = import_module(module)
92 return plugin_module.plugin_factory(plugin_id)
1 NO CONTENT: new file 100644
@@ -0,0 +1,77 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import pytest
23
24 from rhodecode.authentication.tests.conftest import (
25 EnabledAuthPlugin, DisabledAuthPlugin)
26 from rhodecode.config.routing import ADMIN_PREFIX
27
28
29 @pytest.mark.usefixtures('autologin_user', 'app')
30 class TestAuthenticationSettings:
31
32 def test_auth_settings_global_view_get(self, app):
33 url = '{prefix}/auth/'.format(prefix=ADMIN_PREFIX)
34 response = app.get(url)
35 assert response.status_code == 200
36
37 def test_plugin_settings_view_get(self, app, auth_plugin):
38 url = '{prefix}/auth/{name}'.format(
39 prefix=ADMIN_PREFIX,
40 name=auth_plugin.name)
41 with EnabledAuthPlugin(auth_plugin):
42 response = app.get(url)
43 assert response.status_code == 200
44
45 def test_plugin_settings_view_post(self, app, auth_plugin, csrf_token):
46 url = '{prefix}/auth/{name}'.format(
47 prefix=ADMIN_PREFIX,
48 name=auth_plugin.name)
49 params = {
50 'enabled': True,
51 'cache_ttl': 0,
52 'csrf_token': csrf_token,
53 }
54 with EnabledAuthPlugin(auth_plugin):
55 response = app.post(url, params=params)
56 assert response.status_code in [200, 302]
57
58 def test_plugin_settings_view_get_404(self, app, auth_plugin):
59 url = '{prefix}/auth/{name}'.format(
60 prefix=ADMIN_PREFIX,
61 name=auth_plugin.name)
62 with DisabledAuthPlugin(auth_plugin):
63 response = app.get(url, status=404)
64 assert response.status_code == 404
65
66 def test_plugin_settings_view_post_404(self, app, auth_plugin, csrf_token):
67 url = '{prefix}/auth/{name}'.format(
68 prefix=ADMIN_PREFIX,
69 name=auth_plugin.name)
70 params = {
71 'enabled': True,
72 'cache_ttl': 0,
73 'csrf_token': csrf_token,
74 }
75 with DisabledAuthPlugin(auth_plugin):
76 response = app.post(url, params=params, status=404)
77 assert response.status_code == 404
@@ -0,0 +1,28 b''
1 # Copyright (C) 2016 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 """
20 Checks around the API of the class RhodeCodeAuthPluginBase.
21 """
22
23 from rhodecode.authentication.base import RhodeCodeAuthPluginBase
24
25
26 def test_str_returns_plugin_id():
27 plugin = RhodeCodeAuthPluginBase(plugin_id='stub_plugin_id')
28 assert str(plugin) == 'stub_plugin_id'
@@ -0,0 +1,75 b''
1 <%
2 elems = [
3 ## general
4 (_('RhodeCode Enterprise version'), c.rhodecode_version, ''),
5 (_('Upgrade info endpoint'), c.rhodecode_update_url, ''),
6 (_('Configuration INI file'), c.rhodecode_config_ini, ''),
7 ## systems stats
8 (_('RhodeCode Enterprise Server IP'), c.server_ip, ''),
9 (_('RhodeCode Enterprise Server ID'), c.server_id, ''),
10 (_('Platform'), c.platform, ''),
11 (_('Uptime'), c.uptime_age, ''),
12 (_('Storage location'), c.storage, ''),
13 (_('Storage disk space'), "%s/%s, %s%% used%s" % (h.format_byte_size_binary(c.disk['used']), h.format_byte_size_binary(c.disk['total']),(c.disk['percent']), ' %s' % c.disk['error'] if 'error' in c.disk else ''), ''),
14
15 (_('Search index storage'), c.index_storage, ''),
16 (_('Search index size'), "%s %s" % (h.format_byte_size_binary(c.disk_index['used']), ' %s' % c.disk_index['error'] if 'error' in c.disk_index else ''), ''),
17
18 (_('Gist storage'), c.gist_storage, ''),
19 (_('Gist storage size'), "%s (%s items)%s" % (h.format_byte_size_binary(c.disk_gist['used']),c.disk_gist['items'], ' %s' % c.disk_gist['error'] if 'error' in c.disk_gist else ''), ''),
20
21 (_('Archive cache'), c.archive_storage, ''),
22 (_('Archive cache size'), "%s%s" % (h.format_byte_size_binary(c.disk_archive['used']), ' %s' % c.disk_archive['error'] if 'error' in c.disk_archive else ''), ''),
23
24 (_('System memory'), c.system_memory, ''),
25 (_('CPU'), '%s %%' %(c.cpu), ''),
26 (_('Load'), '1min: %s, 5min: %s, 15min: %s' %(c.load['1_min'],c.load['5_min'],c.load['15_min']), ''),
27
28 ## rhodecode stuff
29 (_('Python version'), c.py_version, ''),
30 (_('Python path'), c.py_path, ''),
31 (_('GIT version'), c.git_version, ''),
32 (_('HG version'), c.hg_version, ''),
33 (_('SVN version'), c.svn_version, ''),
34 (_('Database'), "%s @ version: %s" % (c.db_type, c.db_migrate_version), ''),
35 (_('Database version'), c.db_version, ''),
36
37 ]
38 %>
39
40 <pre>
41 SYSTEM INFO
42 -----------
43
44 % for dt, dd, tt in elems:
45 ${dt}: ${dd}
46 % endfor
47
48 PYTHON PACKAGES
49 ---------------
50
51 % for key, value in c.py_modules:
52 ${key}: ${value}
53 % endfor
54
55 SYSTEM SETTINGS
56 ---------------
57
58 % for key, value in sorted(c.rhodecode_ini_safe.items()):
59 % if isinstance(value, dict):
60
61 % for key2, value2 in value.items():
62 [${key}]${key2}: ${value2}
63 % endfor
64
65 % else:
66 ${key}: ${value}
67 % endif
68 % endfor
69
70 </pre>
71
72
73
74
75
@@ -1,6 +1,6 b''
1 1 [bumpversion]
2 current_version = 4.1.2
2 current_version = 4.2.0
3 3 message = release: Bump version {current_version} to {new_version}
4 4
5 5 [bumpversion:file:rhodecode/VERSION]
6 6
@@ -1,34 +1,28 b''
1 1 [DEFAULT]
2 2 done = false
3 3
4 [task:fixes_on_stable]
5
6 [task:changelog_updated]
7
4 8 [task:bump_version]
5 9 done = true
6 10
7 [task:rc_tools_pinned]
8 done = true
11 [task:generate_api_docs]
12
13 [task:updated_translation]
9 14
10 [task:fixes_on_stable]
11 done = true
15 [release]
16 state = in_progress
17 version = 4.2.0
18
19 [task:rc_tools_pinned]
12 20
13 21 [task:pip2nix_generated]
14 done = true
15
16 [task:changelog_updated]
17 done = true
18
19 [task:generate_api_docs]
20 done = true
21 22
22 23 [task:generate_js_routes]
23 done = true
24
25 [release]
26 state = prepared
27 version = 4.1.2
28
29 [task:updated_translation]
30 24
31 25 [task:updated_trial_license]
32 26
33 27 [task:generate_oss_licenses]
34 28
@@ -1,101 +1,114 b''
1 1 =========
2 2 RhodeCode
3 3 =========
4 4
5 5 About
6 6 -----
7 7
8 8 ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_
9 9 and Subversion_ with a built in push/pull server, full text search,
10 10 pull requests and powerfull code-review system. It works on http/https and
11 11 has a few unique features like:
12 12 - plugable architecture
13 13 - advanced permission system with IP restrictions
14 14 - rich set of authentication plugins including LDAP,
15 15 ActiveDirectory, Atlassian Crowd, Http-Headers, Pam, Token-Auth.
16 16 - live code-review chat
17 17 - full web based file editing
18 18 - unified multi vcs support
19 19 - snippets (gist) system
20 20 - integration with all 3rd party issue trackers
21 21
22 22 RhodeCode also provides rich API, and multiple event hooks so it's easy
23 23 integrable with existing external systems.
24 24
25 25 RhodeCode is similar in some respects to gitlab_, github_ or bitbucket_,
26 26 however RhodeCode can be run as standalone hosted application on your own server.
27 27 RhodeCode can be installed on \*nix or Windows systems.
28 28
29 29 RhodeCode uses `PEP386 versioning <http://www.python.org/dev/peps/pep-0386/>`_
30 30
31 31 Installation
32 32 ------------
33 33 Please visit https://docs.rhodecode.com/RhodeCode-Control/tasks/install-cli.html
34 34 for more details
35 35
36 36
37 37 Source code
38 38 -----------
39 39
40 40 The latest sources can be obtained from official RhodeCode instance
41 41 https://code.rhodecode.com
42 42
43 43
44 Contributions
45 -------------
46
47 RhodeCode is open-source; contributions are welcome!
48
49 Please see the contribution documentation inside of the docs folder, which is
50 also available at
51 https://docs.rhodecode.com/RhodeCode-Enterprise/contributing/contributing.html
52
53 For additional information about collaboration tools, our issue tracker,
54 licensing, and contribution credit, visit https://rhodecode.com/open-source
55
56
44 57 RhodeCode Features
45 58 ------------------
46 59
47 60 Check out all features of RhodeCode at https://rhodecode.com/features
48 61
49 62 License
50 63 -------
51 64
52 65 ``RhodeCode`` is dual-licensed with AGPLv3 and commercial license.
53 66 Please see LICENSE.txt file for details.
54 67
55 68
56 69 Getting help
57 70 ------------
58 71
59 72 Listed bellow are various support resources that should help.
60 73
61 74 .. note::
62 75
63 76 Please try to read the documentation before posting any issues, especially
64 77 the **troubleshooting section**
65 78
66 79 - Official issue tracker `RhodeCode Issue tracker <https://issues.rhodecode.com>`_
67 80
68 81 - Search our community portal `Community portal <https://community.rhodecode.com>`_
69 82
70 83 - Join #rhodecode on FreeNode (irc.freenode.net)
71 84 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
72 85
73 86 - You can also follow RhodeCode on twitter **@RhodeCode** where we often post
74 87 news and other interesting stuff about RhodeCode.
75 88
76 89
77 90 Online documentation
78 91 --------------------
79 92
80 93 Online documentation for the current version of RhodeCode is available at
81 94 - http://rhodecode.com/docs
82 95
83 96 You may also build the documentation for yourself - go into ``docs/`` and run::
84 97
85 98 nix-build default.nix -o result && make clean html
86 99
87 100 (You need to have sphinx_ installed to build the documentation. If you don't
88 101 have sphinx_ installed you can install it via the command:
89 102 ``pip install sphinx``)
90 103
91 104 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
92 105 .. _python: http://www.python.org/
93 106 .. _sphinx: http://sphinx.pocoo.org/
94 107 .. _mercurial: http://mercurial.selenic.com/
95 108 .. _bitbucket: http://bitbucket.org/
96 109 .. _github: http://github.com/
97 110 .. _gitlab: http://gitlab.com/
98 111 .. _subversion: http://subversion.tigris.org/
99 112 .. _git: http://git-scm.com/
100 113 .. _celery: http://celeryproject.org/
101 114 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,47 +1,47 b''
1 1 README - Quickstart
2 2 ===================
3 3
4 This folder contains functional tests and the automation of specification
4 This folder contains the functional tests and automation of specification
5 5 examples. Details about testing can be found in
6 6 `/docs-internal/testing/index.rst`.
7 7
8 8
9 9 Setting up your Rhodecode Enterprise instance
10 10 ---------------------------------------------
11 11
12 12 The tests will create users and repositories as needed, so you can start with a
13 13 new and empty instance.
14 14
15 15 Use the following example call for the database setup of Enterprise::
16 16
17 17 paster setup-rhodecode \
18 18 --user=admin \
19 19 --email=admin@example.com \
20 20 --password=secret \
21 21 --api-key=9999999999999999999999999999999999999999 \
22 22 your-enterprise-config.ini
23 23
24 This way the username, password and auth token of the admin user will match the
24 This way the username, password, and auth token of the admin user will match the
25 25 defaults from the test run.
26 26
27 27
28 28 Usage
29 29 -----
30 30
31 31 1. Make sure your Rhodecode Enterprise instance is running at
32 32 http://localhost:5000.
33 33
34 34 2. Enter `nix-shell` from the acceptance_tests folder::
35 35
36 36 cd acceptance_tests
37 nix-shell -I ~/dev
37 nix-shell
38 38
39 39 Make sure that `rcpkgs` and `rcnixpkgs` are available on the nix path.
40 40
41 41 3. Run the tests::
42 42
43 43 py.test -c example.ini -vs
44 44
45 45 The parameter ``-vs`` allows you to see debugging output during the test
46 46 run. Check ``py.test --help`` and the documentation at http://pytest.org to
47 47 learn all details about the test runner.
@@ -1,608 +1,612 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode Enterprise - configuration file #
4 4 # Built-in functions and variables #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 # #
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10 debug = true
11 pdebug = false
12 11 ################################################################################
13 12 ## Uncomment and replace with the email address which should receive ##
14 13 ## any error reports after an application crash ##
15 14 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 15 ################################################################################
17 16 #email_to = admin@localhost
18 17 #error_email_from = paste_error@localhost
19 18 #app_email_from = rhodecode-noreply@localhost
20 19 #error_message =
21 20 #email_prefix = [RhodeCode]
22 21
23 22 #smtp_server = mail.server.com
24 23 #smtp_username =
25 24 #smtp_password =
26 25 #smtp_port =
27 26 #smtp_use_tls = false
28 27 #smtp_use_ssl = true
29 28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
30 29 #smtp_auth =
31 30
32 31 [server:main]
33 32 ## COMMON ##
34 33 host = 127.0.0.1
35 34 port = 5000
36 35
37 36 ##################################
38 37 ## WAITRESS WSGI SERVER ##
39 38 ## Recommended for Development ##
40 39 ##################################
41 40 use = egg:waitress#main
42 41 ## number of worker threads
43 42 threads = 5
44 43 ## MAX BODY SIZE 100GB
45 44 max_request_body_size = 107374182400
46 45 ## Use poll instead of select, fixes file descriptors limits problems.
47 46 ## May not work on old windows systems.
48 47 asyncore_use_poll = true
49 48
50 49
51 50 ##########################
52 51 ## GUNICORN WSGI SERVER ##
53 52 ##########################
54 53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
55 54 #use = egg:gunicorn#main
56 55 ## Sets the number of process workers. You must set `instance_id = *`
57 56 ## when this option is set to more than one worker, recommended
58 57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
59 58 ## The `instance_id = *` must be set in the [app:main] section below
60 59 #workers = 2
61 60 ## number of threads for each of the worker, must be set to 1 for gevent
62 61 ## generally recommened to be at 1
63 62 #threads = 1
64 63 ## process name
65 64 #proc_name = rhodecode
66 65 ## type of worker class, one of sync, gevent
67 66 ## recommended for bigger setup is using of of other than sync one
68 67 #worker_class = sync
69 68 ## The maximum number of simultaneous clients. Valid only for Gevent
70 69 #worker_connections = 10
71 70 ## max number of requests that worker will handle before being gracefully
72 71 ## restarted, could prevent memory leaks
73 72 #max_requests = 1000
74 73 #max_requests_jitter = 30
75 74 ## amount of time a worker can spend with handling a request before it
76 75 ## gets killed and restarted. Set to 6hrs
77 76 #timeout = 21600
78 77
79 78
80 79 ## prefix middleware for RhodeCode, disables force_https flag.
81 80 ## allows to set RhodeCode under a prefix in server.
82 81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
83 82 #[filter:proxy-prefix]
84 83 #use = egg:PasteDeploy#prefix
85 84 #prefix = /<your-prefix>
86 85
87 86 [app:main]
88 87 use = egg:rhodecode-enterprise-ce
89 88 ## enable proxy prefix middleware, defined below
90 89 #filter-with = proxy-prefix
91 90
92 91 # During development the we want to have the debug toolbar enabled
93 92 pyramid.includes =
94 93 pyramid_debugtoolbar
95 94 rhodecode.utils.debugtoolbar
96 95 rhodecode.lib.middleware.request_wrapper
97 96
98 97 pyramid.reload_templates = true
99 98
100 99 debugtoolbar.hosts = 0.0.0.0/0
101 100 debugtoolbar.exclude_prefixes =
102 101 /css
103 102 /fonts
104 103 /images
105 104 /js
106 105
107 106 ## RHODECODE PLUGINS ##
108 107 rhodecode.includes =
109 108 rhodecode.api
110 109
111 110
112 111 # api prefix url
113 112 rhodecode.api.url = /_admin/api
114 113
115 114
116 115 ## END RHODECODE PLUGINS ##
117 116
117 ## encryption key used to encrypt social plugin tokens,
118 ## remote_urls with credentials etc, if not set it defaults to
119 ## `beaker.session.secret`
120 #rhodecode.encrypted_values.secret =
121
122 ## decryption strict mode (enabled by default). It controls if decryption raises
123 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
124 #rhodecode.encrypted_values.strict = false
125
118 126 full_stack = true
119 127
120 128 ## Serve static files via RhodeCode, disable to serve them via HTTP server
121 129 static_files = true
122 130
131 # autogenerate javascript routes file on startup
132 generate_js_files = false
133
123 134 ## Optional Languages
124 135 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
125 136 lang = en
126 137
127 138 ## perform a full repository scan on each server start, this should be
128 139 ## set to false after first startup, to allow faster server restarts.
129 140 startup.import_repos = false
130 141
131 142 ## Uncomment and set this path to use archive download cache.
132 143 ## Once enabled, generated archives will be cached at this location
133 144 ## and served from the cache during subsequent requests for the same archive of
134 145 ## the repository.
135 146 #archive_cache_dir = /tmp/tarballcache
136 147
137 148 ## change this to unique ID for security
138 149 app_instance_uuid = rc-production
139 150
140 151 ## cut off limit for large diffs (size in bytes)
141 152 cut_off_limit_diff = 1024000
142 153 cut_off_limit_file = 256000
143 154
144 155 ## use cache version of scm repo everywhere
145 156 vcs_full_cache = true
146 157
147 158 ## force https in RhodeCode, fixes https redirects, assumes it's always https
148 159 ## Normally this is controlled by proper http flags sent from http server
149 160 force_https = false
150 161
151 162 ## use Strict-Transport-Security headers
152 163 use_htsts = false
153 164
154 165 ## number of commits stats will parse on each iteration
155 166 commit_parse_limit = 25
156 167
157 168 ## git rev filter option, --all is the default filter, if you need to
158 169 ## hide all refs in changelog switch this to --branches --tags
159 170 git_rev_filter = --branches --tags
160 171
161 172 # Set to true if your repos are exposed using the dumb protocol
162 173 git_update_server_info = false
163 174
164 175 ## RSS/ATOM feed options
165 176 rss_cut_off_limit = 256000
166 177 rss_items_per_page = 10
167 178 rss_include_diff = false
168 179
169 180 ## gist URL alias, used to create nicer urls for gist. This should be an
170 181 ## url that does rewrites to _admin/gists/<gistid>.
171 182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
172 183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
173 184 gist_alias_url =
174 185
175 186 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
176 187 ## used for access.
177 188 ## Adding ?auth_token = <token> to the url authenticates this request as if it
178 189 ## came from the the logged in user who own this authentication token.
179 190 ##
180 191 ## Syntax is <ControllerClass>:<function_pattern>.
181 192 ## To enable access to raw_files put `FilesController:raw`.
182 193 ## To enable access to patches add `ChangesetController:changeset_patch`.
183 194 ## The list should be "," separated and on a single line.
184 195 ##
185 196 ## Recommended controllers to enable:
186 197 # ChangesetController:changeset_patch,
187 198 # ChangesetController:changeset_raw,
188 199 # FilesController:raw,
189 200 # FilesController:archivefile,
190 201 # GistsController:*,
191 202 api_access_controllers_whitelist =
192 203
193 204 ## default encoding used to convert from and to unicode
194 205 ## can be also a comma separated list of encoding in case of mixed encodings
195 206 default_encoding = UTF-8
196 207
197 208 ## instance-id prefix
198 209 ## a prefix key for this instance used for cache invalidation when running
199 210 ## multiple instances of rhodecode, make sure it's globally unique for
200 211 ## all running rhodecode instances. Leave empty if you don't use it
201 212 instance_id =
202 213
203 214 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
204 215 ## of an authentication plugin also if it is disabled by it's settings.
205 216 ## This could be useful if you are unable to log in to the system due to broken
206 217 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
207 218 ## module to log in again and fix the settings.
208 219 ##
209 220 ## Available builtin plugin IDs (hash is part of the ID):
210 221 ## egg:rhodecode-enterprise-ce#rhodecode
211 222 ## egg:rhodecode-enterprise-ce#pam
212 223 ## egg:rhodecode-enterprise-ce#ldap
213 224 ## egg:rhodecode-enterprise-ce#jasig_cas
214 225 ## egg:rhodecode-enterprise-ce#headers
215 226 ## egg:rhodecode-enterprise-ce#crowd
216 227 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
217 228
218 229 ## alternative return HTTP header for failed authentication. Default HTTP
219 230 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
220 231 ## handling that causing a series of failed authentication calls.
221 232 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
222 233 ## This will be served instead of default 401 on bad authnetication
223 234 auth_ret_code =
224 235
225 236 ## use special detection method when serving auth_ret_code, instead of serving
226 237 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
227 238 ## and then serve auth_ret_code to clients
228 239 auth_ret_code_detection = false
229 240
230 241 ## locking return code. When repository is locked return this HTTP code. 2XX
231 242 ## codes don't break the transactions while 4XX codes do
232 243 lock_ret_code = 423
233 244
234 245 ## allows to change the repository location in settings page
235 246 allow_repo_location_change = true
236 247
237 248 ## allows to setup custom hooks in settings page
238 249 allow_custom_hooks_settings = true
239 250
240 251 ## generated license token, goto license page in RhodeCode settings to obtain
241 252 ## new token
242 253 license_token =
243 254
244 255 ## supervisor connection uri, for managing supervisor and logs.
245 256 supervisor.uri =
246 257 ## supervisord group name/id we only want this RC instance to handle
247 258 supervisor.group_id = dev
248 259
249 260 ## Display extended labs settings
250 261 labs_settings_active = true
251 262
252 263 ####################################
253 264 ### CELERY CONFIG ####
254 265 ####################################
255 266 use_celery = false
256 267 broker.host = localhost
257 268 broker.vhost = rabbitmqhost
258 269 broker.port = 5672
259 270 broker.user = rabbitmq
260 271 broker.password = qweqwe
261 272
262 273 celery.imports = rhodecode.lib.celerylib.tasks
263 274
264 275 celery.result.backend = amqp
265 276 celery.result.dburi = amqp://
266 277 celery.result.serialier = json
267 278
268 279 #celery.send.task.error.emails = true
269 280 #celery.amqp.task.result.expires = 18000
270 281
271 282 celeryd.concurrency = 2
272 283 #celeryd.log.file = celeryd.log
273 284 celeryd.log.level = debug
274 285 celeryd.max.tasks.per.child = 1
275 286
276 287 ## tasks will never be sent to the queue, but executed locally instead.
277 288 celery.always.eager = false
278 289
279 290 ####################################
280 291 ### BEAKER CACHE ####
281 292 ####################################
282 293 # default cache dir for templates. Putting this into a ramdisk
283 294 ## can boost performance, eg. %(here)s/data_ramdisk
284 295 cache_dir = %(here)s/data
285 296
286 297 ## locking and default file storage for Beaker. Putting this into a ramdisk
287 298 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
288 299 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
289 300 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
290 301
291 302 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
292 303
293 304 beaker.cache.super_short_term.type = memory
294 305 beaker.cache.super_short_term.expire = 10
295 306 beaker.cache.super_short_term.key_length = 256
296 307
297 308 beaker.cache.short_term.type = memory
298 309 beaker.cache.short_term.expire = 60
299 310 beaker.cache.short_term.key_length = 256
300 311
301 312 beaker.cache.long_term.type = memory
302 313 beaker.cache.long_term.expire = 36000
303 314 beaker.cache.long_term.key_length = 256
304 315
305 316 beaker.cache.sql_cache_short.type = memory
306 317 beaker.cache.sql_cache_short.expire = 10
307 318 beaker.cache.sql_cache_short.key_length = 256
308 319
309 320 # default is memory cache, configure only if required
310 321 # using multi-node or multi-worker setup
311 322 #beaker.cache.auth_plugins.type = ext:database
312 323 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
313 324 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
314 325 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
315 326 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
316 327 #beaker.cache.auth_plugins.sa.pool_size = 10
317 328 #beaker.cache.auth_plugins.sa.max_overflow = 0
318 329
319 330 beaker.cache.repo_cache_long.type = memorylru_base
320 331 beaker.cache.repo_cache_long.max_items = 4096
321 332 beaker.cache.repo_cache_long.expire = 2592000
322 333
323 334 # default is memorylru_base cache, configure only if required
324 335 # using multi-node or multi-worker setup
325 336 #beaker.cache.repo_cache_long.type = ext:memcached
326 337 #beaker.cache.repo_cache_long.url = localhost:11211
327 338 #beaker.cache.repo_cache_long.expire = 1209600
328 339 #beaker.cache.repo_cache_long.key_length = 256
329 340
330 341 ####################################
331 342 ### BEAKER SESSION ####
332 343 ####################################
333 344
334 345 ## .session.type is type of storage options for the session, current allowed
335 346 ## types are file, ext:memcached, ext:database, and memory (default).
336 347 beaker.session.type = file
337 348 beaker.session.data_dir = %(here)s/data/sessions/data
338 349
339 350 ## db based session, fast, and allows easy management over logged in users ##
340 351 #beaker.session.type = ext:database
341 352 #beaker.session.table_name = db_session
342 353 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
343 354 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
344 355 #beaker.session.sa.pool_recycle = 3600
345 356 #beaker.session.sa.echo = false
346 357
347 358 beaker.session.key = rhodecode
348 359 beaker.session.secret = develop-rc-uytcxaz
349 360 beaker.session.lock_dir = %(here)s/data/sessions/lock
350 361
351 362 ## Secure encrypted cookie. Requires AES and AES python libraries
352 363 ## you must disable beaker.session.secret to use this
353 364 #beaker.session.encrypt_key = <key_for_encryption>
354 365 #beaker.session.validate_key = <validation_key>
355 366
356 367 ## sets session as invalid(also logging out user) if it haven not been
357 368 ## accessed for given amount of time in seconds
358 369 beaker.session.timeout = 2592000
359 370 beaker.session.httponly = true
360 371 #beaker.session.cookie_path = /<your-prefix>
361 372
362 373 ## uncomment for https secure cookie
363 374 beaker.session.secure = false
364 375
365 376 ## auto save the session to not to use .save()
366 377 beaker.session.auto = false
367 378
368 379 ## default cookie expiration time in seconds, set to `true` to set expire
369 380 ## at browser close
370 381 #beaker.session.cookie_expires = 3600
371 382
372 383 ###################################
373 384 ## SEARCH INDEXING CONFIGURATION ##
374 385 ###################################
375 386 ## Full text search indexer is available in rhodecode-tools under
376 387 ## `rhodecode-tools index` command
377 388
378 389 # WHOOSH Backend, doesn't require additional services to run
379 390 # it works good with few dozen repos
380 391 search.module = rhodecode.lib.index.whoosh
381 392 search.location = %(here)s/data/index
382 393
383
384 394 ###################################
385 395 ## APPENLIGHT CONFIG ##
386 396 ###################################
387 397
388 398 ## Appenlight is tailored to work with RhodeCode, see
389 399 ## http://appenlight.com for details how to obtain an account
390 400
391 401 ## appenlight integration enabled
392 402 appenlight = false
393 403
394 404 appenlight.server_url = https://api.appenlight.com
395 405 appenlight.api_key = YOUR_API_KEY
396 406 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
397 407
398 408 # used for JS client
399 409 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
400 410
401 411 ## TWEAK AMOUNT OF INFO SENT HERE
402 412
403 413 ## enables 404 error logging (default False)
404 414 appenlight.report_404 = false
405 415
406 416 ## time in seconds after request is considered being slow (default 1)
407 417 appenlight.slow_request_time = 1
408 418
409 419 ## record slow requests in application
410 420 ## (needs to be enabled for slow datastore recording and time tracking)
411 421 appenlight.slow_requests = true
412 422
413 423 ## enable hooking to application loggers
414 424 appenlight.logging = true
415 425
416 426 ## minimum log level for log capture
417 427 appenlight.logging.level = WARNING
418 428
419 429 ## send logs only from erroneous/slow requests
420 430 ## (saves API quota for intensive logging)
421 431 appenlight.logging_on_error = false
422 432
423 433 ## list of additonal keywords that should be grabbed from environ object
424 434 ## can be string with comma separated list of words in lowercase
425 435 ## (by default client will always send following info:
426 436 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
427 437 ## start with HTTP* this list be extended with additional keywords here
428 438 appenlight.environ_keys_whitelist =
429 439
430 440 ## list of keywords that should be blanked from request object
431 441 ## can be string with comma separated list of words in lowercase
432 442 ## (by default client will always blank keys that contain following words
433 443 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
434 444 ## this list be extended with additional keywords set here
435 445 appenlight.request_keys_blacklist =
436 446
437 447 ## list of namespaces that should be ignores when gathering log entries
438 448 ## can be string with comma separated list of namespaces
439 449 ## (by default the client ignores own entries: appenlight_client.client)
440 450 appenlight.log_namespace_blacklist =
441 451
442 452
443 453 ################################################################################
444 454 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
445 455 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
446 456 ## execute malicious code after an exception is raised. ##
447 457 ################################################################################
448 458 #set debug = false
449 459
450 460
451 461 ##############
452 462 ## STYLING ##
453 463 ##############
454 464 debug_style = true
455 465
456 466 #########################################################
457 467 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
458 468 #########################################################
459 469 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
460 470 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
461 471 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
462 472
463 473 # see sqlalchemy docs for other advanced settings
464 474
465 475 ## print the sql statements to output
466 476 sqlalchemy.db1.echo = false
467 477 ## recycle the connections after this ammount of seconds
468 478 sqlalchemy.db1.pool_recycle = 3600
469 479 sqlalchemy.db1.convert_unicode = true
470 480
471 481 ## the number of connections to keep open inside the connection pool.
472 482 ## 0 indicates no limit
473 483 #sqlalchemy.db1.pool_size = 5
474 484
475 485 ## the number of connections to allow in connection pool "overflow", that is
476 486 ## connections that can be opened above and beyond the pool_size setting,
477 487 ## which defaults to five.
478 488 #sqlalchemy.db1.max_overflow = 10
479 489
480 490
481 491 ##################
482 492 ### VCS CONFIG ###
483 493 ##################
484 494 vcs.server.enable = true
485 495 vcs.server = localhost:9900
486 496
487 497 ## Web server connectivity protocol, responsible for web based VCS operatations
488 498 ## Available protocols are:
489 499 ## `pyro4` - using pyro4 server
490 500 ## `http` - using http-rpc backend
491 501 #vcs.server.protocol = http
492 502
493 503 ## Push/Pull operations protocol, available options are:
494 504 ## `pyro4` - using pyro4 server
495 505 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
496 506 ## `vcsserver.scm_app` - internal app (EE only)
497 507 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
498 508
499 509 ## Push/Pull operations hooks protocol, available options are:
500 510 ## `pyro4` - using pyro4 server
501 511 ## `http` - using http-rpc backend
502 512 #vcs.hooks.protocol = http
503 513
504 514 vcs.server.log_level = debug
505 515 ## Start VCSServer with this instance as a subprocess, usefull for development
506 516 vcs.start_server = true
507 517 vcs.backends = hg, git, svn
508 518 vcs.connection_timeout = 3600
509 519 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
510 520 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
511 521 #vcs.svn.compatible_version = pre-1.8-compatible
512 522
513 523 ################################
514 524 ### LOGGING CONFIGURATION ####
515 525 ################################
516 526 [loggers]
517 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
527 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
518 528
519 529 [handlers]
520 530 keys = console, console_sql
521 531
522 532 [formatters]
523 533 keys = generic, color_formatter, color_formatter_sql
524 534
525 535 #############
526 536 ## LOGGERS ##
527 537 #############
528 538 [logger_root]
529 539 level = NOTSET
530 540 handlers = console
531 541
532 542 [logger_routes]
533 543 level = DEBUG
534 544 handlers =
535 545 qualname = routes.middleware
536 546 ## "level = DEBUG" logs the route matched and routing variables.
537 547 propagate = 1
538 548
539 549 [logger_beaker]
540 550 level = DEBUG
541 551 handlers =
542 552 qualname = beaker.container
543 553 propagate = 1
544 554
545 555 [logger_pyro4]
546 556 level = DEBUG
547 557 handlers =
548 558 qualname = Pyro4
549 559 propagate = 1
550 560
551 561 [logger_templates]
552 562 level = INFO
553 563 handlers =
554 564 qualname = pylons.templating
555 565 propagate = 1
556 566
557 567 [logger_rhodecode]
558 568 level = DEBUG
559 569 handlers =
560 570 qualname = rhodecode
561 571 propagate = 1
562 572
563 573 [logger_sqlalchemy]
564 574 level = INFO
565 575 handlers = console_sql
566 576 qualname = sqlalchemy.engine
567 577 propagate = 0
568 578
569 [logger_whoosh_indexer]
570 level = DEBUG
571 handlers =
572 qualname = whoosh_indexer
573 propagate = 1
574
575 579 ##############
576 580 ## HANDLERS ##
577 581 ##############
578 582
579 583 [handler_console]
580 584 class = StreamHandler
581 585 args = (sys.stderr,)
582 586 level = DEBUG
583 587 formatter = color_formatter
584 588
585 589 [handler_console_sql]
586 590 class = StreamHandler
587 591 args = (sys.stderr,)
588 592 level = DEBUG
589 593 formatter = color_formatter_sql
590 594
591 595 ################
592 596 ## FORMATTERS ##
593 597 ################
594 598
595 599 [formatter_generic]
596 600 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
597 601 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
598 602 datefmt = %Y-%m-%d %H:%M:%S
599 603
600 604 [formatter_color_formatter]
601 605 class = rhodecode.lib.logging_formatter.ColorFormatter
602 606 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
603 607 datefmt = %Y-%m-%d %H:%M:%S
604 608
605 609 [formatter_color_formatter_sql]
606 610 class = rhodecode.lib.logging_formatter.ColorFormatterSql
607 611 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
608 612 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,577 +1,581 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode Enterprise - configuration file #
4 4 # Built-in functions and variables #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 # #
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10 debug = true
11 pdebug = false
12 11 ################################################################################
13 12 ## Uncomment and replace with the email address which should receive ##
14 13 ## any error reports after an application crash ##
15 14 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 15 ################################################################################
17 16 #email_to = admin@localhost
18 17 #error_email_from = paste_error@localhost
19 18 #app_email_from = rhodecode-noreply@localhost
20 19 #error_message =
21 20 #email_prefix = [RhodeCode]
22 21
23 22 #smtp_server = mail.server.com
24 23 #smtp_username =
25 24 #smtp_password =
26 25 #smtp_port =
27 26 #smtp_use_tls = false
28 27 #smtp_use_ssl = true
29 28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
30 29 #smtp_auth =
31 30
32 31 [server:main]
33 32 ## COMMON ##
34 33 host = 127.0.0.1
35 34 port = 5000
36 35
37 36 ##################################
38 37 ## WAITRESS WSGI SERVER ##
39 38 ## Recommended for Development ##
40 39 ##################################
41 40 #use = egg:waitress#main
42 41 ## number of worker threads
43 42 #threads = 5
44 43 ## MAX BODY SIZE 100GB
45 44 #max_request_body_size = 107374182400
46 45 ## Use poll instead of select, fixes file descriptors limits problems.
47 46 ## May not work on old windows systems.
48 47 #asyncore_use_poll = true
49 48
50 49
51 50 ##########################
52 51 ## GUNICORN WSGI SERVER ##
53 52 ##########################
54 53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
55 54 use = egg:gunicorn#main
56 55 ## Sets the number of process workers. You must set `instance_id = *`
57 56 ## when this option is set to more than one worker, recommended
58 57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
59 58 ## The `instance_id = *` must be set in the [app:main] section below
60 59 workers = 2
61 60 ## number of threads for each of the worker, must be set to 1 for gevent
62 61 ## generally recommened to be at 1
63 62 #threads = 1
64 63 ## process name
65 64 proc_name = rhodecode
66 65 ## type of worker class, one of sync, gevent
67 66 ## recommended for bigger setup is using of of other than sync one
68 67 worker_class = sync
69 68 ## The maximum number of simultaneous clients. Valid only for Gevent
70 69 #worker_connections = 10
71 70 ## max number of requests that worker will handle before being gracefully
72 71 ## restarted, could prevent memory leaks
73 72 max_requests = 1000
74 73 max_requests_jitter = 30
75 74 ## amount of time a worker can spend with handling a request before it
76 75 ## gets killed and restarted. Set to 6hrs
77 76 timeout = 21600
78 77
79 78
80 79 ## prefix middleware for RhodeCode, disables force_https flag.
81 80 ## allows to set RhodeCode under a prefix in server.
82 81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
83 82 #[filter:proxy-prefix]
84 83 #use = egg:PasteDeploy#prefix
85 84 #prefix = /<your-prefix>
86 85
87 86 [app:main]
88 87 use = egg:rhodecode-enterprise-ce
89 88 ## enable proxy prefix middleware, defined below
90 89 #filter-with = proxy-prefix
91 90
91 ## encryption key used to encrypt social plugin tokens,
92 ## remote_urls with credentials etc, if not set it defaults to
93 ## `beaker.session.secret`
94 #rhodecode.encrypted_values.secret =
95
96 ## decryption strict mode (enabled by default). It controls if decryption raises
97 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
98 #rhodecode.encrypted_values.strict = false
99
92 100 full_stack = true
93 101
94 102 ## Serve static files via RhodeCode, disable to serve them via HTTP server
95 103 static_files = true
96 104
105 # autogenerate javascript routes file on startup
106 generate_js_files = false
107
97 108 ## Optional Languages
98 109 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
99 110 lang = en
100 111
101 112 ## perform a full repository scan on each server start, this should be
102 113 ## set to false after first startup, to allow faster server restarts.
103 114 startup.import_repos = false
104 115
105 116 ## Uncomment and set this path to use archive download cache.
106 117 ## Once enabled, generated archives will be cached at this location
107 118 ## and served from the cache during subsequent requests for the same archive of
108 119 ## the repository.
109 120 #archive_cache_dir = /tmp/tarballcache
110 121
111 122 ## change this to unique ID for security
112 123 app_instance_uuid = rc-production
113 124
114 125 ## cut off limit for large diffs (size in bytes)
115 126 cut_off_limit_diff = 1024000
116 127 cut_off_limit_file = 256000
117 128
118 129 ## use cache version of scm repo everywhere
119 130 vcs_full_cache = true
120 131
121 132 ## force https in RhodeCode, fixes https redirects, assumes it's always https
122 133 ## Normally this is controlled by proper http flags sent from http server
123 134 force_https = false
124 135
125 136 ## use Strict-Transport-Security headers
126 137 use_htsts = false
127 138
128 139 ## number of commits stats will parse on each iteration
129 140 commit_parse_limit = 25
130 141
131 142 ## git rev filter option, --all is the default filter, if you need to
132 143 ## hide all refs in changelog switch this to --branches --tags
133 144 git_rev_filter = --branches --tags
134 145
135 146 # Set to true if your repos are exposed using the dumb protocol
136 147 git_update_server_info = false
137 148
138 149 ## RSS/ATOM feed options
139 150 rss_cut_off_limit = 256000
140 151 rss_items_per_page = 10
141 152 rss_include_diff = false
142 153
143 154 ## gist URL alias, used to create nicer urls for gist. This should be an
144 155 ## url that does rewrites to _admin/gists/<gistid>.
145 156 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
146 157 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
147 158 gist_alias_url =
148 159
149 160 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
150 161 ## used for access.
151 162 ## Adding ?auth_token = <token> to the url authenticates this request as if it
152 163 ## came from the the logged in user who own this authentication token.
153 164 ##
154 165 ## Syntax is <ControllerClass>:<function_pattern>.
155 166 ## To enable access to raw_files put `FilesController:raw`.
156 167 ## To enable access to patches add `ChangesetController:changeset_patch`.
157 168 ## The list should be "," separated and on a single line.
158 169 ##
159 170 ## Recommended controllers to enable:
160 171 # ChangesetController:changeset_patch,
161 172 # ChangesetController:changeset_raw,
162 173 # FilesController:raw,
163 174 # FilesController:archivefile,
164 175 # GistsController:*,
165 176 api_access_controllers_whitelist =
166 177
167 178 ## default encoding used to convert from and to unicode
168 179 ## can be also a comma separated list of encoding in case of mixed encodings
169 180 default_encoding = UTF-8
170 181
171 182 ## instance-id prefix
172 183 ## a prefix key for this instance used for cache invalidation when running
173 184 ## multiple instances of rhodecode, make sure it's globally unique for
174 185 ## all running rhodecode instances. Leave empty if you don't use it
175 186 instance_id =
176 187
177 188 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
178 189 ## of an authentication plugin also if it is disabled by it's settings.
179 190 ## This could be useful if you are unable to log in to the system due to broken
180 191 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
181 192 ## module to log in again and fix the settings.
182 193 ##
183 194 ## Available builtin plugin IDs (hash is part of the ID):
184 195 ## egg:rhodecode-enterprise-ce#rhodecode
185 196 ## egg:rhodecode-enterprise-ce#pam
186 197 ## egg:rhodecode-enterprise-ce#ldap
187 198 ## egg:rhodecode-enterprise-ce#jasig_cas
188 199 ## egg:rhodecode-enterprise-ce#headers
189 200 ## egg:rhodecode-enterprise-ce#crowd
190 201 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
191 202
192 203 ## alternative return HTTP header for failed authentication. Default HTTP
193 204 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
194 205 ## handling that causing a series of failed authentication calls.
195 206 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
196 207 ## This will be served instead of default 401 on bad authnetication
197 208 auth_ret_code =
198 209
199 210 ## use special detection method when serving auth_ret_code, instead of serving
200 211 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
201 212 ## and then serve auth_ret_code to clients
202 213 auth_ret_code_detection = false
203 214
204 215 ## locking return code. When repository is locked return this HTTP code. 2XX
205 216 ## codes don't break the transactions while 4XX codes do
206 217 lock_ret_code = 423
207 218
208 219 ## allows to change the repository location in settings page
209 220 allow_repo_location_change = true
210 221
211 222 ## allows to setup custom hooks in settings page
212 223 allow_custom_hooks_settings = true
213 224
214 225 ## generated license token, goto license page in RhodeCode settings to obtain
215 226 ## new token
216 227 license_token =
217 228
218 229 ## supervisor connection uri, for managing supervisor and logs.
219 230 supervisor.uri =
220 231 ## supervisord group name/id we only want this RC instance to handle
221 232 supervisor.group_id = prod
222 233
223 234 ## Display extended labs settings
224 235 labs_settings_active = true
225 236
226 237 ####################################
227 238 ### CELERY CONFIG ####
228 239 ####################################
229 240 use_celery = false
230 241 broker.host = localhost
231 242 broker.vhost = rabbitmqhost
232 243 broker.port = 5672
233 244 broker.user = rabbitmq
234 245 broker.password = qweqwe
235 246
236 247 celery.imports = rhodecode.lib.celerylib.tasks
237 248
238 249 celery.result.backend = amqp
239 250 celery.result.dburi = amqp://
240 251 celery.result.serialier = json
241 252
242 253 #celery.send.task.error.emails = true
243 254 #celery.amqp.task.result.expires = 18000
244 255
245 256 celeryd.concurrency = 2
246 257 #celeryd.log.file = celeryd.log
247 258 celeryd.log.level = debug
248 259 celeryd.max.tasks.per.child = 1
249 260
250 261 ## tasks will never be sent to the queue, but executed locally instead.
251 262 celery.always.eager = false
252 263
253 264 ####################################
254 265 ### BEAKER CACHE ####
255 266 ####################################
256 267 # default cache dir for templates. Putting this into a ramdisk
257 268 ## can boost performance, eg. %(here)s/data_ramdisk
258 269 cache_dir = %(here)s/data
259 270
260 271 ## locking and default file storage for Beaker. Putting this into a ramdisk
261 272 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
262 273 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
263 274 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
264 275
265 276 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
266 277
267 278 beaker.cache.super_short_term.type = memory
268 279 beaker.cache.super_short_term.expire = 10
269 280 beaker.cache.super_short_term.key_length = 256
270 281
271 282 beaker.cache.short_term.type = memory
272 283 beaker.cache.short_term.expire = 60
273 284 beaker.cache.short_term.key_length = 256
274 285
275 286 beaker.cache.long_term.type = memory
276 287 beaker.cache.long_term.expire = 36000
277 288 beaker.cache.long_term.key_length = 256
278 289
279 290 beaker.cache.sql_cache_short.type = memory
280 291 beaker.cache.sql_cache_short.expire = 10
281 292 beaker.cache.sql_cache_short.key_length = 256
282 293
283 294 # default is memory cache, configure only if required
284 295 # using multi-node or multi-worker setup
285 296 #beaker.cache.auth_plugins.type = ext:database
286 297 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
287 298 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
288 299 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
289 300 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
290 301 #beaker.cache.auth_plugins.sa.pool_size = 10
291 302 #beaker.cache.auth_plugins.sa.max_overflow = 0
292 303
293 304 beaker.cache.repo_cache_long.type = memorylru_base
294 305 beaker.cache.repo_cache_long.max_items = 4096
295 306 beaker.cache.repo_cache_long.expire = 2592000
296 307
297 308 # default is memorylru_base cache, configure only if required
298 309 # using multi-node or multi-worker setup
299 310 #beaker.cache.repo_cache_long.type = ext:memcached
300 311 #beaker.cache.repo_cache_long.url = localhost:11211
301 312 #beaker.cache.repo_cache_long.expire = 1209600
302 313 #beaker.cache.repo_cache_long.key_length = 256
303 314
304 315 ####################################
305 316 ### BEAKER SESSION ####
306 317 ####################################
307 318
308 319 ## .session.type is type of storage options for the session, current allowed
309 320 ## types are file, ext:memcached, ext:database, and memory (default).
310 321 beaker.session.type = file
311 322 beaker.session.data_dir = %(here)s/data/sessions/data
312 323
313 324 ## db based session, fast, and allows easy management over logged in users ##
314 325 #beaker.session.type = ext:database
315 326 #beaker.session.table_name = db_session
316 327 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
317 328 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
318 329 #beaker.session.sa.pool_recycle = 3600
319 330 #beaker.session.sa.echo = false
320 331
321 332 beaker.session.key = rhodecode
322 333 beaker.session.secret = production-rc-uytcxaz
323 334 beaker.session.lock_dir = %(here)s/data/sessions/lock
324 335
325 336 ## Secure encrypted cookie. Requires AES and AES python libraries
326 337 ## you must disable beaker.session.secret to use this
327 338 #beaker.session.encrypt_key = <key_for_encryption>
328 339 #beaker.session.validate_key = <validation_key>
329 340
330 341 ## sets session as invalid(also logging out user) if it haven not been
331 342 ## accessed for given amount of time in seconds
332 343 beaker.session.timeout = 2592000
333 344 beaker.session.httponly = true
334 345 #beaker.session.cookie_path = /<your-prefix>
335 346
336 347 ## uncomment for https secure cookie
337 348 beaker.session.secure = false
338 349
339 350 ## auto save the session to not to use .save()
340 351 beaker.session.auto = false
341 352
342 353 ## default cookie expiration time in seconds, set to `true` to set expire
343 354 ## at browser close
344 355 #beaker.session.cookie_expires = 3600
345 356
346 357 ###################################
347 358 ## SEARCH INDEXING CONFIGURATION ##
348 359 ###################################
349 360 ## Full text search indexer is available in rhodecode-tools under
350 361 ## `rhodecode-tools index` command
351 362
352 363 # WHOOSH Backend, doesn't require additional services to run
353 364 # it works good with few dozen repos
354 365 search.module = rhodecode.lib.index.whoosh
355 366 search.location = %(here)s/data/index
356 367
357
358 368 ###################################
359 369 ## APPENLIGHT CONFIG ##
360 370 ###################################
361 371
362 372 ## Appenlight is tailored to work with RhodeCode, see
363 373 ## http://appenlight.com for details how to obtain an account
364 374
365 375 ## appenlight integration enabled
366 376 appenlight = false
367 377
368 378 appenlight.server_url = https://api.appenlight.com
369 379 appenlight.api_key = YOUR_API_KEY
370 380 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
371 381
372 382 # used for JS client
373 383 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
374 384
375 385 ## TWEAK AMOUNT OF INFO SENT HERE
376 386
377 387 ## enables 404 error logging (default False)
378 388 appenlight.report_404 = false
379 389
380 390 ## time in seconds after request is considered being slow (default 1)
381 391 appenlight.slow_request_time = 1
382 392
383 393 ## record slow requests in application
384 394 ## (needs to be enabled for slow datastore recording and time tracking)
385 395 appenlight.slow_requests = true
386 396
387 397 ## enable hooking to application loggers
388 398 appenlight.logging = true
389 399
390 400 ## minimum log level for log capture
391 401 appenlight.logging.level = WARNING
392 402
393 403 ## send logs only from erroneous/slow requests
394 404 ## (saves API quota for intensive logging)
395 405 appenlight.logging_on_error = false
396 406
397 407 ## list of additonal keywords that should be grabbed from environ object
398 408 ## can be string with comma separated list of words in lowercase
399 409 ## (by default client will always send following info:
400 410 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
401 411 ## start with HTTP* this list be extended with additional keywords here
402 412 appenlight.environ_keys_whitelist =
403 413
404 414 ## list of keywords that should be blanked from request object
405 415 ## can be string with comma separated list of words in lowercase
406 416 ## (by default client will always blank keys that contain following words
407 417 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
408 418 ## this list be extended with additional keywords set here
409 419 appenlight.request_keys_blacklist =
410 420
411 421 ## list of namespaces that should be ignores when gathering log entries
412 422 ## can be string with comma separated list of namespaces
413 423 ## (by default the client ignores own entries: appenlight_client.client)
414 424 appenlight.log_namespace_blacklist =
415 425
416 426
417 427 ################################################################################
418 428 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
419 429 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
420 430 ## execute malicious code after an exception is raised. ##
421 431 ################################################################################
422 432 set debug = false
423 433
424 434
425 435 #########################################################
426 436 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
427 437 #########################################################
428 438 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
429 439 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
430 440 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
431 441
432 442 # see sqlalchemy docs for other advanced settings
433 443
434 444 ## print the sql statements to output
435 445 sqlalchemy.db1.echo = false
436 446 ## recycle the connections after this ammount of seconds
437 447 sqlalchemy.db1.pool_recycle = 3600
438 448 sqlalchemy.db1.convert_unicode = true
439 449
440 450 ## the number of connections to keep open inside the connection pool.
441 451 ## 0 indicates no limit
442 452 #sqlalchemy.db1.pool_size = 5
443 453
444 454 ## the number of connections to allow in connection pool "overflow", that is
445 455 ## connections that can be opened above and beyond the pool_size setting,
446 456 ## which defaults to five.
447 457 #sqlalchemy.db1.max_overflow = 10
448 458
449 459
450 460 ##################
451 461 ### VCS CONFIG ###
452 462 ##################
453 463 vcs.server.enable = true
454 464 vcs.server = localhost:9900
455 465
456 466 ## Web server connectivity protocol, responsible for web based VCS operatations
457 467 ## Available protocols are:
458 468 ## `pyro4` - using pyro4 server
459 469 ## `http` - using http-rpc backend
460 470 #vcs.server.protocol = http
461 471
462 472 ## Push/Pull operations protocol, available options are:
463 473 ## `pyro4` - using pyro4 server
464 474 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
465 475 ## `vcsserver.scm_app` - internal app (EE only)
466 476 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
467 477
468 478 ## Push/Pull operations hooks protocol, available options are:
469 479 ## `pyro4` - using pyro4 server
470 480 ## `http` - using http-rpc backend
471 481 #vcs.hooks.protocol = http
472 482
473 483 vcs.server.log_level = info
474 484 ## Start VCSServer with this instance as a subprocess, usefull for development
475 485 vcs.start_server = false
476 486 vcs.backends = hg, git, svn
477 487 vcs.connection_timeout = 3600
478 488 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
479 489 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
480 490 #vcs.svn.compatible_version = pre-1.8-compatible
481 491
482 492 ################################
483 493 ### LOGGING CONFIGURATION ####
484 494 ################################
485 495 [loggers]
486 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
496 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
487 497
488 498 [handlers]
489 499 keys = console, console_sql
490 500
491 501 [formatters]
492 502 keys = generic, color_formatter, color_formatter_sql
493 503
494 504 #############
495 505 ## LOGGERS ##
496 506 #############
497 507 [logger_root]
498 508 level = NOTSET
499 509 handlers = console
500 510
501 511 [logger_routes]
502 512 level = DEBUG
503 513 handlers =
504 514 qualname = routes.middleware
505 515 ## "level = DEBUG" logs the route matched and routing variables.
506 516 propagate = 1
507 517
508 518 [logger_beaker]
509 519 level = DEBUG
510 520 handlers =
511 521 qualname = beaker.container
512 522 propagate = 1
513 523
514 524 [logger_pyro4]
515 525 level = DEBUG
516 526 handlers =
517 527 qualname = Pyro4
518 528 propagate = 1
519 529
520 530 [logger_templates]
521 531 level = INFO
522 532 handlers =
523 533 qualname = pylons.templating
524 534 propagate = 1
525 535
526 536 [logger_rhodecode]
527 537 level = DEBUG
528 538 handlers =
529 539 qualname = rhodecode
530 540 propagate = 1
531 541
532 542 [logger_sqlalchemy]
533 543 level = INFO
534 544 handlers = console_sql
535 545 qualname = sqlalchemy.engine
536 546 propagate = 0
537 547
538 [logger_whoosh_indexer]
539 level = DEBUG
540 handlers =
541 qualname = whoosh_indexer
542 propagate = 1
543
544 548 ##############
545 549 ## HANDLERS ##
546 550 ##############
547 551
548 552 [handler_console]
549 553 class = StreamHandler
550 554 args = (sys.stderr,)
551 555 level = INFO
552 556 formatter = generic
553 557
554 558 [handler_console_sql]
555 559 class = StreamHandler
556 560 args = (sys.stderr,)
557 561 level = WARN
558 562 formatter = generic
559 563
560 564 ################
561 565 ## FORMATTERS ##
562 566 ################
563 567
564 568 [formatter_generic]
565 569 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
566 570 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
567 571 datefmt = %Y-%m-%d %H:%M:%S
568 572
569 573 [formatter_color_formatter]
570 574 class = rhodecode.lib.logging_formatter.ColorFormatter
571 575 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
572 576 datefmt = %Y-%m-%d %H:%M:%S
573 577
574 578 [formatter_color_formatter_sql]
575 579 class = rhodecode.lib.logging_formatter.ColorFormatterSql
576 580 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
577 581 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,219 +1,227 b''
1 1 # Nix environment for the community edition
2 2 #
3 3 # This shall be as lean as possible, just producing the Enterprise
4 4 # derivation. For advanced tweaks to pimp up the development environment we use
5 5 # "shell.nix" so that it does not have to clutter this file.
6 6
7 7 { pkgs ? (import <nixpkgs> {})
8 8 , pythonPackages ? "python27Packages"
9 9 , pythonExternalOverrides ? self: super: {}
10 10 , doCheck ? true
11 11 }:
12 12
13 13 let pkgs_ = pkgs; in
14 14
15 15 let
16 16 pkgs = pkgs_.overridePackages (self: super: {
17 17 # Override subversion derivation to
18 18 # - activate python bindings
19 19 # - set version to 1.8
20 20 subversion = super.subversion18.override {
21 21 httpSupport = true;
22 22 pythonBindings = true;
23 23 python = self.python27Packages.python;
24 24 };
25 25 });
26 26
27 27 inherit (pkgs.lib) fix extends;
28 28
29 29 basePythonPackages = with builtins; if isAttrs pythonPackages
30 30 then pythonPackages
31 31 else getAttr pythonPackages pkgs;
32 32
33 33 elem = builtins.elem;
34 34 basename = path: with pkgs.lib; last (splitString "/" path);
35 35 startsWith = prefix: full: let
36 36 actualPrefix = builtins.substring 0 (builtins.stringLength prefix) full;
37 37 in actualPrefix == prefix;
38 38
39 39 src-filter = path: type: with pkgs.lib;
40 40 let
41 41 ext = last (splitString "." path);
42 42 in
43 43 !elem (basename path) [
44 44 ".git" ".hg" "__pycache__" ".eggs" "node_modules"
45 45 "build" "data" "tmp"] &&
46 46 !elem ext ["egg-info" "pyc"] &&
47 47 !startsWith "result" path;
48 48
49 sources = pkgs.config.rc.sources or {};
49 50 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
50 51
51 52 # Load the generated node packages
52 53 nodePackages = pkgs.callPackage "${pkgs.path}/pkgs/top-level/node-packages.nix" rec {
53 54 self = nodePackages;
54 55 generated = pkgs.callPackage ./pkgs/node-packages.nix { inherit self; };
55 56 };
56 57
57 58 # TODO: Should be taken automatically out of the generates packages.
58 59 # apps.nix has one solution for this, although I'd prefer to have the deps
59 60 # from package.json mapped in here.
60 61 nodeDependencies = with nodePackages; [
61 62 grunt
62 63 grunt-contrib-concat
63 64 grunt-contrib-jshint
64 65 grunt-contrib-less
65 66 grunt-contrib-watch
66 67 jshint
67 68 ];
68 69
69 70 pythonGeneratedPackages = self: basePythonPackages.override (a: {
70 71 inherit self;
71 72 })
72 73 // (scopedImport {
73 74 self = self;
74 75 super = basePythonPackages;
75 76 inherit pkgs;
76 77 inherit (pkgs) fetchurl fetchgit;
77 78 } ./pkgs/python-packages.nix);
78 79
79 80 pythonOverrides = import ./pkgs/python-packages-overrides.nix {
80 81 inherit
81 82 basePythonPackages
82 83 pkgs;
83 84 };
84 85
85 86 pythonLocalOverrides = self: super: {
86 87 rhodecode-enterprise-ce =
87 88 let
88 89 version = builtins.readFile ./rhodecode/VERSION;
89 90 linkNodeModules = ''
90 91 echo "Link node packages"
91 92 # TODO: check if this adds stuff as a dependency, closure size
92 93 rm -fr node_modules
93 94 mkdir -p node_modules
94 95 ${pkgs.lib.concatMapStrings (dep: ''
95 96 ln -sfv ${dep}/lib/node_modules/${dep.pkgName} node_modules/
96 97 '') nodeDependencies}
97 98 echo "DONE: Link node packages"
98 99 '';
99 100 in super.rhodecode-enterprise-ce.override (attrs: {
100 101
101 inherit doCheck;
102 inherit
103 doCheck
104 version;
102 105 name = "rhodecode-enterprise-ce-${version}";
103 version = version;
106 releaseName = "RhodeCodeEnterpriseCE-${version}";
104 107 src = rhodecode-enterprise-ce-src;
105 108
106 109 buildInputs =
107 110 attrs.buildInputs ++
108 111 (with self; [
109 112 pkgs.nodePackages.grunt-cli
110 113 pkgs.subversion
111 114 pytest-catchlog
112 rc_testdata
115 rhodecode-testdata
113 116 ]);
114 117
115 118 propagatedBuildInputs = attrs.propagatedBuildInputs ++ (with self; [
116 119 rhodecode-tools
117 120 ]);
118 121
119 122 # TODO: johbo: Make a nicer way to expose the parts. Maybe
120 123 # pkgs/default.nix?
121 124 passthru = {
122 125 inherit
123 126 pythonLocalOverrides
124 127 myPythonPackagesUnfix;
125 128 pythonPackages = self;
126 129 };
127 130
128 131 LC_ALL = "en_US.UTF-8";
129 132 LOCALE_ARCHIVE =
130 133 if pkgs.stdenv ? glibc
131 134 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
132 135 else "";
133 136
134 137 # Somewhat snappier setup of the development environment
135 138 # TODO: move into shell.nix
136 139 # TODO: think of supporting a stable path again, so that multiple shells
137 140 # can share it.
138 141 shellHook = ''
139 142 tmp_path=$(mktemp -d)
140 143 export PATH="$tmp_path/bin:$PATH"
141 144 export PYTHONPATH="$tmp_path/${self.python.sitePackages}:$PYTHONPATH"
142 145 mkdir -p $tmp_path/${self.python.sitePackages}
143 146 python setup.py develop --prefix $tmp_path --allow-hosts ""
144 147 '' + linkNodeModules;
145 148
146 149 preCheck = ''
147 150 export PATH="$out/bin:$PATH"
148 151 '';
149 152
150 153 postCheck = ''
151 154 rm -rf $out/lib/${self.python.libPrefix}/site-packages/pytest_pylons
152 155 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
153 156 '';
154 157
155 158 preBuild = linkNodeModules + ''
156 159 grunt
157 160 rm -fr node_modules
158 161 '';
159 162
160 163 postInstall = ''
161 164 # python based programs need to be wrapped
162 165 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
163 166 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
164 167 ln -s ${self.PasteScript}/bin/paster $out/bin/
165 168 ln -s ${self.pyramid}/bin/* $out/bin/ #*/
166 169
167 170 # rhodecode-tools
168 171 # TODO: johbo: re-think this. Do the tools import anything from enterprise?
169 172 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
170 173
171 174 # note that condition should be restricted when adding further tools
172 175 for file in $out/bin/*; do #*/
173 176 wrapProgram $file \
174 177 --prefix PYTHONPATH : $PYTHONPATH \
175 178 --prefix PATH : $PATH \
176 179 --set PYTHONHASHSEED random
177 180 done
178 181
179 182 mkdir $out/etc
180 183 cp configs/production.ini $out/etc
181 184
182 185 echo "Writing meta information for rccontrol to nix-support/rccontrol"
183 186 mkdir -p $out/nix-support/rccontrol
184 187 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
185 188 echo "DONE: Meta information for rccontrol written"
186 189
187 190 # TODO: johbo: Make part of ac-tests
188 191 if [ ! -f rhodecode/public/js/scripts.js ]; then
189 192 echo "Missing scripts.js"
190 193 exit 1
191 194 fi
192 195 if [ ! -f rhodecode/public/css/style.css ]; then
193 196 echo "Missing style.css"
194 197 exit 1
195 198 fi
196 199 '';
197 200
198 201 });
199 202
200 rc_testdata = self.buildPythonPackage rec {
201 name = "rc_testdata-0.7.0";
202 src = pkgs.fetchhg {
203 url = "https://code.rhodecode.com/upstream/rc_testdata";
204 rev = "v0.7.0";
205 sha256 = "0w3z0zn8lagr707v67lgys23sl6pbi4xg7pfvdbw58h3q384h6rx";
206 };
203 rhodecode-testdata = import "${rhodecode-testdata-src}/default.nix" {
204 inherit
205 doCheck
206 pkgs
207 pythonPackages;
207 208 };
208 209
209 210 };
210 211
212 rhodecode-testdata-src = sources.rhodecode-testdata or (
213 pkgs.fetchhg {
214 url = "https://code.rhodecode.com/upstream/rc_testdata";
215 rev = "v0.8.0";
216 sha256 = "0hy1ba134rq2f9si85yx7j4qhc9ky0hjzdk553s3q026i7km809m";
217 });
218
211 219 # Apply all overrides and fix the final package set
212 220 myPythonPackagesUnfix =
213 221 (extends pythonExternalOverrides
214 222 (extends pythonLocalOverrides
215 223 (extends pythonOverrides
216 224 pythonGeneratedPackages)));
217 225 myPythonPackages = (fix myPythonPackagesUnfix);
218 226
219 227 in myPythonPackages.rhodecode-enterprise-ce
@@ -1,136 +1,130 b''
1 1 .. _debug-mode:
2 2
3 3 Enabling Debug Mode
4 4 -------------------
5 5
6 6 To enable debug mode on a |RCE| instance you need to set the debug property
7 7 in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. To
8 8 do this, use the following steps
9 9
10 10 1. Open the file and set the ``debug`` line to ``true``
11 11 2. Restart you instance using the ``rccontrol restart`` command,
12 12 see the following example:
13 13
14 14 You can also set the log level, the follow are the valid options;
15 15 ``debug``, ``info``, ``warning``, or ``fatal``.
16 16
17 17 .. code-block:: ini
18 18
19 19 [DEFAULT]
20 20 debug = true
21 21 pdebug = false
22 22
23 23 .. code-block:: bash
24 24
25 25 # Restart your instance
26 26 $ rccontrol restart enterprise-1
27 27 Instance "enterprise-1" successfully stopped.
28 28 Instance "enterprise-1" successfully started.
29 29
30 30 Debug and Logging Configuration
31 31 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
32 32
33 33 Further debugging and logging settings can also be set in the
34 34 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
35 35
36 36 In the logging section, the various packages that run with |RCE| can have
37 37 different debug levels set. If you want to increase the logging level change
38 38 ``level = DEBUG`` line to one of the valid options.
39 39
40 40 You also need to change the log level for handlers. See the example
41 41 ``##handler`` section below. The ``handler`` level takes the same options as
42 42 the ``debug`` level.
43 43
44 44 .. code-block:: ini
45 45
46 46 ################################
47 47 ### LOGGING CONFIGURATION ####
48 48 ################################
49 49 [loggers]
50 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
50 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
51 51
52 52 [handlers]
53 53 keys = console, console_sql, file, file_rotating
54 54
55 55 [formatters]
56 56 keys = generic, color_formatter, color_formatter_sql
57 57
58 58 #############
59 59 ## LOGGERS ##
60 60 #############
61 61 [logger_root]
62 62 level = NOTSET
63 63 handlers = console
64 64
65 65 [logger_routes]
66 66 level = DEBUG
67 67 handlers =
68 68 qualname = routes.middleware
69 69 ## "level = DEBUG" logs the route matched and routing variables.
70 70 propagate = 1
71 71
72 72 [logger_beaker]
73 73 level = DEBUG
74 74 handlers =
75 75 qualname = beaker.container
76 76 propagate = 1
77 77
78 78 [logger_pyro4]
79 79 level = DEBUG
80 80 handlers =
81 81 qualname = Pyro4
82 82 propagate = 1
83 83
84 84 [logger_templates]
85 85 level = INFO
86 86 handlers =
87 87 qualname = pylons.templating
88 88 propagate = 1
89 89
90 90 [logger_rhodecode]
91 91 level = DEBUG
92 92 handlers =
93 93 qualname = rhodecode
94 94 propagate = 1
95 95
96 96 [logger_sqlalchemy]
97 97 level = INFO
98 98 handlers = console_sql
99 99 qualname = sqlalchemy.engine
100 100 propagate = 0
101 101
102 [logger_whoosh_indexer]
103 level = DEBUG
104 handlers =
105 qualname = whoosh_indexer
106 propagate = 1
107
108 102 ##############
109 103 ## HANDLERS ##
110 104 ##############
111 105
112 106 [handler_console]
113 107 class = StreamHandler
114 108 args = (sys.stderr,)
115 109 level = INFO
116 110 formatter = generic
117 111
118 112 [handler_console_sql]
119 113 class = StreamHandler
120 114 args = (sys.stderr,)
121 115 level = WARN
122 116 formatter = generic
123 117
124 118 [handler_file]
125 119 class = FileHandler
126 120 args = ('rhodecode.log', 'a',)
127 121 level = INFO
128 122 formatter = generic
129 123
130 124 [handler_file_rotating]
131 125 class = logging.handlers.TimedRotatingFileHandler
132 126 # 'D', 5 - rotate every 5days
133 127 # you can set 'h', 'midnight'
134 128 args = ('rhodecode.log', 'D', 5, 10,)
135 129 level = INFO
136 130 formatter = generic
@@ -1,19 +1,20 b''
1 1 .. _contributing:
2 2
3 3 Contributing to RhodeCode
4 4 =========================
5 5
6 6
7 7
8 Welcome to contribution guides and development docs of RhodeCode.
8 Welcome to the contribution guides and development docs of RhodeCode.
9 9
10 10
11 11
12 12 .. toctree::
13 13 :maxdepth: 1
14 14
15 overview
15 16 testing/index
16 17 dev-setup
17 18 db-schema
18 19 dev-settings
19 20 api
@@ -1,52 +1,52 b''
1 1 =======================
2 2 DB Schema and Migration
3 3 =======================
4 4
5 To create or alter tables in the database it's necessary to change a couple of
5 To create or alter tables in the database, it's necessary to change a couple of
6 6 files, apart from configuring the settings pointing to the latest database
7 7 schema.
8 8
9 9
10 10 Database Model and ORM
11 11 ----------------------
12 12
13 13 On ``rhodecode.model.db`` you will find the database definition of all tables and
14 fields. Any fresh install database will be correctly created by the definitions
15 here. So, any change to this files will affect the tests without having to change
14 fields. Any freshly installed database will be correctly created by the definitions
15 here. So, any change to this file will affect the tests without having to change
16 16 any other file.
17 17
18 A second layer are the businness classes that are inside ``rhodecode.model``.
18 A second layer are the business classes inside ``rhodecode.model``.
19 19
20 20
21 21 Database Migration
22 22 ------------------
23 23
24 24 Three files play a role when creating database migrations:
25 25
26 26 * Database schema inside ``rhodecode.lib.dbmigrate``
27 27 * Database version inside ``rhodecode.lib.dbmigrate``
28 28 * Configuration ``__dbversion__`` at ``rhodecode.__init__``
29 29
30 30
31 31 Schema is a snapshot of the database version BEFORE the migration. So, it's
32 32 the initial state before any changes were added. The name convention is
33 the latest release version where the snapshot were created, and not the
33 the latest release version where the snapshot was created, and not the
34 34 target version of this code.
35 35
36 36 Version is the method that will define how to UPGRADE/DOWNGRADE the database.
37 37
38 38 ``rhodecode.__init__`` contains only a variable that defines up to which version of
39 39 the database will be used to upgrade. Eg.: ``__dbversion__ = 45``
40 40
41 41
42 42 For examples on how to create those files, please see the existing code.
43 43
44 44
45 45 Migration Command
46 46 ^^^^^^^^^^^^^^^^^
47 47
48 After you changed the database ORM and migration files, you can run::
48 After you've changed the database ORM and migration files, you can run::
49 49
50 50 paster upgrade-db <ini-file>
51 51
52 And the database will be upgraded up to the version defined in the ``__init__`` file. No newline at end of file
52 The database will be upgraded up to the version defined in the ``__init__`` file. No newline at end of file
@@ -1,46 +1,46 b''
1 1
2 2 ==========================
3 3 Settings for Development
4 4 ==========================
5 5
6 6
7 7 We have a few settings which are intended to be used only for development
8 8 purposes. This section contains an overview of them.
9 9
10 10
11 11
12 12 `debug_style`
13 13 =============
14 14
15 15 Enables the section "Style" in the application. This section provides an
16 overview of all components which are found in the frontend style of the
16 overview of all components which are found in the frontend of the
17 17 application.
18 18
19 19
20 20
21 21 `vcs.start_server`
22 22 ==================
23 23
24 24 Starts the server as a subprocess while the system comes up. Intended usage is
25 25 to ease development.
26 26
27 27
28 28
29 29 `[logging]`
30 30 ===========
31 31
32 Use this to configure loggig to your current needs. The documentation of
33 Python's `logging` module explains all details. The following snippets are
34 useful for day to day development work.
32 Use this to configure logging to your current needs. The documentation of
33 Python's `logging` module explains all of the details. The following snippets
34 are useful for day to day development work.
35 35
36 36
37 37 Mute SQL output
38 38 ---------------
39 39
40 40 They come out of the package `sqlalchemy.engine`::
41 41
42 42 [logger_sqlalchemy]
43 43 level = WARNING
44 44 handlers = console_sql
45 45 qualname = sqlalchemy.engine
46 46 propagate = 0
@@ -1,141 +1,144 b''
1 .. _dev-setup:
1 2
2 3 ===================
3 4 Development setup
4 5 ===================
5 6
6 7
7 8 RhodeCode Enterprise runs inside a Nix managed environment. This ensures build
8 9 environment dependencies are correctly declared and installed during setup.
9 10 It also enables atomic upgrades, rollbacks, and multiple instances of RhodeCode
10 Enterprise for efficient cluster management.
11 Enterprise running with isolation.
11 12
12 To set up RhodeCode Enterprise inside the Nix environment use the following steps:
13 To set up RhodeCode Enterprise inside the Nix environment, use the following steps:
13 14
14 15
15 16
16 17 Setup Nix Package Manager
17 18 -------------------------
18 19
19 To install the Nix Package Manager please run::
20 To install the Nix Package Manager, please run::
20 21
21 22 $ curl https://nixos.org/nix/install | sh
22 23
23 or go to https://nixos.org/nix/ and follow their installation instructions.
24 Once this is correctly set up on your system you should be able to use the
24 or go to https://nixos.org/nix/ and follow the installation instructions.
25 Once this is correctly set up on your system, you should be able to use the
25 26 following commands:
26 27
27 28 * `nix-env`
28 29
29 30 * `nix-shell`
30 31
31 32
32 33 .. tip::
33 34
34 35 Update your channels frequently by running ``nix-channel --upgrade``.
35 36
36 37
37 Switch nix to latest STABLE channel
38 -----------------------------------
38 Switch nix to the latest STABLE channel
39 ---------------------------------------
39 40
40 41 run::
41 42
42 43 nix-channel --add https://nixos.org/channels/nixos-16.03 nixpkgs
43 44
44 45 Followed by::
45 46
46 47 nix-channel --update
47 48
48 49
49 50 Clone the required repositories
50 51 -------------------------------
51 52
52 After Nix is set up, clone the RhodeCode Enterprise Community Edition, and
53 After Nix is set up, clone the RhodeCode Enterprise Community Edition and
53 54 RhodeCode VCSServer repositories into the same directory.
54 55 To do this, use the following example::
55 56
56 57 mkdir rhodecode-develop && cd rhodecode-develop
57 58 hg clone https://code.rhodecode.com/rhodecode-enterprise-ce
58 59 hg clone https://code.rhodecode.com/rhodecode-vcsserver
59 60
60 61 .. note::
61 62
62 If you cannot clone the repository, please request read permissions.
63 If you cannot clone the repository, please request read permissions
64 via support@rhodecode.com
63 65
64 66
65 67
66 68 Enter the Development Shell
67 69 ---------------------------
68 70
69 The final step is to start into the development shell. To do this run the
71 The final step is to start the development shell. To do this, run the
70 72 following command from inside the cloned repository::
71 73
72 74 cd ~/rhodecode-enterprise-ce
73 nix-shell --arg dev true
75 nix-shell
74 76
75 77 .. note::
76 78
77 79 On the first run, this will take a while to download and optionally compile
78 a few things. The next runs of it will be faster.
80 a few things. The following runs will be faster. The development shell works
81 fine on MacOS and Linux platforms.
79 82
80 83
81 84
82 85 Creating a Development Configuration
83 86 ------------------------------------
84 87
85 88 To create a development environment for RhodeCode Enterprise,
86 89 use the following steps:
87 90
88 91 1. Create a copy of `~/rhodecode-enterprise-ce/configs/development.ini`
89 92 2. Adjust the configuration settings to your needs
90 93
91 94 .. note::
92 95
93 It is recommended to call it `dev.ini`.
96 It is recommended to use the name `dev.ini`.
94 97
95 98
96 99 Setup the Development Database
97 100 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
98 101
99 To create a development database use the following example. This is a one
102 To create a development database, use the following example. This is a one
100 103 time operation::
101 104
102 105 paster setup-rhodecode dev.ini \
103 106 --user=admin --password=secret \
104 107 --email=admin@example.com \
105 108 --repos=~/my_dev_repos
106 109
107 110
108 111 Start the Development Server
109 112 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
110 113
111 114 When starting the development server, you should start the vcsserver as a
112 separate process. To do this use one of the following examples:
115 separate process. To do this, use one of the following examples:
113 116
114 117 1. Set the `start.vcs_server` flag in the ``dev.ini`` file to true. For example:
115 118
116 119 .. code-block:: python
117 120
118 121 ### VCS CONFIG ###
119 122 ##################
120 123 vcs.start_server = true
121 124 vcs.server = localhost:9900
122 125 vcs.server.log_level = debug
123 126
124 127 Then start the server using the following command: ``rcserver dev.ini``
125 128
126 129 2. Start the development server using the following example::
127 130
128 131 rcserver --with-vcsserver dev.ini
129 132
130 133 3. Start the development server in a different terminal using the following
131 134 example::
132 135
133 136 vcsserver
134 137
135 138
136 139 Run the Environment Tests
137 140 ^^^^^^^^^^^^^^^^^^^^^^^^^
138 141
139 Please make sure that the test are passing to verify that your environment is
140 set up correctly. More details about the tests are described in:
141 :file:`/docs/dev/testing`.
142 Please make sure that the tests are passing to verify that your environment is
143 set up correctly. RhodeCode uses py.test to run tests.
144 Please simply run ``make test`` to run the basic test suite.
@@ -1,28 +1,28 b''
1 1
2 2
3 3 ============================
4 4 Testing and Specifications
5 5 ============================
6 6
7 7
8 8 .. toctree::
9 9 :maxdepth: 2
10 10
11 11 unit-and-functional
12 12 spec-by-example
13 13 naming-conventions
14 14
15 15
16 16
17 17 Overview
18 18 ========
19 19
20 We have a quite big test suite inside of :file:`rhodecode/tests` which is a mix
20 We have a quite large test suite inside of :file:`rhodecode/tests` which is a mix
21 21 of unit tests and functional or integration tests. More details are in
22 22 :ref:`test-unit-and-functional`.
23 23
24 24
25 Apart from that we start to apply "Specification by Example" and maintain a
26 collection of such specifications together with an implementation so that it can
27 be validated in an automatic way. The files can be found in
25 Apart from that, we are starting to apply "Specification by Example" and maintain
26 a collection of such specifications together with an implementation so that it
27 can be validated in an automatic way. The files can be found in
28 28 :file:`acceptance_tests`. More details are in :ref:`test-spec-by-example`.
@@ -1,75 +1,75 b''
1 1
2 2 .. _test-spec-by-example:
3 3
4 4 ==========================
5 5 Specification by Example
6 6 ==========================
7 7
8 8
9 9 .. Avoid duplicating the quickstart instructions by importing the README
10 10 file.
11 11
12 12 .. include:: ../../../acceptance_tests/README.rst
13 13
14 14
15 15
16 16 Choices of technology and tools
17 17 ===============================
18 18
19 19
20 20 `nix` as runtime environment
21 21 ----------------------------
22 22
23 23 We settled to use the `nix` tools to provide us the needed environment for
24 24 running the tests.
25 25
26 26
27 27
28 28 `Gherkins` as specification language
29 29 ------------------------------------
30 30
31 31 To specify by example, we settled on Gherkins as the semi-formal specification
32 32 language.
33 33
34 34
35 35 `py.test` as a runner
36 36 ---------------------
37 37
38 38 After experimenting with `behave` and `py.test` our choice was `pytest-bdd`
39 39 because it allows us to use our existing knowledge about `py.test` and avoids
40 40 that we have to learn another tool.
41 41
42 42
43 43
44 44 Concepts
45 45 ========
46 46
47 47 The logic is structured around the design pattern of "page objects". The
48 48 documentation of `python-selemium` contains a few more details about this
49 49 pattern.
50 50
51 51
52 52
53 53 Page Objects
54 54 ------------
55 55
56 56 We introduce an abstraction class for every page which we have to interact with
57 57 in order to validate the specifications.
58 58
59 59 The implementation for the page objects is inside of the module
60 60 :mod:`page_objects`. The class :class:`page_objects.base.BasePage` should be
61 61 used as a base for all page object implementations.
62 62
63 63
64 64
65 65 Locators
66 66 --------
67 67
68 68 The specific information how to locate an element inside of the DOM tree of a
69 page is kept in a separate class. This class serves mainly as a data container,
69 page is kept in a separate class. This class serves mainly as a data container;
70 70 it shall not contain any logic.
71 71
72 72 The reason for keeping the locators separate is that we expect a frequent need
73 for change whenever we work on our templates. In such a case it is more
74 efficient to have all locators together and update them there instead of having
75 to find all locators inside of the logic of a page object.
73 for change whenever we work on our templates. In such a case, it is more
74 efficient to have all of thelocators together and update them there instead of
75 having to find every locator inside of the logic of a page object.
@@ -1,61 +1,61 b''
1 1
2 2 .. _test-unit-and-functional:
3 3
4 4 ===========================
5 5 Unit and Functional Tests
6 6 ===========================
7 7
8 8
9 9
10 10 py.test based test suite
11 11 ========================
12 12
13 13
14 14 The test suite is in the folder :file:`rhodecode/tests/` and should be run with
15 15 the test runner `py.test` inside of your `nix-shell` environment::
16 16
17 17 # In case you need the cythonized version
18 18 CYTHONIZE=1 python setup.py develop --prefix=$tmp_path
19 19
20 20 py.test rhodecode
21 21
22 22
23 23
24 24 py.test integration
25 25 -------------------
26 26
27 27 The integration with the test runner is based on the following three parts:
28 28
29 29 - `pytest_pylons` is a py.test plugin which does the integration with the
30 Pylons web framework. It sets up the Pylons environment based on a given ini
30 Pylons web framework. It sets up the Pylons environment based on the given ini
31 31 file.
32 32
33 33 Tests which depend on the Pylons environment to be set up must request the
34 34 fixture `pylonsapp`.
35 35
36 36 - :file:`rhodecode/tests/plugin.py` contains the integration of py.test with
37 37 RhodeCode Enterprise itself.
38 38
39 39 - :file:`conftest.py` plugins are used to provide a special integration for
40 40 certain groups of tests based on the directory location.
41 41
42 42
43 43
44 44 VCS backend selection
45 45 ---------------------
46 46
47 47 The py.test integration provides a parameter `--backends`. It will skip all
48 48 tests which are marked for other backends.
49 49
50 50 To run only Subversion tests::
51 51
52 52 py.test rhodecode --backends=svn
53 53
54 54
55 55
56 56 Frontend / Styling support
57 57 ==========================
58 58
59 59 All relevant style components have an example inside of the "Style" section
60 60 within the application. Enable the setting `debug_style` to make this section
61 61 visible in your local instance of the application.
@@ -1,82 +1,83 b''
1 1 .. _rhodecode-release-notes-ref:
2 2
3 3 Release Notes
4 4 =============
5 5
6 6 |RCE| 4.x Versions
7 7 ------------------
8 8
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.2.0.rst
12 13 release-notes-4.1.2.rst
13 14 release-notes-4.1.1.rst
14 15 release-notes-4.1.0.rst
15 16 release-notes-4.0.1.rst
16 17 release-notes-4.0.0.rst
17 18
18 19 |RCE| 3.x Versions
19 20 ------------------
20 21
21 22 .. toctree::
22 23 :maxdepth: 1
23 24
24 25 release-notes-3.8.4.rst
25 26 release-notes-3.8.3.rst
26 27 release-notes-3.8.2.rst
27 28 release-notes-3.8.1.rst
28 29 release-notes-3.8.0.rst
29 30 release-notes-3.7.1.rst
30 31 release-notes-3.7.0.rst
31 32 release-notes-3.6.1.rst
32 33 release-notes-3.6.0.rst
33 34 release-notes-3.5.2.rst
34 35 release-notes-3.5.1.rst
35 36 release-notes-3.5.0.rst
36 37 release-notes-3.4.1.rst
37 38 release-notes-3.4.0.rst
38 39 release-notes-3.3.4.rst
39 40 release-notes-3.3.3.rst
40 41 release-notes-3.3.2.rst
41 42 release-notes-3.3.1.rst
42 43 release-notes-3.3.0.rst
43 44 release-notes-3.2.3.rst
44 45 release-notes-3.2.2.rst
45 46 release-notes-3.2.1.rst
46 47 release-notes-3.2.0.rst
47 48 release-notes-3.1.1.rst
48 49 release-notes-3.1.0.rst
49 50 release-notes-3.0.2.rst
50 51 release-notes-3.0.1.rst
51 52 release-notes-3.0.0.rst
52 53
53 54 |RCE| 2.x Versions
54 55 ------------------
55 56
56 57 .. toctree::
57 58 :maxdepth: 1
58 59
59 60 release-notes-2.2.8.rst
60 61 release-notes-2.2.7.rst
61 62 release-notes-2.2.6.rst
62 63 release-notes-2.2.5.rst
63 64 release-notes-2.2.4.rst
64 65 release-notes-2.2.3.rst
65 66 release-notes-2.2.2.rst
66 67 release-notes-2.2.1.rst
67 68 release-notes-2.2.0.rst
68 69 release-notes-2.1.0.rst
69 70 release-notes-2.0.2.rst
70 71 release-notes-2.0.1.rst
71 72 release-notes-2.0.0.rst
72 73
73 74 |RCE| 1.x Versions
74 75 ------------------
75 76
76 77 .. toctree::
77 78 :maxdepth: 1
78 79
79 80 release-notes-1.7.2.rst
80 81 release-notes-1.7.1.rst
81 82 release-notes-1.7.0.rst
82 83 release-notes-1.6.0.rst
@@ -1,163 +1,160 b''
1 1 # Utility to generate the license information
2 2 #
3 3 # Usage:
4 4 #
5 5 # nix-build -I ~/dev license.nix -A result
6 6 #
7 7 # Afterwards ./result will contain the license information as JSON files.
8 8 #
9 9 #
10 10 # Overview
11 11 #
12 12 # Uses two steps to get the relevant license information:
13 13 #
14 14 # 1. Walk down the derivations based on "buildInputs" and
15 15 # "propagatedBuildInputs". This results in all dependencies based on the nix
16 16 # declartions.
17 17 #
18 18 # 2. Build Enterprise and query nix-store to get a list of runtime
19 19 # dependencies. The results from step 1 are then limited to the ones which
20 20 # are in this list.
21 21 #
22 22 # The result is then available in ./result/license.json.
23 23 #
24 24
25 25
26 26 let
27 27
28 28 nixpkgs = import <nixpkgs> {};
29 29
30 30 stdenv = nixpkgs.stdenv;
31 31
32 32 # Enterprise as simple as possible, goal here is just to identify the runtime
33 33 # dependencies. Ideally we could avoid building Enterprise at all and somehow
34 34 # figure it out without calling into nix-store.
35 35 enterprise = import ./default.nix {
36 36 doCheck = false;
37 with_vcsserver = false;
38 with_pyramid = false;
39 cythonize = false;
40 37 };
41 38
42 39 # For a given derivation, return the list of all dependencies
43 40 drvToDependencies = drv: nixpkgs.lib.flatten [
44 41 drv.nativeBuildInputs or []
45 42 drv.propagatedNativeBuildInputs or []
46 43 ];
47 44
48 45 # Transform the given derivation into the meta information which we need in
49 46 # the resulting JSON files.
50 47 drvToMeta = drv: {
51 48 name = drv.name or "UNNAMED";
52 49 license = if drv ? meta.license then drv.meta.license else "UNKNOWN";
53 50 };
54 51
55 52 # Walk the tree of buildInputs and propagatedBuildInputs and return it as a
56 53 # flat list. Duplicates are avoided.
57 54 listDrvDependencies = drv: let
58 55 addElement = element: seen:
59 56 if (builtins.elem element seen)
60 57 then seen
61 58 else let
62 59 newSeen = seen ++ [ element ];
63 60 newDeps = drvToDependencies element;
64 61 in nixpkgs.lib.fold addElement newSeen newDeps;
65 62 initialElements = drvToDependencies drv;
66 63 in nixpkgs.lib.fold addElement [] initialElements;
67 64
68 65 # Reads in a file with store paths and returns a list of derivation names.
69 66 #
70 67 # Reads the file, splits the lines, then removes the prefix, so that we
71 68 # end up with a list of derivation names in the end.
72 69 storePathsToDrvNames = srcPath: let
73 70 rawStorePaths = nixpkgs.lib.removeSuffix "\n" (
74 71 builtins.readFile srcPath);
75 72 storePaths = nixpkgs.lib.splitString "\n" rawStorePaths;
76 73 # TODO: johbo: Would be nice to use some sort of utility here to convert
77 74 # the path to a derivation name.
78 75 storePathPrefix = (
79 76 builtins.stringLength "/nix/store/zwy7aavnif9ayw30rya1k6xiacafzzl6-");
80 77 storePathToName = path:
81 78 builtins.substring storePathPrefix (builtins.stringLength path) path;
82 79 in (map storePathToName storePaths);
83 80
84 81 in rec {
85 82
86 83 # Build Enterprise and call nix-store to retrieve the runtime
87 84 # dependencies. The result is available in the nix store.
88 85 runtimeDependencies = stdenv.mkDerivation {
89 86 name = "runtime-dependencies";
90 87 buildInputs = [
91 88 # Needed to query the store
92 89 nixpkgs.nix
93 90 ];
94 91 unpackPhase = ''
95 92 echo "Nothing to unpack"
96 93 '';
97 94 buildPhase = ''
98 95 # Get a list of runtime dependencies
99 96 nix-store -q --references ${enterprise} > nix-store-references
100 97 '';
101 98 installPhase = ''
102 99 mkdir -p $out
103 100 cp -v nix-store-references $out/
104 101 '';
105 102 };
106 103
107 104 # Produce the license overview files.
108 105 result = let
109 106
110 107 # Dependencies according to the nix-store
111 108 runtimeDependencyNames = (
112 109 storePathsToDrvNames "${runtimeDependencies}/nix-store-references");
113 110
114 111 # Dependencies based on buildInputs and propagatedBuildInputs
115 112 enterpriseAllDependencies = listDrvDependencies enterprise;
116 113 enterpriseRuntimeDependencies = let
117 114 elemName = element: element.name or "UNNAMED";
118 115 isRuntime = element: builtins.elem (elemName element) runtimeDependencyNames;
119 116 in builtins.filter isRuntime enterpriseAllDependencies;
120 117
121 118 # Extract relevant meta information
122 119 enterpriseAllLicenses = map drvToMeta enterpriseAllDependencies;
123 120 enterpriseRuntimeLicenses = map drvToMeta enterpriseRuntimeDependencies;
124 121
125 122 in stdenv.mkDerivation {
126 123
127 124 name = "licenses";
128 125
129 126 buildInputs = [];
130 127
131 128 unpackPhase = ''
132 129 echo "Nothing to unpack"
133 130 '';
134 131
135 132 buildPhase = ''
136 133 mkdir build
137 134
138 135 # Copy list of runtime dependencies for the Python processor
139 136 cp "${runtimeDependencies}/nix-store-references" ./build/nix-store-references
140 137
141 138 # All licenses which we found by walking buildInputs and
142 139 # propagatedBuildInputs
143 140 cat > build/all-licenses.json <<EOF
144 141 ${builtins.toJSON enterpriseAllLicenses}
145 142 EOF
146 143
147 144 # License information for our runtime dependencies only. Basically all
148 145 # licenses limited to the items which where also reported by nix-store as
149 146 # a dependency.
150 147 cat > build/licenses.json <<EOF
151 148 ${builtins.toJSON enterpriseRuntimeLicenses}
152 149 EOF
153 150 '';
154 151
155 152 installPhase = ''
156 153 mkdir -p $out
157 154
158 155 # Store it all, that helps when things go wrong
159 156 cp -rv ./build/* $out
160 157 '';
161 158 };
162 159
163 160 }
@@ -1,165 +1,273 b''
1 1 # Overrides for the generated python-packages.nix
2 2 #
3 3 # This function is intended to be used as an extension to the generated file
4 4 # python-packages.nix. The main objective is to add needed dependencies of C
5 5 # libraries and tweak the build instructions where needed.
6 6
7 7 { pkgs, basePythonPackages }:
8 8
9 9 let
10 10 sed = "sed -i";
11 localLicenses = {
12 repoze = {
13 fullName = "Repoze License";
14 url = http://www.repoze.org/LICENSE.txt;
15 };
16 };
11 17 in
12 18
13 19 self: super: {
14 20
21 appenlight-client = super.appenlight-client.override (attrs: {
22 meta = {
23 license = [ pkgs.lib.licenses.bsdOriginal ];
24 };
25 });
26
27 future = super.future.override (attrs: {
28 meta = {
29 license = [ pkgs.lib.licenses.mit ];
30 };
31 });
32
15 33 gnureadline = super.gnureadline.override (attrs: {
16 34 buildInputs = attrs.buildInputs ++ [
17 35 pkgs.ncurses
18 36 ];
19 37 patchPhase = ''
20 38 substituteInPlace setup.py --replace "/bin/bash" "${pkgs.bash}/bin/bash"
21 39 '';
22 40 });
23 41
24 42 gunicorn = super.gunicorn.override (attrs: {
25 43 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
26 44 # johbo: futures is needed as long as we are on Python 2, otherwise
27 45 # gunicorn explodes if used with multiple threads per worker.
28 46 self.futures
29 47 ];
30 48 });
31 49
32 50 ipython = super.ipython.override (attrs: {
33 51 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
34 52 self.gnureadline
35 53 ];
36 54 });
37 55
38 56 kombu = super.kombu.override (attrs: {
39 57 # The current version of kombu needs some patching to work with the
40 58 # other libs. Should be removed once we update celery and kombu.
41 59 patches = [
42 60 ./patch-kombu-py-2-7-11.diff
43 61 ./patch-kombu-msgpack.diff
44 62 ];
45 63 });
46 64
47 65 lxml = super.lxml.override (attrs: {
48 66 buildInputs = with self; [
49 67 pkgs.libxml2
50 68 pkgs.libxslt
51 69 ];
52 70 });
53 71
54 72 MySQL-python = super.MySQL-python.override (attrs: {
55 73 buildInputs = attrs.buildInputs ++ [
56 74 pkgs.openssl
57 75 ];
58 76 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
59 77 pkgs.mysql.lib
60 78 pkgs.zlib
61 79 ];
62 80 });
63 81
64 82 psutil = super.psutil.override (attrs: {
65 83 buildInputs = attrs.buildInputs ++
66 84 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.darwin.IOKit;
67 85 });
68 86
69 87 psycopg2 = super.psycopg2.override (attrs: {
70 88 buildInputs = attrs.buildInputs ++
71 89 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.openssl;
72 90 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
73 91 pkgs.postgresql
74 92 ];
93 meta = {
94 license = pkgs.lib.licenses.lgpl3Plus;
95 };
75 96 });
76 97
77 98 pycurl = super.pycurl.override (attrs: {
78 99 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
79 100 pkgs.curl
80 101 pkgs.openssl
81 102 ];
82 103 preConfigure = ''
83 104 substituteInPlace setup.py --replace '--static-libs' '--libs'
84 105 export PYCURL_SSL_LIBRARY=openssl
85 106 '';
107 meta = {
108 # TODO: It is LGPL and MIT
109 license = pkgs.lib.licenses.mit;
110 };
86 111 });
87 112
88 113 Pylons = super.Pylons.override (attrs: {
89 114 name = "Pylons-1.0.1-patch1";
90 115 src = pkgs.fetchgit {
91 116 url = "https://code.rhodecode.com/upstream/pylons";
92 117 rev = "707354ee4261b9c10450404fc9852ccea4fd667d";
93 118 sha256 = "b2763274c2780523a335f83a1df65be22ebe4ff413a7bc9e9288d23c1f62032e";
94 119 };
95 120 });
96 121
97 122 pyramid = super.pyramid.override (attrs: {
98 123 postFixup = ''
99 124 wrapPythonPrograms
100 125 # TODO: johbo: "wrapPython" adds this magic line which
101 126 # confuses pserve.
102 127 ${sed} '/import sys; sys.argv/d' $out/bin/.pserve-wrapped
103 128 '';
129 meta = {
130 license = localLicenses.repoze;
131 };
132 });
133
134 pyramid-debugtoolbar = super.pyramid-debugtoolbar.override (attrs: {
135 meta = {
136 license = [ pkgs.lib.licenses.bsdOriginal localLicenses.repoze ];
137 };
104 138 });
105 139
106 140 Pyro4 = super.Pyro4.override (attrs: {
107 141 # TODO: Was not able to generate this version, needs further
108 142 # investigation.
109 143 name = "Pyro4-4.35";
110 144 src = pkgs.fetchurl {
111 145 url = "https://pypi.python.org/packages/source/P/Pyro4/Pyro4-4.35.src.tar.gz";
112 146 md5 = "cbe6cb855f086a0f092ca075005855f3";
113 147 };
114 148 });
115 149
116 150 pysqlite = super.pysqlite.override (attrs: {
117 151 propagatedBuildInputs = [
118 152 pkgs.sqlite
119 153 ];
154 meta = {
155 license = [ pkgs.lib.licenses.zlib pkgs.lib.licenses.libpng ];
156 };
120 157 });
121 158
122 159 pytest-runner = super.pytest-runner.override (attrs: {
123 160 propagatedBuildInputs = [
124 161 self.setuptools-scm
125 162 ];
126 163 });
127 164
128 165 python-ldap = super.python-ldap.override (attrs: {
129 166 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
130 167 pkgs.cyrus_sasl
131 168 pkgs.openldap
132 169 pkgs.openssl
133 170 ];
134 171 NIX_CFLAGS_COMPILE = "-I${pkgs.cyrus_sasl}/include/sasl";
135 172 });
136 173
137 174 python-pam = super.python-pam.override (attrs:
138 175 let
139 176 includeLibPam = pkgs.stdenv.isLinux;
140 177 in {
141 178 # TODO: johbo: Move the option up into the default.nix, we should
142 179 # include python-pam only on supported platforms.
143 180 propagatedBuildInputs = attrs.propagatedBuildInputs ++
144 181 pkgs.lib.optional includeLibPam [
145 182 pkgs.pam
146 183 ];
147 184 # TODO: johbo: Check if this can be avoided, or transform into
148 185 # a real patch
149 186 patchPhase = pkgs.lib.optionals includeLibPam ''
150 187 substituteInPlace pam.py \
151 188 --replace 'find_library("pam")' '"${pkgs.pam}/lib/libpam.so.0"'
152 189 '';
153 190 });
154 191
155 192 rhodecode-tools = super.rhodecode-tools.override (attrs: {
156 193 patches = [
157 194 ./patch-rhodecode-tools-setup.diff
158 195 ];
159 196 });
160 197
198 URLObject = super.URLObject.override (attrs: {
199 meta = {
200 license = {
201 spdxId = "Unlicense";
202 fullName = "The Unlicense";
203 url = http://unlicense.org/;
204 };
205 };
206 });
207
208 amqplib = super.amqplib.override (attrs: {
209 meta = {
210 license = pkgs.lib.licenses.lgpl3;
211 };
212 });
213
214 docutils = super.docutils.override (attrs: {
215 meta = {
216 license = pkgs.lib.licenses.bsd2;
217 };
218 });
219
220 colander = super.colander.override (attrs: {
221 meta = {
222 license = localLicenses.repoze;
223 };
224 });
225
226 pyramid-beaker = super.pyramid-beaker.override (attrs: {
227 meta = {
228 license = localLicenses.repoze;
229 };
230 });
231
232 pyramid-mako = super.pyramid-mako.override (attrs: {
233 meta = {
234 license = localLicenses.repoze;
235 };
236 });
237
238 repoze.lru = super.repoze.lru.override (attrs: {
239 meta = {
240 license = localLicenses.repoze;
241 };
242 });
243
244 recaptcha-client = super.recaptcha-client.override (attrs: {
245 meta = {
246 # TODO: It is MIT/X11
247 license = pkgs.lib.licenses.mit;
248 };
249 });
250
251 python-editor = super.python-editor.override (attrs: {
252 meta = {
253 license = pkgs.lib.licenses.asl20;
254 };
255 });
256
257 translationstring = super.translationstring.override (attrs: {
258 meta = {
259 license = localLicenses.repoze;
260 };
261 });
262
263 venusian = super.venusian.override (attrs: {
264 meta = {
265 license = localLicenses.repoze;
266 };
267 });
268
161 269 # Avoid that setuptools is replaced, this leads to trouble
162 270 # with buildPythonPackage.
163 271 setuptools = basePythonPackages.setuptools;
164 272
165 273 }
@@ -1,1263 +1,1641 b''
1 1 {
2 2 Babel = super.buildPythonPackage {
3 3 name = "Babel-1.3";
4 4 buildInputs = with self; [];
5 5 doCheck = false;
6 6 propagatedBuildInputs = with self; [pytz];
7 7 src = fetchurl {
8 8 url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
9 9 md5 = "5264ceb02717843cbc9ffce8e6e06bdb";
10 10 };
11 meta = {
12 license = [ pkgs.lib.licenses.bsdOriginal ];
13 };
11 14 };
12 15 Beaker = super.buildPythonPackage {
13 16 name = "Beaker-1.7.0";
14 17 buildInputs = with self; [];
15 18 doCheck = false;
16 19 propagatedBuildInputs = with self; [];
17 20 src = fetchurl {
18 21 url = "https://pypi.python.org/packages/97/8e/409d2e7c009b8aa803dc9e6f239f1db7c3cdf578249087a404e7c27a505d/Beaker-1.7.0.tar.gz";
19 22 md5 = "386be3f7fe427358881eee4622b428b3";
20 23 };
24 meta = {
25 license = [ pkgs.lib.licenses.bsdOriginal ];
26 };
21 27 };
22 28 CProfileV = super.buildPythonPackage {
23 29 name = "CProfileV-1.0.6";
24 30 buildInputs = with self; [];
25 31 doCheck = false;
26 32 propagatedBuildInputs = with self; [bottle];
27 33 src = fetchurl {
28 34 url = "https://pypi.python.org/packages/eb/df/983a0b6cfd3ac94abf023f5011cb04f33613ace196e33f53c86cf91850d5/CProfileV-1.0.6.tar.gz";
29 35 md5 = "08c7c242b6e64237bc53c5d13537e03d";
30 36 };
37 meta = {
38 license = [ pkgs.lib.licenses.mit ];
39 };
31 40 };
32 41 Fabric = super.buildPythonPackage {
33 42 name = "Fabric-1.10.0";
34 43 buildInputs = with self; [];
35 44 doCheck = false;
36 45 propagatedBuildInputs = with self; [paramiko];
37 46 src = fetchurl {
38 47 url = "https://pypi.python.org/packages/e3/5f/b6ebdb5241d5ec9eab582a5c8a01255c1107da396f849e538801d2fe64a5/Fabric-1.10.0.tar.gz";
39 48 md5 = "2cb96473387f0e7aa035210892352f4a";
40 49 };
50 meta = {
51 license = [ pkgs.lib.licenses.bsdOriginal ];
52 };
41 53 };
42 54 FormEncode = super.buildPythonPackage {
43 55 name = "FormEncode-1.2.4";
44 56 buildInputs = with self; [];
45 57 doCheck = false;
46 58 propagatedBuildInputs = with self; [];
47 59 src = fetchurl {
48 60 url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
49 61 md5 = "6bc17fb9aed8aea198975e888e2077f4";
50 62 };
63 meta = {
64 license = [ pkgs.lib.licenses.psfl ];
65 };
51 66 };
52 67 Jinja2 = super.buildPythonPackage {
53 68 name = "Jinja2-2.7.3";
54 69 buildInputs = with self; [];
55 70 doCheck = false;
56 71 propagatedBuildInputs = with self; [MarkupSafe];
57 72 src = fetchurl {
58 73 url = "https://pypi.python.org/packages/b0/73/eab0bca302d6d6a0b5c402f47ad1760dc9cb2dd14bbc1873ad48db258e4d/Jinja2-2.7.3.tar.gz";
59 74 md5 = "b9dffd2f3b43d673802fe857c8445b1a";
60 75 };
76 meta = {
77 license = [ pkgs.lib.licenses.bsdOriginal ];
78 };
61 79 };
62 80 Mako = super.buildPythonPackage {
63 81 name = "Mako-1.0.1";
64 82 buildInputs = with self; [];
65 83 doCheck = false;
66 84 propagatedBuildInputs = with self; [MarkupSafe];
67 85 src = fetchurl {
68 86 url = "https://pypi.python.org/packages/8e/a4/aa56533ecaa5f22ca92428f74e074d0c9337282933c722391902c8f9e0f8/Mako-1.0.1.tar.gz";
69 87 md5 = "9f0aafd177b039ef67b90ea350497a54";
70 88 };
89 meta = {
90 license = [ pkgs.lib.licenses.mit ];
91 };
71 92 };
72 93 Markdown = super.buildPythonPackage {
73 94 name = "Markdown-2.6.2";
74 95 buildInputs = with self; [];
75 96 doCheck = false;
76 97 propagatedBuildInputs = with self; [];
77 98 src = fetchurl {
78 99 url = "https://pypi.python.org/packages/62/8b/83658b5f6c220d5fcde9f9852d46ea54765d734cfbc5a9f4c05bfc36db4d/Markdown-2.6.2.tar.gz";
79 100 md5 = "256d19afcc564dc4ce4c229bb762f7ae";
80 101 };
102 meta = {
103 license = [ pkgs.lib.licenses.bsdOriginal ];
104 };
81 105 };
82 106 MarkupSafe = super.buildPythonPackage {
83 107 name = "MarkupSafe-0.23";
84 108 buildInputs = with self; [];
85 109 doCheck = false;
86 110 propagatedBuildInputs = with self; [];
87 111 src = fetchurl {
88 112 url = "https://pypi.python.org/packages/c0/41/bae1254e0396c0cc8cf1751cb7d9afc90a602353695af5952530482c963f/MarkupSafe-0.23.tar.gz";
89 113 md5 = "f5ab3deee4c37cd6a922fb81e730da6e";
90 114 };
115 meta = {
116 license = [ pkgs.lib.licenses.bsdOriginal ];
117 };
91 118 };
92 119 MySQL-python = super.buildPythonPackage {
93 120 name = "MySQL-python-1.2.5";
94 121 buildInputs = with self; [];
95 122 doCheck = false;
96 123 propagatedBuildInputs = with self; [];
97 124 src = fetchurl {
98 125 url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
99 126 md5 = "654f75b302db6ed8dc5a898c625e030c";
100 127 };
128 meta = {
129 license = [ pkgs.lib.licenses.gpl1 ];
130 };
101 131 };
102 132 Paste = super.buildPythonPackage {
103 133 name = "Paste-2.0.2";
104 134 buildInputs = with self; [];
105 135 doCheck = false;
106 136 propagatedBuildInputs = with self; [six];
107 137 src = fetchurl {
108 138 url = "https://pypi.python.org/packages/d5/8d/0f8ac40687b97ff3e07ebd1369be20bdb3f93864d2dc3c2ff542edb4ce50/Paste-2.0.2.tar.gz";
109 139 md5 = "4bfc8a7eaf858f6309d2ac0f40fc951c";
110 140 };
141 meta = {
142 license = [ pkgs.lib.licenses.mit ];
143 };
111 144 };
112 145 PasteDeploy = super.buildPythonPackage {
113 146 name = "PasteDeploy-1.5.2";
114 147 buildInputs = with self; [];
115 148 doCheck = false;
116 149 propagatedBuildInputs = with self; [];
117 150 src = fetchurl {
118 151 url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz";
119 152 md5 = "352b7205c78c8de4987578d19431af3b";
120 153 };
154 meta = {
155 license = [ pkgs.lib.licenses.mit ];
156 };
121 157 };
122 158 PasteScript = super.buildPythonPackage {
123 159 name = "PasteScript-1.7.5";
124 160 buildInputs = with self; [];
125 161 doCheck = false;
126 162 propagatedBuildInputs = with self; [Paste PasteDeploy];
127 163 src = fetchurl {
128 164 url = "https://pypi.python.org/packages/a5/05/fc60efa7c2f17a1dbaeccb2a903a1e90902d92b9d00eebabe3095829d806/PasteScript-1.7.5.tar.gz";
129 165 md5 = "4c72d78dcb6bb993f30536842c16af4d";
130 166 };
167 meta = {
168 license = [ pkgs.lib.licenses.mit ];
169 };
131 170 };
132 171 Pygments = super.buildPythonPackage {
133 172 name = "Pygments-2.0.2";
134 173 buildInputs = with self; [];
135 174 doCheck = false;
136 175 propagatedBuildInputs = with self; [];
137 176 src = fetchurl {
138 177 url = "https://pypi.python.org/packages/f4/c6/bdbc5a8a112256b2b6136af304dbae93d8b1ef8738ff2d12a51018800e46/Pygments-2.0.2.tar.gz";
139 178 md5 = "238587a1370d62405edabd0794b3ec4a";
140 179 };
180 meta = {
181 license = [ pkgs.lib.licenses.bsdOriginal ];
182 };
141 183 };
142 184 Pylons = super.buildPythonPackage {
143 185 name = "Pylons-1.0.1";
144 186 buildInputs = with self; [];
145 187 doCheck = false;
146 188 propagatedBuildInputs = with self; [Routes WebHelpers Beaker Paste PasteDeploy PasteScript FormEncode simplejson decorator nose Mako WebError WebTest Tempita MarkupSafe WebOb];
147 189 src = fetchurl {
148 190 url = "https://pypi.python.org/packages/a2/69/b835a6bad00acbfeed3f33c6e44fa3f936efc998c795bfb15c61a79ecf62/Pylons-1.0.1.tar.gz";
149 191 md5 = "6cb880d75fa81213192142b07a6e4915";
150 192 };
193 meta = {
194 license = [ pkgs.lib.licenses.bsdOriginal ];
195 };
151 196 };
152 197 Pyro4 = super.buildPythonPackage {
153 198 name = "Pyro4-4.41";
154 199 buildInputs = with self; [];
155 200 doCheck = false;
156 201 propagatedBuildInputs = with self; [serpent];
157 202 src = fetchurl {
158 203 url = "https://pypi.python.org/packages/56/2b/89b566b4bf3e7f8ba790db2d1223852f8cb454c52cab7693dd41f608ca2a/Pyro4-4.41.tar.gz";
159 204 md5 = "ed69e9bfafa9c06c049a87cb0c4c2b6c";
160 205 };
206 meta = {
207 license = [ pkgs.lib.licenses.mit ];
208 };
161 209 };
162 210 Routes = super.buildPythonPackage {
163 211 name = "Routes-1.13";
164 212 buildInputs = with self; [];
165 213 doCheck = false;
166 214 propagatedBuildInputs = with self; [repoze.lru];
167 215 src = fetchurl {
168 216 url = "https://pypi.python.org/packages/88/d3/259c3b3cde8837eb9441ab5f574a660e8a4acea8f54a078441d4d2acac1c/Routes-1.13.tar.gz";
169 217 md5 = "d527b0ab7dd9172b1275a41f97448783";
170 218 };
219 meta = {
220 license = [ pkgs.lib.licenses.bsdOriginal ];
221 };
171 222 };
172 223 SQLAlchemy = super.buildPythonPackage {
173 224 name = "SQLAlchemy-0.9.9";
174 225 buildInputs = with self; [];
175 226 doCheck = false;
176 227 propagatedBuildInputs = with self; [];
177 228 src = fetchurl {
178 229 url = "https://pypi.python.org/packages/28/f7/1bbfd0d8597e8c358d5e15a166a486ad82fc5579b4e67b6ef7c05b1d182b/SQLAlchemy-0.9.9.tar.gz";
179 230 md5 = "8a10a9bd13ed3336ef7333ac2cc679ff";
180 231 };
232 meta = {
233 license = [ pkgs.lib.licenses.mit ];
234 };
181 235 };
182 236 Sphinx = super.buildPythonPackage {
183 237 name = "Sphinx-1.2.2";
184 238 buildInputs = with self; [];
185 239 doCheck = false;
186 240 propagatedBuildInputs = with self; [Pygments docutils Jinja2];
187 241 src = fetchurl {
188 242 url = "https://pypi.python.org/packages/0a/50/34017e6efcd372893a416aba14b84a1a149fc7074537b0e9cb6ca7b7abe9/Sphinx-1.2.2.tar.gz";
189 243 md5 = "3dc73ccaa8d0bfb2d62fb671b1f7e8a4";
190 244 };
245 meta = {
246 license = [ pkgs.lib.licenses.bsdOriginal ];
247 };
191 248 };
192 249 Tempita = super.buildPythonPackage {
193 250 name = "Tempita-0.5.2";
194 251 buildInputs = with self; [];
195 252 doCheck = false;
196 253 propagatedBuildInputs = with self; [];
197 254 src = fetchurl {
198 255 url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
199 256 md5 = "4c2f17bb9d481821c41b6fbee904cea1";
200 257 };
258 meta = {
259 license = [ pkgs.lib.licenses.mit ];
260 };
201 261 };
202 262 URLObject = super.buildPythonPackage {
203 263 name = "URLObject-2.4.0";
204 264 buildInputs = with self; [];
205 265 doCheck = false;
206 266 propagatedBuildInputs = with self; [];
207 267 src = fetchurl {
208 268 url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz";
209 269 md5 = "2ed819738a9f0a3051f31dc9924e3065";
210 270 };
271 meta = {
272 license = [ ];
273 };
211 274 };
212 275 WebError = super.buildPythonPackage {
213 276 name = "WebError-0.10.3";
214 277 buildInputs = with self; [];
215 278 doCheck = false;
216 279 propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste];
217 280 src = fetchurl {
218 281 url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
219 282 md5 = "84b9990b0baae6fd440b1e60cdd06f9a";
220 283 };
284 meta = {
285 license = [ pkgs.lib.licenses.mit ];
286 };
221 287 };
222 288 WebHelpers = super.buildPythonPackage {
223 289 name = "WebHelpers-1.3";
224 290 buildInputs = with self; [];
225 291 doCheck = false;
226 292 propagatedBuildInputs = with self; [MarkupSafe];
227 293 src = fetchurl {
228 294 url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
229 295 md5 = "32749ffadfc40fea51075a7def32588b";
230 296 };
297 meta = {
298 license = [ pkgs.lib.licenses.bsdOriginal ];
299 };
231 300 };
232 301 WebHelpers2 = super.buildPythonPackage {
233 302 name = "WebHelpers2-2.0";
234 303 buildInputs = with self; [];
235 304 doCheck = false;
236 305 propagatedBuildInputs = with self; [MarkupSafe six];
237 306 src = fetchurl {
238 307 url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
239 308 md5 = "0f6b68d70c12ee0aed48c00b24da13d3";
240 309 };
310 meta = {
311 license = [ pkgs.lib.licenses.mit ];
312 };
241 313 };
242 314 WebOb = super.buildPythonPackage {
243 315 name = "WebOb-1.3.1";
244 316 buildInputs = with self; [];
245 317 doCheck = false;
246 318 propagatedBuildInputs = with self; [];
247 319 src = fetchurl {
248 320 url = "https://pypi.python.org/packages/16/78/adfc0380b8a0d75b2d543fa7085ba98a573b1ae486d9def88d172b81b9fa/WebOb-1.3.1.tar.gz";
249 321 md5 = "20918251c5726956ba8fef22d1556177";
250 322 };
323 meta = {
324 license = [ pkgs.lib.licenses.mit ];
325 };
251 326 };
252 327 WebTest = super.buildPythonPackage {
253 328 name = "WebTest-1.4.3";
254 329 buildInputs = with self; [];
255 330 doCheck = false;
256 331 propagatedBuildInputs = with self; [WebOb];
257 332 src = fetchurl {
258 333 url = "https://pypi.python.org/packages/51/3d/84fd0f628df10b30c7db87895f56d0158e5411206b721ca903cb51bfd948/WebTest-1.4.3.zip";
259 334 md5 = "631ce728bed92c681a4020a36adbc353";
260 335 };
336 meta = {
337 license = [ pkgs.lib.licenses.mit ];
338 };
261 339 };
262 340 Whoosh = super.buildPythonPackage {
263 341 name = "Whoosh-2.7.0";
264 342 buildInputs = with self; [];
265 343 doCheck = false;
266 344 propagatedBuildInputs = with self; [];
267 345 src = fetchurl {
268 346 url = "https://pypi.python.org/packages/1c/dc/2f0231ff3875ded36df8c1ab851451e51a237dc0e5a86d3d96036158da94/Whoosh-2.7.0.zip";
269 347 md5 = "7abfd970f16fadc7311960f3fa0bc7a9";
270 348 };
349 meta = {
350 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
351 };
271 352 };
272 353 alembic = super.buildPythonPackage {
273 354 name = "alembic-0.8.4";
274 355 buildInputs = with self; [];
275 356 doCheck = false;
276 357 propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor];
277 358 src = fetchurl {
278 359 url = "https://pypi.python.org/packages/ca/7e/299b4499b5c75e5a38c5845145ad24755bebfb8eec07a2e1c366b7181eeb/alembic-0.8.4.tar.gz";
279 360 md5 = "5f95d8ee62b443f9b37eb5bee76c582d";
280 361 };
362 meta = {
363 license = [ pkgs.lib.licenses.mit ];
364 };
281 365 };
282 366 amqplib = super.buildPythonPackage {
283 367 name = "amqplib-1.0.2";
284 368 buildInputs = with self; [];
285 369 doCheck = false;
286 370 propagatedBuildInputs = with self; [];
287 371 src = fetchurl {
288 372 url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz";
289 373 md5 = "5c92f17fbedd99b2b4a836d4352d1e2f";
290 374 };
375 meta = {
376 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
377 };
291 378 };
292 379 anyjson = super.buildPythonPackage {
293 380 name = "anyjson-0.3.3";
294 381 buildInputs = with self; [];
295 382 doCheck = false;
296 383 propagatedBuildInputs = with self; [];
297 384 src = fetchurl {
298 385 url = "https://pypi.python.org/packages/c3/4d/d4089e1a3dd25b46bebdb55a992b0797cff657b4477bc32ce28038fdecbc/anyjson-0.3.3.tar.gz";
299 386 md5 = "2ea28d6ec311aeeebaf993cb3008b27c";
300 387 };
388 meta = {
389 license = [ pkgs.lib.licenses.bsdOriginal ];
390 };
301 391 };
302 392 appenlight-client = super.buildPythonPackage {
303 393 name = "appenlight-client-0.6.14";
304 394 buildInputs = with self; [];
305 395 doCheck = false;
306 396 propagatedBuildInputs = with self; [WebOb requests];
307 397 src = fetchurl {
308 398 url = "https://pypi.python.org/packages/4d/e0/23fee3ebada8143f707e65c06bcb82992040ee64ea8355e044ed55ebf0c1/appenlight_client-0.6.14.tar.gz";
309 399 md5 = "578c69b09f4356d898fff1199b98a95c";
310 400 };
401 meta = {
402 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "DFSG approved"; } ];
403 };
311 404 };
312 405 authomatic = super.buildPythonPackage {
313 406 name = "authomatic-0.1.0.post1";
314 407 buildInputs = with self; [];
315 408 doCheck = false;
316 409 propagatedBuildInputs = with self; [];
317 410 src = fetchurl {
318 411 url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz";
319 412 md5 = "be3f3ce08747d776aae6d6cc8dcb49a9";
320 413 };
414 meta = {
415 license = [ pkgs.lib.licenses.mit ];
416 };
321 417 };
322 418 backport-ipaddress = super.buildPythonPackage {
323 419 name = "backport-ipaddress-0.1";
324 420 buildInputs = with self; [];
325 421 doCheck = false;
326 422 propagatedBuildInputs = with self; [];
327 423 src = fetchurl {
328 424 url = "https://pypi.python.org/packages/d3/30/54c6dab05a4dec44db25ff309f1fbb6b7a8bde3f2bade38bb9da67bbab8f/backport_ipaddress-0.1.tar.gz";
329 425 md5 = "9c1f45f4361f71b124d7293a60006c05";
330 426 };
427 meta = {
428 license = [ pkgs.lib.licenses.psfl ];
429 };
331 430 };
332 431 bottle = super.buildPythonPackage {
333 432 name = "bottle-0.12.8";
334 433 buildInputs = with self; [];
335 434 doCheck = false;
336 435 propagatedBuildInputs = with self; [];
337 436 src = fetchurl {
338 437 url = "https://pypi.python.org/packages/52/df/e4a408f3a7af396d186d4ecd3b389dd764f0f943b4fa8d257bfe7b49d343/bottle-0.12.8.tar.gz";
339 438 md5 = "13132c0a8f607bf860810a6ee9064c5b";
340 439 };
440 meta = {
441 license = [ pkgs.lib.licenses.mit ];
442 };
341 443 };
342 444 bumpversion = super.buildPythonPackage {
343 445 name = "bumpversion-0.5.3";
344 446 buildInputs = with self; [];
345 447 doCheck = false;
346 448 propagatedBuildInputs = with self; [];
347 449 src = fetchurl {
348 450 url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
349 451 md5 = "c66a3492eafcf5ad4b024be9fca29820";
350 452 };
453 meta = {
454 license = [ pkgs.lib.licenses.mit ];
455 };
351 456 };
352 457 celery = super.buildPythonPackage {
353 458 name = "celery-2.2.10";
354 459 buildInputs = with self; [];
355 460 doCheck = false;
356 461 propagatedBuildInputs = with self; [python-dateutil anyjson kombu pyparsing];
357 462 src = fetchurl {
358 463 url = "https://pypi.python.org/packages/b1/64/860fd50e45844c83442e7953effcddeff66b2851d90b2d784f7201c111b8/celery-2.2.10.tar.gz";
359 464 md5 = "898bc87e54f278055b561316ba73e222";
360 465 };
466 meta = {
467 license = [ pkgs.lib.licenses.bsdOriginal ];
468 };
361 469 };
362 470 click = super.buildPythonPackage {
363 471 name = "click-5.1";
364 472 buildInputs = with self; [];
365 473 doCheck = false;
366 474 propagatedBuildInputs = with self; [];
367 475 src = fetchurl {
368 476 url = "https://pypi.python.org/packages/b7/34/a496632c4fb6c1ee76efedf77bb8d28b29363d839953d95095b12defe791/click-5.1.tar.gz";
369 477 md5 = "9c5323008cccfe232a8b161fc8196d41";
370 478 };
479 meta = {
480 license = [ pkgs.lib.licenses.bsdOriginal ];
481 };
371 482 };
372 483 colander = super.buildPythonPackage {
373 484 name = "colander-1.2";
374 485 buildInputs = with self; [];
375 486 doCheck = false;
376 487 propagatedBuildInputs = with self; [translationstring iso8601];
377 488 src = fetchurl {
378 489 url = "https://pypi.python.org/packages/14/23/c9ceba07a6a1dc0eefbb215fc0dc64aabc2b22ee756bc0f0c13278fa0887/colander-1.2.tar.gz";
379 490 md5 = "83db21b07936a0726e588dae1914b9ed";
380 491 };
492 meta = {
493 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
494 };
381 495 };
382 496 configobj = super.buildPythonPackage {
383 497 name = "configobj-5.0.6";
384 498 buildInputs = with self; [];
385 499 doCheck = false;
386 500 propagatedBuildInputs = with self; [six];
387 501 src = fetchurl {
388 502 url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz";
389 503 md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6";
390 504 };
505 meta = {
506 license = [ pkgs.lib.licenses.bsdOriginal ];
507 };
391 508 };
392 509 cov-core = super.buildPythonPackage {
393 510 name = "cov-core-1.15.0";
394 511 buildInputs = with self; [];
395 512 doCheck = false;
396 513 propagatedBuildInputs = with self; [coverage];
397 514 src = fetchurl {
398 515 url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
399 516 md5 = "f519d4cb4c4e52856afb14af52919fe6";
400 517 };
518 meta = {
519 license = [ pkgs.lib.licenses.mit ];
520 };
401 521 };
402 522 coverage = super.buildPythonPackage {
403 523 name = "coverage-3.7.1";
404 524 buildInputs = with self; [];
405 525 doCheck = false;
406 526 propagatedBuildInputs = with self; [];
407 527 src = fetchurl {
408 528 url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz";
409 529 md5 = "c47b36ceb17eaff3ecfab3bcd347d0df";
410 530 };
531 meta = {
532 license = [ pkgs.lib.licenses.bsdOriginal ];
533 };
411 534 };
412 535 cssselect = super.buildPythonPackage {
413 536 name = "cssselect-0.9.1";
414 537 buildInputs = with self; [];
415 538 doCheck = false;
416 539 propagatedBuildInputs = with self; [];
417 540 src = fetchurl {
418 541 url = "https://pypi.python.org/packages/aa/e5/9ee1460d485b94a6d55732eb7ad5b6c084caf73dd6f9cb0bb7d2a78fafe8/cssselect-0.9.1.tar.gz";
419 542 md5 = "c74f45966277dc7a0f768b9b0f3522ac";
420 543 };
544 meta = {
545 license = [ pkgs.lib.licenses.bsdOriginal ];
546 };
421 547 };
422 548 decorator = super.buildPythonPackage {
423 549 name = "decorator-3.4.2";
424 550 buildInputs = with self; [];
425 551 doCheck = false;
426 552 propagatedBuildInputs = with self; [];
427 553 src = fetchurl {
428 554 url = "https://pypi.python.org/packages/35/3a/42566eb7a2cbac774399871af04e11d7ae3fc2579e7dae85213b8d1d1c57/decorator-3.4.2.tar.gz";
429 555 md5 = "9e0536870d2b83ae27d58dbf22582f4d";
430 556 };
557 meta = {
558 license = [ pkgs.lib.licenses.bsdOriginal ];
559 };
431 560 };
432 561 docutils = super.buildPythonPackage {
433 562 name = "docutils-0.12";
434 563 buildInputs = with self; [];
435 564 doCheck = false;
436 565 propagatedBuildInputs = with self; [];
437 566 src = fetchurl {
438 567 url = "https://pypi.python.org/packages/37/38/ceda70135b9144d84884ae2fc5886c6baac4edea39550f28bcd144c1234d/docutils-0.12.tar.gz";
439 568 md5 = "4622263b62c5c771c03502afa3157768";
440 569 };
570 meta = {
571 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.publicDomain pkgs.lib.licenses.gpl1 { fullName = "public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)"; } pkgs.lib.licenses.psfl ];
572 };
441 573 };
442 574 dogpile.cache = super.buildPythonPackage {
443 575 name = "dogpile.cache-0.5.7";
444 576 buildInputs = with self; [];
445 577 doCheck = false;
446 578 propagatedBuildInputs = with self; [dogpile.core];
447 579 src = fetchurl {
448 580 url = "https://pypi.python.org/packages/07/74/2a83bedf758156d9c95d112691bbad870d3b77ccbcfb781b4ef836ea7d96/dogpile.cache-0.5.7.tar.gz";
449 581 md5 = "3e58ce41af574aab41d78e9c4190f194";
450 582 };
583 meta = {
584 license = [ pkgs.lib.licenses.bsdOriginal ];
585 };
451 586 };
452 587 dogpile.core = super.buildPythonPackage {
453 588 name = "dogpile.core-0.4.1";
454 589 buildInputs = with self; [];
455 590 doCheck = false;
456 591 propagatedBuildInputs = with self; [];
457 592 src = fetchurl {
458 593 url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
459 594 md5 = "01cb19f52bba3e95c9b560f39341f045";
460 595 };
596 meta = {
597 license = [ pkgs.lib.licenses.bsdOriginal ];
598 };
461 599 };
462 600 dulwich = super.buildPythonPackage {
463 601 name = "dulwich-0.12.0";
464 602 buildInputs = with self; [];
465 603 doCheck = false;
466 604 propagatedBuildInputs = with self; [];
467 605 src = fetchurl {
468 606 url = "https://pypi.python.org/packages/6f/04/fbe561b6d45c0ec758330d5b7f5ba4b6cb4f1ca1ab49859d2fc16320da75/dulwich-0.12.0.tar.gz";
469 607 md5 = "f3a8a12bd9f9dd8c233e18f3d49436fa";
470 608 };
609 meta = {
610 license = [ pkgs.lib.licenses.gpl2Plus ];
611 };
471 612 };
472 613 ecdsa = super.buildPythonPackage {
473 614 name = "ecdsa-0.11";
474 615 buildInputs = with self; [];
475 616 doCheck = false;
476 617 propagatedBuildInputs = with self; [];
477 618 src = fetchurl {
478 619 url = "https://pypi.python.org/packages/6c/3f/92fe5dcdcaa7bd117be21e5520c9a54375112b66ec000d209e9e9519fad1/ecdsa-0.11.tar.gz";
479 620 md5 = "8ef586fe4dbb156697d756900cb41d7c";
480 621 };
622 meta = {
623 license = [ pkgs.lib.licenses.mit ];
624 };
481 625 };
482 626 elasticsearch = super.buildPythonPackage {
483 627 name = "elasticsearch-2.3.0";
484 628 buildInputs = with self; [];
485 629 doCheck = false;
486 630 propagatedBuildInputs = with self; [urllib3];
487 631 src = fetchurl {
488 632 url = "https://pypi.python.org/packages/10/35/5fd52c5f0b0ee405ed4b5195e8bce44c5e041787680dc7b94b8071cac600/elasticsearch-2.3.0.tar.gz";
489 633 md5 = "2550f3b51629cf1ef9636608af92c340";
490 634 };
635 meta = {
636 license = [ pkgs.lib.licenses.asl20 ];
637 };
491 638 };
492 639 elasticsearch-dsl = super.buildPythonPackage {
493 640 name = "elasticsearch-dsl-2.0.0";
494 641 buildInputs = with self; [];
495 642 doCheck = false;
496 643 propagatedBuildInputs = with self; [six python-dateutil elasticsearch];
497 644 src = fetchurl {
498 645 url = "https://pypi.python.org/packages/4e/5d/e788ae8dbe2ff4d13426db0a027533386a5c276c77a2654dc0e2007ce04a/elasticsearch-dsl-2.0.0.tar.gz";
499 646 md5 = "4cdfec81bb35383dd3b7d02d7dc5ee68";
500 647 };
648 meta = {
649 license = [ pkgs.lib.licenses.asl20 ];
650 };
501 651 };
502 652 flake8 = super.buildPythonPackage {
503 653 name = "flake8-2.4.1";
504 654 buildInputs = with self; [];
505 655 doCheck = false;
506 656 propagatedBuildInputs = with self; [pyflakes pep8 mccabe];
507 657 src = fetchurl {
508 658 url = "https://pypi.python.org/packages/8f/b5/9a73c66c7dba273bac8758398f060c008a25f3e84531063b42503b5d0a95/flake8-2.4.1.tar.gz";
509 659 md5 = "ed45d3db81a3b7c88bd63c6e37ca1d65";
510 660 };
661 meta = {
662 license = [ pkgs.lib.licenses.mit ];
663 };
511 664 };
512 665 future = super.buildPythonPackage {
513 666 name = "future-0.14.3";
514 667 buildInputs = with self; [];
515 668 doCheck = false;
516 669 propagatedBuildInputs = with self; [];
517 670 src = fetchurl {
518 671 url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
519 672 md5 = "e94079b0bd1fc054929e8769fc0f6083";
520 673 };
674 meta = {
675 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
676 };
521 677 };
522 678 futures = super.buildPythonPackage {
523 679 name = "futures-3.0.2";
524 680 buildInputs = with self; [];
525 681 doCheck = false;
526 682 propagatedBuildInputs = with self; [];
527 683 src = fetchurl {
528 684 url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
529 685 md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a";
530 686 };
687 meta = {
688 license = [ pkgs.lib.licenses.bsdOriginal ];
689 };
531 690 };
532 691 gnureadline = super.buildPythonPackage {
533 692 name = "gnureadline-6.3.3";
534 693 buildInputs = with self; [];
535 694 doCheck = false;
536 695 propagatedBuildInputs = with self; [];
537 696 src = fetchurl {
538 697 url = "https://pypi.python.org/packages/3a/ee/2c3f568b0a74974791ac590ec742ef6133e2fbd287a074ba72a53fa5e97c/gnureadline-6.3.3.tar.gz";
539 698 md5 = "c4af83c9a3fbeac8f2da9b5a7c60e51c";
540 699 };
700 meta = {
701 license = [ pkgs.lib.licenses.gpl1 ];
702 };
541 703 };
542 704 gprof2dot = super.buildPythonPackage {
543 name = "gprof2dot-2015.12.01";
705 name = "gprof2dot-2015.12.1";
544 706 buildInputs = with self; [];
545 707 doCheck = false;
546 708 propagatedBuildInputs = with self; [];
547 709 src = fetchurl {
548 710 url = "https://pypi.python.org/packages/b9/34/7bf93c1952d40fa5c95ad963f4d8344b61ef58558632402eca18e6c14127/gprof2dot-2015.12.1.tar.gz";
549 711 md5 = "e23bf4e2f94db032750c193384b4165b";
550 712 };
713 meta = {
714 license = [ { fullName = "LGPL"; } ];
715 };
551 716 };
552 717 greenlet = super.buildPythonPackage {
553 718 name = "greenlet-0.4.9";
554 719 buildInputs = with self; [];
555 720 doCheck = false;
556 721 propagatedBuildInputs = with self; [];
557 722 src = fetchurl {
558 723 url = "https://pypi.python.org/packages/4e/3d/9d421539b74e33608b245092870156b2e171fb49f2b51390aa4641eecb4a/greenlet-0.4.9.zip";
559 724 md5 = "c6659cdb2a5e591723e629d2eef22e82";
560 725 };
726 meta = {
727 license = [ pkgs.lib.licenses.mit ];
728 };
561 729 };
562 730 gunicorn = super.buildPythonPackage {
563 731 name = "gunicorn-19.6.0";
564 732 buildInputs = with self; [];
565 733 doCheck = false;
566 734 propagatedBuildInputs = with self; [];
567 735 src = fetchurl {
568 736 url = "https://pypi.python.org/packages/84/ce/7ea5396efad1cef682bbc4068e72a0276341d9d9d0f501da609fab9fcb80/gunicorn-19.6.0.tar.gz";
569 737 md5 = "338e5e8a83ea0f0625f768dba4597530";
570 738 };
739 meta = {
740 license = [ pkgs.lib.licenses.mit ];
741 };
571 742 };
572 743 infrae.cache = super.buildPythonPackage {
573 744 name = "infrae.cache-1.0.1";
574 745 buildInputs = with self; [];
575 746 doCheck = false;
576 747 propagatedBuildInputs = with self; [Beaker repoze.lru];
577 748 src = fetchurl {
578 749 url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
579 750 md5 = "b09076a766747e6ed2a755cc62088e32";
580 751 };
752 meta = {
753 license = [ pkgs.lib.licenses.zpt21 ];
754 };
581 755 };
582 756 invoke = super.buildPythonPackage {
583 name = "invoke-0.11.1";
757 name = "invoke-0.13.0";
584 758 buildInputs = with self; [];
585 759 doCheck = false;
586 760 propagatedBuildInputs = with self; [];
587 761 src = fetchurl {
588 url = "https://pypi.python.org/packages/d3/bb/36a5558ea19882073def7b0edeef4a0e6282056fed96506dd10b1d532bd4/invoke-0.11.1.tar.gz";
589 md5 = "3d4ecbe26779ceef1046ecf702c9c4a8";
762 url = "https://pypi.python.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
763 md5 = "c0d1ed4bfb34eaab551662d8cfee6540";
764 };
765 meta = {
766 license = [ pkgs.lib.licenses.bsdOriginal ];
590 767 };
591 768 };
592 769 ipdb = super.buildPythonPackage {
593 770 name = "ipdb-0.8";
594 771 buildInputs = with self; [];
595 772 doCheck = false;
596 773 propagatedBuildInputs = with self; [ipython];
597 774 src = fetchurl {
598 775 url = "https://pypi.python.org/packages/f0/25/d7dd430ced6cd8dc242a933c8682b5dbf32eb4011d82f87e34209e5ec845/ipdb-0.8.zip";
599 776 md5 = "96dca0712efa01aa5eaf6b22071dd3ed";
600 777 };
778 meta = {
779 license = [ pkgs.lib.licenses.gpl1 ];
780 };
601 781 };
602 782 ipython = super.buildPythonPackage {
603 783 name = "ipython-3.1.0";
604 784 buildInputs = with self; [];
605 785 doCheck = false;
606 786 propagatedBuildInputs = with self; [];
607 787 src = fetchurl {
608 788 url = "https://pypi.python.org/packages/06/91/120c0835254c120af89f066afaabf81289bc2726c1fc3ca0555df6882f58/ipython-3.1.0.tar.gz";
609 789 md5 = "a749d90c16068687b0ec45a27e72ef8f";
610 790 };
791 meta = {
792 license = [ pkgs.lib.licenses.bsdOriginal ];
793 };
611 794 };
612 795 iso8601 = super.buildPythonPackage {
613 796 name = "iso8601-0.1.11";
614 797 buildInputs = with self; [];
615 798 doCheck = false;
616 799 propagatedBuildInputs = with self; [];
617 800 src = fetchurl {
618 801 url = "https://pypi.python.org/packages/c0/75/c9209ee4d1b5975eb8c2cba4428bde6b61bd55664a98290dd015cdb18e98/iso8601-0.1.11.tar.gz";
619 802 md5 = "b06d11cd14a64096f907086044f0fe38";
620 803 };
804 meta = {
805 license = [ pkgs.lib.licenses.mit ];
806 };
621 807 };
622 808 itsdangerous = super.buildPythonPackage {
623 809 name = "itsdangerous-0.24";
624 810 buildInputs = with self; [];
625 811 doCheck = false;
626 812 propagatedBuildInputs = with self; [];
627 813 src = fetchurl {
628 814 url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
629 815 md5 = "a3d55aa79369aef5345c036a8a26307f";
630 816 };
817 meta = {
818 license = [ pkgs.lib.licenses.bsdOriginal ];
819 };
631 820 };
632 821 kombu = super.buildPythonPackage {
633 822 name = "kombu-1.5.1";
634 823 buildInputs = with self; [];
635 824 doCheck = false;
636 825 propagatedBuildInputs = with self; [anyjson amqplib];
637 826 src = fetchurl {
638 827 url = "https://pypi.python.org/packages/19/53/74bf2a624644b45f0850a638752514fc10a8e1cbd738f10804951a6df3f5/kombu-1.5.1.tar.gz";
639 828 md5 = "50662f3c7e9395b3d0721fb75d100b63";
640 829 };
830 meta = {
831 license = [ pkgs.lib.licenses.bsdOriginal ];
832 };
641 833 };
642 834 lxml = super.buildPythonPackage {
643 835 name = "lxml-3.4.4";
644 836 buildInputs = with self; [];
645 837 doCheck = false;
646 838 propagatedBuildInputs = with self; [];
647 839 src = fetchurl {
648 840 url = "https://pypi.python.org/packages/63/c7/4f2a2a4ad6c6fa99b14be6b3c1cece9142e2d915aa7c43c908677afc8fa4/lxml-3.4.4.tar.gz";
649 841 md5 = "a9a65972afc173ec7a39c585f4eea69c";
650 842 };
843 meta = {
844 license = [ pkgs.lib.licenses.bsdOriginal ];
845 };
651 846 };
652 847 mccabe = super.buildPythonPackage {
653 848 name = "mccabe-0.3";
654 849 buildInputs = with self; [];
655 850 doCheck = false;
656 851 propagatedBuildInputs = with self; [];
657 852 src = fetchurl {
658 853 url = "https://pypi.python.org/packages/c9/2e/75231479e11a906b64ac43bad9d0bb534d00080b18bdca8db9da46e1faf7/mccabe-0.3.tar.gz";
659 854 md5 = "81640948ff226f8c12b3277059489157";
660 855 };
856 meta = {
857 license = [ { fullName = "Expat license"; } pkgs.lib.licenses.mit ];
858 };
661 859 };
662 860 meld3 = super.buildPythonPackage {
663 861 name = "meld3-1.0.2";
664 862 buildInputs = with self; [];
665 863 doCheck = false;
666 864 propagatedBuildInputs = with self; [];
667 865 src = fetchurl {
668 866 url = "https://pypi.python.org/packages/45/a0/317c6422b26c12fe0161e936fc35f36552069ba8e6f7ecbd99bbffe32a5f/meld3-1.0.2.tar.gz";
669 867 md5 = "3ccc78cd79cffd63a751ad7684c02c91";
670 868 };
869 meta = {
870 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
871 };
671 872 };
672 873 mock = super.buildPythonPackage {
673 874 name = "mock-1.0.1";
674 875 buildInputs = with self; [];
675 876 doCheck = false;
676 877 propagatedBuildInputs = with self; [];
677 878 src = fetchurl {
678 879 url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip";
679 880 md5 = "869f08d003c289a97c1a6610faf5e913";
680 881 };
882 meta = {
883 license = [ pkgs.lib.licenses.bsdOriginal ];
884 };
681 885 };
682 886 msgpack-python = super.buildPythonPackage {
683 887 name = "msgpack-python-0.4.6";
684 888 buildInputs = with self; [];
685 889 doCheck = false;
686 890 propagatedBuildInputs = with self; [];
687 891 src = fetchurl {
688 892 url = "https://pypi.python.org/packages/15/ce/ff2840885789ef8035f66cd506ea05bdb228340307d5e71a7b1e3f82224c/msgpack-python-0.4.6.tar.gz";
689 893 md5 = "8b317669314cf1bc881716cccdaccb30";
690 894 };
895 meta = {
896 license = [ pkgs.lib.licenses.asl20 ];
897 };
691 898 };
692 899 nose = super.buildPythonPackage {
693 900 name = "nose-1.3.6";
694 901 buildInputs = with self; [];
695 902 doCheck = false;
696 903 propagatedBuildInputs = with self; [];
697 904 src = fetchurl {
698 905 url = "https://pypi.python.org/packages/70/c7/469e68148d17a0d3db5ed49150242fd70a74a8147b8f3f8b87776e028d99/nose-1.3.6.tar.gz";
699 906 md5 = "0ca546d81ca8309080fc80cb389e7a16";
700 907 };
908 meta = {
909 license = [ { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "GNU LGPL"; } ];
910 };
701 911 };
702 912 objgraph = super.buildPythonPackage {
703 913 name = "objgraph-2.0.0";
704 914 buildInputs = with self; [];
705 915 doCheck = false;
706 916 propagatedBuildInputs = with self; [];
707 917 src = fetchurl {
708 918 url = "https://pypi.python.org/packages/d7/33/ace750b59247496ed769b170586c5def7202683f3d98e737b75b767ff29e/objgraph-2.0.0.tar.gz";
709 919 md5 = "25b0d5e5adc74aa63ead15699614159c";
710 920 };
921 meta = {
922 license = [ pkgs.lib.licenses.mit ];
923 };
711 924 };
712 925 packaging = super.buildPythonPackage {
713 926 name = "packaging-15.2";
714 927 buildInputs = with self; [];
715 928 doCheck = false;
716 929 propagatedBuildInputs = with self; [];
717 930 src = fetchurl {
718 931 url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
719 932 md5 = "c16093476f6ced42128bf610e5db3784";
720 933 };
934 meta = {
935 license = [ pkgs.lib.licenses.asl20 ];
936 };
721 937 };
722 938 paramiko = super.buildPythonPackage {
723 939 name = "paramiko-1.15.1";
724 940 buildInputs = with self; [];
725 941 doCheck = false;
726 942 propagatedBuildInputs = with self; [pycrypto ecdsa];
727 943 src = fetchurl {
728 944 url = "https://pypi.python.org/packages/04/2b/a22d2a560c1951abbbf95a0628e245945565f70dc082d9e784666887222c/paramiko-1.15.1.tar.gz";
729 945 md5 = "48c274c3f9b1282932567b21f6acf3b5";
730 946 };
947 meta = {
948 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
949 };
731 950 };
732 951 pep8 = super.buildPythonPackage {
733 952 name = "pep8-1.5.7";
734 953 buildInputs = with self; [];
735 954 doCheck = false;
736 955 propagatedBuildInputs = with self; [];
737 956 src = fetchurl {
738 957 url = "https://pypi.python.org/packages/8b/de/259f5e735897ada1683489dd514b2a1c91aaa74e5e6b68f80acf128a6368/pep8-1.5.7.tar.gz";
739 958 md5 = "f6adbdd69365ecca20513c709f9b7c93";
740 959 };
960 meta = {
961 license = [ { fullName = "Expat license"; } pkgs.lib.licenses.mit ];
962 };
741 963 };
742 964 psutil = super.buildPythonPackage {
743 965 name = "psutil-2.2.1";
744 966 buildInputs = with self; [];
745 967 doCheck = false;
746 968 propagatedBuildInputs = with self; [];
747 969 src = fetchurl {
748 970 url = "https://pypi.python.org/packages/df/47/ee54ef14dd40f8ce831a7581001a5096494dc99fe71586260ca6b531fe86/psutil-2.2.1.tar.gz";
749 971 md5 = "1a2b58cd9e3a53528bb6148f0c4d5244";
750 972 };
973 meta = {
974 license = [ pkgs.lib.licenses.bsdOriginal ];
975 };
751 976 };
752 977 psycopg2 = super.buildPythonPackage {
753 978 name = "psycopg2-2.6";
754 979 buildInputs = with self; [];
755 980 doCheck = false;
756 981 propagatedBuildInputs = with self; [];
757 982 src = fetchurl {
758 983 url = "https://pypi.python.org/packages/dd/c7/9016ff8ff69da269b1848276eebfb264af5badf6b38caad805426771f04d/psycopg2-2.6.tar.gz";
759 984 md5 = "fbbb039a8765d561a1c04969bbae7c74";
760 985 };
986 meta = {
987 license = [ pkgs.lib.licenses.zpt21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
988 };
761 989 };
762 990 py = super.buildPythonPackage {
763 991 name = "py-1.4.29";
764 992 buildInputs = with self; [];
765 993 doCheck = false;
766 994 propagatedBuildInputs = with self; [];
767 995 src = fetchurl {
768 996 url = "https://pypi.python.org/packages/2a/bc/a1a4a332ac10069b8e5e25136a35e08a03f01fd6ab03d819889d79a1fd65/py-1.4.29.tar.gz";
769 997 md5 = "c28e0accba523a29b35a48bb703fb96c";
770 998 };
999 meta = {
1000 license = [ pkgs.lib.licenses.mit ];
1001 };
771 1002 };
772 1003 py-bcrypt = super.buildPythonPackage {
773 1004 name = "py-bcrypt-0.4";
774 1005 buildInputs = with self; [];
775 1006 doCheck = false;
776 1007 propagatedBuildInputs = with self; [];
777 1008 src = fetchurl {
778 1009 url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
779 1010 md5 = "dd8b367d6b716a2ea2e72392525f4e36";
780 1011 };
1012 meta = {
1013 license = [ pkgs.lib.licenses.bsdOriginal ];
1014 };
781 1015 };
782 1016 pycrypto = super.buildPythonPackage {
783 1017 name = "pycrypto-2.6.1";
784 1018 buildInputs = with self; [];
785 1019 doCheck = false;
786 1020 propagatedBuildInputs = with self; [];
787 1021 src = fetchurl {
788 1022 url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
789 1023 md5 = "55a61a054aa66812daf5161a0d5d7eda";
790 1024 };
1025 meta = {
1026 license = [ pkgs.lib.licenses.publicDomain ];
1027 };
791 1028 };
792 1029 pycurl = super.buildPythonPackage {
793 1030 name = "pycurl-7.19.5";
794 1031 buildInputs = with self; [];
795 1032 doCheck = false;
796 1033 propagatedBuildInputs = with self; [];
797 1034 src = fetchurl {
798 1035 url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz";
799 1036 md5 = "47b4eac84118e2606658122104e62072";
800 1037 };
1038 meta = {
1039 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1040 };
801 1041 };
802 1042 pyflakes = super.buildPythonPackage {
803 1043 name = "pyflakes-0.8.1";
804 1044 buildInputs = with self; [];
805 1045 doCheck = false;
806 1046 propagatedBuildInputs = with self; [];
807 1047 src = fetchurl {
808 1048 url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
809 1049 md5 = "905fe91ad14b912807e8fdc2ac2e2c23";
810 1050 };
1051 meta = {
1052 license = [ pkgs.lib.licenses.mit ];
1053 };
811 1054 };
812 1055 pyparsing = super.buildPythonPackage {
813 1056 name = "pyparsing-1.5.7";
814 1057 buildInputs = with self; [];
815 1058 doCheck = false;
816 1059 propagatedBuildInputs = with self; [];
817 1060 src = fetchurl {
818 1061 url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip";
819 1062 md5 = "b86854857a368d6ccb4d5b6e76d0637f";
820 1063 };
1064 meta = {
1065 license = [ pkgs.lib.licenses.mit ];
1066 };
821 1067 };
822 1068 pyramid = super.buildPythonPackage {
823 1069 name = "pyramid-1.6.1";
824 1070 buildInputs = with self; [];
825 1071 doCheck = false;
826 1072 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy];
827 1073 src = fetchurl {
828 1074 url = "https://pypi.python.org/packages/30/b3/fcc4a2a4800cbf21989e00454b5828cf1f7fe35c63e0810b350e56d4c475/pyramid-1.6.1.tar.gz";
829 1075 md5 = "b18688ff3cc33efdbb098a35b45dd122";
830 1076 };
1077 meta = {
1078 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1079 };
831 1080 };
832 1081 pyramid-beaker = super.buildPythonPackage {
833 1082 name = "pyramid-beaker-0.8";
834 1083 buildInputs = with self; [];
835 1084 doCheck = false;
836 1085 propagatedBuildInputs = with self; [pyramid Beaker];
837 1086 src = fetchurl {
838 1087 url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
839 1088 md5 = "22f14be31b06549f80890e2c63a93834";
840 1089 };
1090 meta = {
1091 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1092 };
841 1093 };
842 1094 pyramid-debugtoolbar = super.buildPythonPackage {
843 1095 name = "pyramid-debugtoolbar-2.4.2";
844 1096 buildInputs = with self; [];
845 1097 doCheck = false;
846 1098 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments];
847 1099 src = fetchurl {
848 1100 url = "https://pypi.python.org/packages/89/00/ed5426ee41ed747ba3ffd30e8230841a6878286ea67d480b1444d24f06a2/pyramid_debugtoolbar-2.4.2.tar.gz";
849 1101 md5 = "073ea67086cc4bd5decc3a000853642d";
850 1102 };
1103 meta = {
1104 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1105 };
851 1106 };
852 1107 pyramid-jinja2 = super.buildPythonPackage {
853 1108 name = "pyramid-jinja2-2.5";
854 1109 buildInputs = with self; [];
855 1110 doCheck = false;
856 1111 propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe];
857 1112 src = fetchurl {
858 1113 url = "https://pypi.python.org/packages/a1/80/595e26ffab7deba7208676b6936b7e5a721875710f982e59899013cae1ed/pyramid_jinja2-2.5.tar.gz";
859 1114 md5 = "07cb6547204ac5e6f0b22a954ccee928";
860 1115 };
1116 meta = {
1117 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1118 };
861 1119 };
862 1120 pyramid-mako = super.buildPythonPackage {
863 1121 name = "pyramid-mako-1.0.2";
864 1122 buildInputs = with self; [];
865 1123 doCheck = false;
866 1124 propagatedBuildInputs = with self; [pyramid Mako];
867 1125 src = fetchurl {
868 1126 url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
869 1127 md5 = "ee25343a97eb76bd90abdc2a774eb48a";
870 1128 };
1129 meta = {
1130 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1131 };
871 1132 };
872 1133 pysqlite = super.buildPythonPackage {
873 1134 name = "pysqlite-2.6.3";
874 1135 buildInputs = with self; [];
875 1136 doCheck = false;
876 1137 propagatedBuildInputs = with self; [];
877 1138 src = fetchurl {
878 1139 url = "https://pypi.python.org/packages/5c/a6/1c429cd4c8069cf4bfbd0eb4d592b3f4042155a8202df83d7e9b93aa3dc2/pysqlite-2.6.3.tar.gz";
879 1140 md5 = "7ff1cedee74646b50117acff87aa1cfa";
880 1141 };
1142 meta = {
1143 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1144 };
881 1145 };
882 1146 pytest = super.buildPythonPackage {
883 1147 name = "pytest-2.8.5";
884 1148 buildInputs = with self; [];
885 1149 doCheck = false;
886 1150 propagatedBuildInputs = with self; [py];
887 1151 src = fetchurl {
888 1152 url = "https://pypi.python.org/packages/b1/3d/d7ea9b0c51e0cacded856e49859f0a13452747491e842c236bbab3714afe/pytest-2.8.5.zip";
889 1153 md5 = "8493b06f700862f1294298d6c1b715a9";
890 1154 };
1155 meta = {
1156 license = [ pkgs.lib.licenses.mit ];
1157 };
891 1158 };
892 1159 pytest-catchlog = super.buildPythonPackage {
893 1160 name = "pytest-catchlog-1.2.2";
894 1161 buildInputs = with self; [];
895 1162 doCheck = false;
896 1163 propagatedBuildInputs = with self; [py pytest];
897 1164 src = fetchurl {
898 1165 url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip";
899 1166 md5 = "09d890c54c7456c818102b7ff8c182c8";
900 1167 };
1168 meta = {
1169 license = [ pkgs.lib.licenses.mit ];
1170 };
901 1171 };
902 1172 pytest-cov = super.buildPythonPackage {
903 1173 name = "pytest-cov-1.8.1";
904 1174 buildInputs = with self; [];
905 1175 doCheck = false;
906 1176 propagatedBuildInputs = with self; [py pytest coverage cov-core];
907 1177 src = fetchurl {
908 1178 url = "https://pypi.python.org/packages/11/4b/b04646e97f1721878eb21e9f779102d84dd044d324382263b1770a3e4838/pytest-cov-1.8.1.tar.gz";
909 1179 md5 = "76c778afa2494088270348be42d759fc";
910 1180 };
1181 meta = {
1182 license = [ pkgs.lib.licenses.mit ];
1183 };
911 1184 };
912 1185 pytest-profiling = super.buildPythonPackage {
913 1186 name = "pytest-profiling-1.0.1";
914 1187 buildInputs = with self; [];
915 1188 doCheck = false;
916 1189 propagatedBuildInputs = with self; [six pytest gprof2dot];
917 1190 src = fetchurl {
918 1191 url = "https://pypi.python.org/packages/d8/67/8ffab73406e22870e07fa4dc8dce1d7689b26dba8efd00161c9b6fc01ec0/pytest-profiling-1.0.1.tar.gz";
919 1192 md5 = "354404eb5b3fd4dc5eb7fffbb3d9b68b";
920 1193 };
1194 meta = {
1195 license = [ pkgs.lib.licenses.mit ];
1196 };
921 1197 };
922 1198 pytest-runner = super.buildPythonPackage {
923 1199 name = "pytest-runner-2.7.1";
924 1200 buildInputs = with self; [];
925 1201 doCheck = false;
926 1202 propagatedBuildInputs = with self; [];
927 1203 src = fetchurl {
928 1204 url = "https://pypi.python.org/packages/99/6b/c4ff4418d3424d4475b7af60724fd4a5cdd91ed8e489dc9443281f0052bc/pytest-runner-2.7.1.tar.gz";
929 1205 md5 = "e56f0bc8d79a6bd91772b44ef4215c7e";
930 1206 };
1207 meta = {
1208 license = [ pkgs.lib.licenses.mit ];
1209 };
931 1210 };
932 1211 pytest-timeout = super.buildPythonPackage {
933 1212 name = "pytest-timeout-0.4";
934 1213 buildInputs = with self; [];
935 1214 doCheck = false;
936 1215 propagatedBuildInputs = with self; [pytest];
937 1216 src = fetchurl {
938 1217 url = "https://pypi.python.org/packages/24/48/5f6bd4b8026a26e1dd427243d560a29a0f1b24a5c7cffca4bf049a7bb65b/pytest-timeout-0.4.tar.gz";
939 1218 md5 = "03b28aff69cbbfb959ed35ade5fde262";
940 1219 };
1220 meta = {
1221 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1222 };
941 1223 };
942 1224 python-dateutil = super.buildPythonPackage {
943 1225 name = "python-dateutil-1.5";
944 1226 buildInputs = with self; [];
945 1227 doCheck = false;
946 1228 propagatedBuildInputs = with self; [];
947 1229 src = fetchurl {
948 1230 url = "https://pypi.python.org/packages/b4/7c/df59c89a753eb33c7c44e1dd42de0e9bc2ccdd5a4d576e0bfad97cc280cb/python-dateutil-1.5.tar.gz";
949 1231 md5 = "0dcb1de5e5cad69490a3b6ab63f0cfa5";
950 1232 };
1233 meta = {
1234 license = [ pkgs.lib.licenses.psfl ];
1235 };
951 1236 };
952 1237 python-editor = super.buildPythonPackage {
953 1238 name = "python-editor-1.0.1";
954 1239 buildInputs = with self; [];
955 1240 doCheck = false;
956 1241 propagatedBuildInputs = with self; [];
957 1242 src = fetchurl {
958 1243 url = "https://pypi.python.org/packages/2b/c0/df7b87d5cf016f82eab3b05cd35f53287c1178ad8c42bfb6fa61b89b22f6/python-editor-1.0.1.tar.gz";
959 1244 md5 = "e1fa63535b40e022fa4fd646fd8b511a";
960 1245 };
1246 meta = {
1247 license = [ pkgs.lib.licenses.asl20 ];
1248 };
961 1249 };
962 1250 python-ldap = super.buildPythonPackage {
963 1251 name = "python-ldap-2.4.19";
964 1252 buildInputs = with self; [];
965 1253 doCheck = false;
966 1254 propagatedBuildInputs = with self; [setuptools];
967 1255 src = fetchurl {
968 1256 url = "https://pypi.python.org/packages/42/81/1b64838c82e64f14d4e246ff00b52e650a35c012551b891ada2b85d40737/python-ldap-2.4.19.tar.gz";
969 1257 md5 = "b941bf31d09739492aa19ef679e94ae3";
970 1258 };
1259 meta = {
1260 license = [ pkgs.lib.licenses.psfl ];
1261 };
971 1262 };
972 1263 python-memcached = super.buildPythonPackage {
973 1264 name = "python-memcached-1.57";
974 1265 buildInputs = with self; [];
975 1266 doCheck = false;
976 1267 propagatedBuildInputs = with self; [six];
977 1268 src = fetchurl {
978 1269 url = "https://pypi.python.org/packages/52/9d/eebc0dcbc5c7c66840ad207dfc1baa376dadb74912484bff73819cce01e6/python-memcached-1.57.tar.gz";
979 1270 md5 = "de21f64b42b2d961f3d4ad7beb5468a1";
980 1271 };
1272 meta = {
1273 license = [ pkgs.lib.licenses.psfl ];
1274 };
981 1275 };
982 1276 python-pam = super.buildPythonPackage {
983 1277 name = "python-pam-1.8.2";
984 1278 buildInputs = with self; [];
985 1279 doCheck = false;
986 1280 propagatedBuildInputs = with self; [];
987 1281 src = fetchurl {
988 1282 url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz";
989 1283 md5 = "db71b6b999246fb05d78ecfbe166629d";
990 1284 };
1285 meta = {
1286 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1287 };
991 1288 };
992 1289 pytz = super.buildPythonPackage {
993 1290 name = "pytz-2015.4";
994 1291 buildInputs = with self; [];
995 1292 doCheck = false;
996 1293 propagatedBuildInputs = with self; [];
997 1294 src = fetchurl {
998 1295 url = "https://pypi.python.org/packages/7e/1a/f43b5c92df7b156822030fed151327ea096bcf417e45acc23bd1df43472f/pytz-2015.4.zip";
999 1296 md5 = "233f2a2b370d03f9b5911700cc9ebf3c";
1000 1297 };
1298 meta = {
1299 license = [ pkgs.lib.licenses.mit ];
1300 };
1001 1301 };
1002 1302 pyzmq = super.buildPythonPackage {
1003 1303 name = "pyzmq-14.6.0";
1004 1304 buildInputs = with self; [];
1005 1305 doCheck = false;
1006 1306 propagatedBuildInputs = with self; [];
1007 1307 src = fetchurl {
1008 1308 url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1009 1309 md5 = "395b5de95a931afa5b14c9349a5b8024";
1010 1310 };
1311 meta = {
1312 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1313 };
1011 1314 };
1012 1315 recaptcha-client = super.buildPythonPackage {
1013 1316 name = "recaptcha-client-1.0.6";
1014 1317 buildInputs = with self; [];
1015 1318 doCheck = false;
1016 1319 propagatedBuildInputs = with self; [];
1017 1320 src = fetchurl {
1018 1321 url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz";
1019 1322 md5 = "74228180f7e1fb76c4d7089160b0d919";
1020 1323 };
1324 meta = {
1325 license = [ { fullName = "MIT/X11"; } ];
1326 };
1021 1327 };
1022 1328 repoze.lru = super.buildPythonPackage {
1023 1329 name = "repoze.lru-0.6";
1024 1330 buildInputs = with self; [];
1025 1331 doCheck = false;
1026 1332 propagatedBuildInputs = with self; [];
1027 1333 src = fetchurl {
1028 1334 url = "https://pypi.python.org/packages/6e/1e/aa15cc90217e086dc8769872c8778b409812ff036bf021b15795638939e4/repoze.lru-0.6.tar.gz";
1029 1335 md5 = "2c3b64b17a8e18b405f55d46173e14dd";
1030 1336 };
1337 meta = {
1338 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1339 };
1031 1340 };
1032 1341 requests = super.buildPythonPackage {
1033 1342 name = "requests-2.9.1";
1034 1343 buildInputs = with self; [];
1035 1344 doCheck = false;
1036 1345 propagatedBuildInputs = with self; [];
1037 1346 src = fetchurl {
1038 1347 url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1039 1348 md5 = "0b7f480d19012ec52bab78292efd976d";
1040 1349 };
1350 meta = {
1351 license = [ pkgs.lib.licenses.asl20 ];
1352 };
1041 1353 };
1042 1354 rhodecode-enterprise-ce = super.buildPythonPackage {
1043 name = "rhodecode-enterprise-ce-4.1.2";
1355 name = "rhodecode-enterprise-ce-4.2.0";
1044 1356 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner];
1045 1357 doCheck = true;
1046 1358 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors psutil py-bcrypt];
1047 1359 src = ./.;
1360 meta = {
1361 license = [ { fullName = "AGPLv3, and Commercial License"; } ];
1362 };
1048 1363 };
1049 1364 rhodecode-tools = super.buildPythonPackage {
1050 1365 name = "rhodecode-tools-0.8.3";
1051 1366 buildInputs = with self; [];
1052 1367 doCheck = false;
1053 1368 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests Whoosh elasticsearch elasticsearch-dsl];
1054 1369 src = fetchurl {
1055 1370 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.8.3.zip";
1056 1371 md5 = "9acdfd71b8ddf4056057065f37ab9ccb";
1057 1372 };
1373 meta = {
1374 license = [ { fullName = "AGPLv3 and Proprietary"; } ];
1375 };
1058 1376 };
1059 1377 serpent = super.buildPythonPackage {
1060 1378 name = "serpent-1.12";
1061 1379 buildInputs = with self; [];
1062 1380 doCheck = false;
1063 1381 propagatedBuildInputs = with self; [];
1064 1382 src = fetchurl {
1065 1383 url = "https://pypi.python.org/packages/3b/19/1e0e83b47c09edaef8398655088036e7e67386b5c48770218ebb339fbbd5/serpent-1.12.tar.gz";
1066 1384 md5 = "05869ac7b062828b34f8f927f0457b65";
1067 1385 };
1386 meta = {
1387 license = [ pkgs.lib.licenses.mit ];
1388 };
1068 1389 };
1069 1390 setproctitle = super.buildPythonPackage {
1070 1391 name = "setproctitle-1.1.8";
1071 1392 buildInputs = with self; [];
1072 1393 doCheck = false;
1073 1394 propagatedBuildInputs = with self; [];
1074 1395 src = fetchurl {
1075 1396 url = "https://pypi.python.org/packages/33/c3/ad367a4f4f1ca90468863ae727ac62f6edb558fc09a003d344a02cfc6ea6/setproctitle-1.1.8.tar.gz";
1076 1397 md5 = "728f4c8c6031bbe56083a48594027edd";
1077 1398 };
1399 meta = {
1400 license = [ pkgs.lib.licenses.bsdOriginal ];
1401 };
1078 1402 };
1079 1403 setuptools = super.buildPythonPackage {
1080 1404 name = "setuptools-20.8.1";
1081 1405 buildInputs = with self; [];
1082 1406 doCheck = false;
1083 1407 propagatedBuildInputs = with self; [];
1084 1408 src = fetchurl {
1085 1409 url = "https://pypi.python.org/packages/c4/19/c1bdc88b53da654df43770f941079dbab4e4788c2dcb5658fb86259894c7/setuptools-20.8.1.zip";
1086 1410 md5 = "fe58a5cac0df20bb83942b252a4b0543";
1087 1411 };
1412 meta = {
1413 license = [ pkgs.lib.licenses.mit ];
1414 };
1088 1415 };
1089 1416 setuptools-scm = super.buildPythonPackage {
1090 1417 name = "setuptools-scm-1.11.0";
1091 1418 buildInputs = with self; [];
1092 1419 doCheck = false;
1093 1420 propagatedBuildInputs = with self; [];
1094 1421 src = fetchurl {
1095 1422 url = "https://pypi.python.org/packages/cd/5f/e3a038292358058d83d764a47d09114aa5a8003ed4529518f9e580f1a94f/setuptools_scm-1.11.0.tar.gz";
1096 1423 md5 = "4c5c896ba52e134bbc3507bac6400087";
1097 1424 };
1425 meta = {
1426 license = [ pkgs.lib.licenses.mit ];
1427 };
1098 1428 };
1099 1429 simplejson = super.buildPythonPackage {
1100 1430 name = "simplejson-3.7.2";
1101 1431 buildInputs = with self; [];
1102 1432 doCheck = false;
1103 1433 propagatedBuildInputs = with self; [];
1104 1434 src = fetchurl {
1105 1435 url = "https://pypi.python.org/packages/6d/89/7f13f099344eea9d6722779a1f165087cb559598107844b1ac5dbd831fb1/simplejson-3.7.2.tar.gz";
1106 1436 md5 = "a5fc7d05d4cb38492285553def5d4b46";
1107 1437 };
1438 meta = {
1439 license = [ pkgs.lib.licenses.mit pkgs.lib.licenses.afl21 ];
1440 };
1108 1441 };
1109 1442 six = super.buildPythonPackage {
1110 1443 name = "six-1.9.0";
1111 1444 buildInputs = with self; [];
1112 1445 doCheck = false;
1113 1446 propagatedBuildInputs = with self; [];
1114 1447 src = fetchurl {
1115 1448 url = "https://pypi.python.org/packages/16/64/1dc5e5976b17466fd7d712e59cbe9fb1e18bec153109e5ba3ed6c9102f1a/six-1.9.0.tar.gz";
1116 1449 md5 = "476881ef4012262dfc8adc645ee786c4";
1117 1450 };
1451 meta = {
1452 license = [ pkgs.lib.licenses.mit ];
1453 };
1118 1454 };
1119 1455 subprocess32 = super.buildPythonPackage {
1120 1456 name = "subprocess32-3.2.6";
1121 1457 buildInputs = with self; [];
1122 1458 doCheck = false;
1123 1459 propagatedBuildInputs = with self; [];
1124 1460 src = fetchurl {
1125 1461 url = "https://pypi.python.org/packages/28/8d/33ccbff51053f59ae6c357310cac0e79246bbed1d345ecc6188b176d72c3/subprocess32-3.2.6.tar.gz";
1126 1462 md5 = "754c5ab9f533e764f931136974b618f1";
1127 1463 };
1464 meta = {
1465 license = [ pkgs.lib.licenses.psfl ];
1466 };
1128 1467 };
1129 1468 supervisor = super.buildPythonPackage {
1130 1469 name = "supervisor-3.1.3";
1131 1470 buildInputs = with self; [];
1132 1471 doCheck = false;
1133 1472 propagatedBuildInputs = with self; [meld3];
1134 1473 src = fetchurl {
1135 1474 url = "https://pypi.python.org/packages/a6/41/65ad5bd66230b173eb4d0b8810230f3a9c59ef52ae066e540b6b99895db7/supervisor-3.1.3.tar.gz";
1136 1475 md5 = "aad263c4fbc070de63dd354864d5e552";
1137 1476 };
1477 meta = {
1478 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1479 };
1138 1480 };
1139 1481 transifex-client = super.buildPythonPackage {
1140 1482 name = "transifex-client-0.10";
1141 1483 buildInputs = with self; [];
1142 1484 doCheck = false;
1143 1485 propagatedBuildInputs = with self; [];
1144 1486 src = fetchurl {
1145 1487 url = "https://pypi.python.org/packages/f3/4e/7b925192aee656fb3e04fa6381c8b3dc40198047c3b4a356f6cfd642c809/transifex-client-0.10.tar.gz";
1146 1488 md5 = "5549538d84b8eede6b254cd81ae024fa";
1147 1489 };
1490 meta = {
1491 license = [ pkgs.lib.licenses.gpl2 ];
1492 };
1148 1493 };
1149 1494 translationstring = super.buildPythonPackage {
1150 1495 name = "translationstring-1.3";
1151 1496 buildInputs = with self; [];
1152 1497 doCheck = false;
1153 1498 propagatedBuildInputs = with self; [];
1154 1499 src = fetchurl {
1155 1500 url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
1156 1501 md5 = "a4b62e0f3c189c783a1685b3027f7c90";
1157 1502 };
1503 meta = {
1504 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
1505 };
1158 1506 };
1159 1507 trollius = super.buildPythonPackage {
1160 1508 name = "trollius-1.0.4";
1161 1509 buildInputs = with self; [];
1162 1510 doCheck = false;
1163 1511 propagatedBuildInputs = with self; [futures];
1164 1512 src = fetchurl {
1165 1513 url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz";
1166 1514 md5 = "3631a464d49d0cbfd30ab2918ef2b783";
1167 1515 };
1516 meta = {
1517 license = [ pkgs.lib.licenses.asl20 ];
1518 };
1168 1519 };
1169 1520 uWSGI = super.buildPythonPackage {
1170 1521 name = "uWSGI-2.0.11.2";
1171 1522 buildInputs = with self; [];
1172 1523 doCheck = false;
1173 1524 propagatedBuildInputs = with self; [];
1174 1525 src = fetchurl {
1175 1526 url = "https://pypi.python.org/packages/9b/78/918db0cfab0546afa580c1e565209c49aaf1476bbfe491314eadbe47c556/uwsgi-2.0.11.2.tar.gz";
1176 1527 md5 = "1f02dcbee7f6f61de4b1fd68350cf16f";
1177 1528 };
1529 meta = {
1530 license = [ pkgs.lib.licenses.gpl2 ];
1531 };
1178 1532 };
1179 1533 urllib3 = super.buildPythonPackage {
1180 1534 name = "urllib3-1.16";
1181 1535 buildInputs = with self; [];
1182 1536 doCheck = false;
1183 1537 propagatedBuildInputs = with self; [];
1184 1538 src = fetchurl {
1185 1539 url = "https://pypi.python.org/packages/3b/f0/e763169124e3f5db0926bc3dbfcd580a105f9ca44cf5d8e6c7a803c9f6b5/urllib3-1.16.tar.gz";
1186 1540 md5 = "fcaab1c5385c57deeb7053d3d7d81d59";
1187 1541 };
1542 meta = {
1543 license = [ pkgs.lib.licenses.mit ];
1544 };
1188 1545 };
1189 1546 venusian = super.buildPythonPackage {
1190 1547 name = "venusian-1.0";
1191 1548 buildInputs = with self; [];
1192 1549 doCheck = false;
1193 1550 propagatedBuildInputs = with self; [];
1194 1551 src = fetchurl {
1195 1552 url = "https://pypi.python.org/packages/86/20/1948e0dfc4930ddde3da8c33612f6a5717c0b4bc28f591a5c5cf014dd390/venusian-1.0.tar.gz";
1196 1553 md5 = "dccf2eafb7113759d60c86faf5538756";
1197 1554 };
1555 meta = {
1556 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1557 };
1198 1558 };
1199 1559 waitress = super.buildPythonPackage {
1200 1560 name = "waitress-0.8.9";
1201 1561 buildInputs = with self; [];
1202 1562 doCheck = false;
1203 1563 propagatedBuildInputs = with self; [setuptools];
1204 1564 src = fetchurl {
1205 1565 url = "https://pypi.python.org/packages/ee/65/fc9dee74a909a1187ca51e4f15ad9c4d35476e4ab5813f73421505c48053/waitress-0.8.9.tar.gz";
1206 1566 md5 = "da3f2e62b3676be5dd630703a68e2a04";
1207 1567 };
1568 meta = {
1569 license = [ pkgs.lib.licenses.zpt21 ];
1570 };
1208 1571 };
1209 1572 wsgiref = super.buildPythonPackage {
1210 1573 name = "wsgiref-0.1.2";
1211 1574 buildInputs = with self; [];
1212 1575 doCheck = false;
1213 1576 propagatedBuildInputs = with self; [];
1214 1577 src = fetchurl {
1215 1578 url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
1216 1579 md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb";
1217 1580 };
1581 meta = {
1582 license = [ { fullName = "PSF or ZPL"; } ];
1583 };
1218 1584 };
1219 1585 zope.cachedescriptors = super.buildPythonPackage {
1220 1586 name = "zope.cachedescriptors-4.0.0";
1221 1587 buildInputs = with self; [];
1222 1588 doCheck = false;
1223 1589 propagatedBuildInputs = with self; [setuptools];
1224 1590 src = fetchurl {
1225 1591 url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz";
1226 1592 md5 = "8d308de8c936792c8e758058fcb7d0f0";
1227 1593 };
1594 meta = {
1595 license = [ pkgs.lib.licenses.zpt21 ];
1596 };
1228 1597 };
1229 1598 zope.deprecation = super.buildPythonPackage {
1230 1599 name = "zope.deprecation-4.1.2";
1231 1600 buildInputs = with self; [];
1232 1601 doCheck = false;
1233 1602 propagatedBuildInputs = with self; [setuptools];
1234 1603 src = fetchurl {
1235 1604 url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz";
1236 1605 md5 = "e9a663ded58f4f9f7881beb56cae2782";
1237 1606 };
1607 meta = {
1608 license = [ pkgs.lib.licenses.zpt21 ];
1609 };
1238 1610 };
1239 1611 zope.event = super.buildPythonPackage {
1240 1612 name = "zope.event-4.0.3";
1241 1613 buildInputs = with self; [];
1242 1614 doCheck = false;
1243 1615 propagatedBuildInputs = with self; [setuptools];
1244 1616 src = fetchurl {
1245 1617 url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz";
1246 1618 md5 = "9a3780916332b18b8b85f522bcc3e249";
1247 1619 };
1620 meta = {
1621 license = [ pkgs.lib.licenses.zpt21 ];
1622 };
1248 1623 };
1249 1624 zope.interface = super.buildPythonPackage {
1250 1625 name = "zope.interface-4.1.3";
1251 1626 buildInputs = with self; [];
1252 1627 doCheck = false;
1253 1628 propagatedBuildInputs = with self; [setuptools];
1254 1629 src = fetchurl {
1255 1630 url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz";
1256 1631 md5 = "9ae3d24c0c7415deb249dd1a132f0f79";
1257 1632 };
1633 meta = {
1634 license = [ pkgs.lib.licenses.zpt21 ];
1635 };
1258 1636 };
1259 1637
1260 1638 ### Test requirements
1261 1639
1262 1640
1263 1641 }
@@ -1,231 +1,232 b''
1 1 #
2 2 # About
3 3 # =====
4 4 #
5 5 # This file defines jobs for our CI system and the attribute "build" is used
6 6 # as the input for packaging.
7 7 #
8 8 #
9 9 # CI details
10 10 # ==========
11 11 #
12 12 # This file defines an attribute set of derivations. Each of these attributes is
13 13 # then used in our CI system as one job to run. This way we keep the
14 14 # configuration for the CI jobs as well under version control.
15 15 #
16 16 # Run CI jobs locally
17 17 # -------------------
18 18 #
19 19 # Since it is all based on normal Nix derivations, the jobs can be tested
20 20 # locally with a run of "nix-build" like the following example:
21 21 #
22 22 # nix-build release.nix -A test-api -I vcsserver=~/rhodecode-vcsserver
23 23 #
24 24 # Note: Replace "~/rhodecode-vcsserver" with a path where a clone of the
25 25 # vcsserver resides.
26 26
27 27 { pkgs ? import <nixpkgs> {}
28 , doCheck ? true
28 29 }:
29 30
30 31 let
31 32
32 33 inherit (pkgs)
33 34 stdenv
34 35 system;
35 36
36 37 testing = import <nixpkgs/nixos/lib/testing.nix> {
37 38 inherit system;
38 39 };
39 40
40 41 runInMachine = testing.runInMachine;
41 42
42 43 sphinx = import ./docs/default.nix {};
43 44
44 45 mkDocs = kind: stdenv.mkDerivation {
45 46 name = kind;
46 47 srcs = [
47 48 (./. + (builtins.toPath "/${kind}"))
48 49 (builtins.filterSource
49 50 (path: type: baseNameOf path == "VERSION")
50 51 ./rhodecode)
51 52 ];
52 53 sourceRoot = kind;
53 54 buildInputs = [ sphinx ];
54 55 configurePhase = null;
55 56 buildPhase = ''
56 57 make SPHINXBUILD=sphinx-build html
57 58 '';
58 59 installPhase = ''
59 60 mkdir -p $out
60 61 mv _build/html $out/
61 62
62 63 mkdir -p $out/nix-support
63 64 echo "doc manual $out/html index.html" >> \
64 65 "$out/nix-support/hydra-build-products"
65 66 '';
66 67 };
67 68
68 69 enterprise = import ./default.nix {
69 70 inherit
70 71 pkgs;
71 72
72 73 # TODO: for quick local testing
73 74 doCheck = false;
74 75 };
75 76
76 77 test-cfg = stdenv.mkDerivation {
77 78 name = "test-cfg";
78 79 unpackPhase = "true";
79 80 buildInputs = [
80 81 enterprise.src
81 82 ];
82 83 installPhase = ''
83 84 mkdir -p $out/etc
84 85 cp ${enterprise.src}/test.ini $out/etc/enterprise.ini
85 86 # TODO: johbo: Needed, so that the login works, this causes
86 87 # probably some side effects
87 88 substituteInPlace $out/etc/enterprise.ini --replace "is_test = True" ""
88 89
89 90 # Gevent configuration
90 91 cp $out/etc/enterprise.ini $out/etc/enterprise-gevent.ini;
91 92 cat >> $out/etc/enterprise-gevent.ini <<EOF
92 93
93 94 [server:main]
94 95 use = egg:gunicorn#main
95 96 worker_class = gevent
96 97 EOF
97 98
98 99 cp ${enterprise.src}/vcsserver/test.ini $out/etc/vcsserver.ini
99 100 '';
100 101 };
101 102
102 103 ac-test-drv = import ./acceptance_tests {
103 104 withExternals = false;
104 105 };
105 106
106 107 # TODO: johbo: Currently abusing buildPythonPackage to make the
107 108 # needed environment for the ac-test tools.
108 109 mkAcTests = {
109 110 # Path to an INI file which will be used to run Enterprise.
110 111 #
111 112 # Intended usage is to provide different configuration files to
112 113 # run the tests against a different configuration.
113 114 enterpriseCfg ? "${test-cfg}/etc/enterprise.ini"
114 115
115 116 # Path to an INI file which will be used to run the VCSServer.
116 117 , vcsserverCfg ? "${test-cfg}/etc/vcsserver.ini"
117 118 }: pkgs.pythonPackages.buildPythonPackage {
118 119 name = "enterprise-ac-tests";
119 120 src = ./acceptance_tests;
120 121
121 122 buildInputs = with pkgs; [
122 123 curl
123 124 enterprise
124 125 ac-test-drv
125 126 ];
126 127
127 128 buildPhase = ''
128 129 cp ${enterpriseCfg} enterprise.ini
129 130
130 131 echo "Creating a fake home directory"
131 132 mkdir fake-home
132 133 export HOME=$PWD/fake-home
133 134
134 135 echo "Creating a repository directory"
135 136 mkdir repos
136 137
137 138 echo "Preparing the database"
138 139 paster setup-rhodecode \
139 140 --user=admin \
140 141 --email=admin@example.com \
141 142 --password=secret \
142 143 --api-key=9999999999999999999999999999999999999999 \
143 144 --force-yes \
144 145 --repos=$PWD/repos \
145 146 enterprise.ini > /dev/null
146 147
147 148 echo "Starting rcserver"
148 149 vcsserver --config ${vcsserverCfg} >vcsserver.log 2>&1 &
149 150 rcserver enterprise.ini >rcserver.log 2>&1 &
150 151
151 152 while ! curl -f -s http://localhost:5000 > /dev/null
152 153 do
153 154 echo "Waiting for server to be ready..."
154 155 sleep 3
155 156 done
156 157 echo "Webserver is ready."
157 158
158 159 echo "Starting the test run"
159 160 py.test -c example.ini -vs --maxfail=5 tests
160 161
161 162 echo "Kill rcserver"
162 163 kill %2
163 164 kill %1
164 165 '';
165 166
166 167 # TODO: johbo: Use the install phase again once the normal mkDerivation
167 168 # can be used again.
168 169 postInstall = ''
169 170 mkdir -p $out
170 171 cp enterprise.ini $out
171 172 cp ${vcsserverCfg} $out/vcsserver.ini
172 173 cp rcserver.log $out
173 174 cp vcsserver.log $out
174 175
175 176 mkdir -p $out/nix-support
176 177 echo "report config $out enterprise.ini" >> $out/nix-support/hydra-build-products
177 178 echo "report config $out vcsserver.ini" >> $out/nix-support/hydra-build-products
178 179 echo "report rcserver $out rcserver.log" >> $out/nix-support/hydra-build-products
179 180 echo "report vcsserver $out vcsserver.log" >> $out/nix-support/hydra-build-products
180 181 '';
181 182 };
182 183
183 184 vcsserver = import <vcsserver> {
184 185 inherit pkgs;
185 186
186 187 # TODO: johbo: Think of a more elegant solution to this problem
187 188 pythonExternalOverrides = self: super: (enterprise.myPythonPackagesUnfix self);
188 189 };
189 190
190 191 runTests = optionString: (enterprise.override (attrs: {
191 192 doCheck = true;
192 193 name = "test-run";
193 194 buildInputs = attrs.buildInputs ++ [
194 195 vcsserver
195 196 ];
196 197 checkPhase = ''
197 198 py.test ${optionString} -vv -ra
198 199 '';
199 200 buildPhase = attrs.shellHook;
200 201 installPhase = ''
201 202 echo "Intentionally not installing anything"
202 203 '';
203 204 meta.description = "Enterprise test run ${optionString}";
204 205 }));
205 206
206 207 jobs = {
207 208
208 209 build = enterprise;
209 210
210 211 # johbo: Currently this is simply running the tests against the sources. Nicer
211 212 # would be to run xdist and against the installed application, so that we also
212 213 # cover the impact of installing the application.
213 214 test-api = runTests "rhodecode/api";
214 215 test-functional = runTests "rhodecode/tests/functional";
215 216 test-rest = runTests "rhodecode/tests --ignore=rhodecode/tests/functional";
216 217 test-full = runTests "rhodecode";
217 218
218 219 docs = mkDocs "docs";
219 220
220 221 aggregate = pkgs.releaseTools.aggregate {
221 222 name = "aggregated-jobs";
222 223 constituents = [
223 224 jobs.build
224 225 jobs.test-api
225 226 jobs.test-rest
226 227 jobs.docs
227 228 ];
228 229 };
229 230 };
230 231
231 232 in jobs
@@ -1,151 +1,150 b''
1 1 Babel==1.3
2 2 Beaker==1.7.0
3 3 CProfileV==1.0.6
4 4 Fabric==1.10.0
5 5 FormEncode==1.2.4
6 6 Jinja2==2.7.3
7 7 Mako==1.0.1
8 8 Markdown==2.6.2
9 9 MarkupSafe==0.23
10 10 MySQL-python==1.2.5
11 11 Paste==2.0.2
12 12 PasteDeploy==1.5.2
13 13 PasteScript==1.7.5
14 14 Pygments==2.0.2
15 15
16 16 # TODO: This version is not available on PyPI
17 17 # Pylons==1.0.2.dev20160108
18 18 Pylons==1.0.1
19 19
20 20 # TODO: This version is not available, but newer ones are
21 21 # Pyro4==4.35
22 22 Pyro4==4.41
23 23
24 24 # TODO: This should probably not be in here
25 25 # -e hg+https://johbo@code.rhodecode.com/johbo/rhodecode-fork@3a454bd1f17c0b2b2a951cf2b111e0320d7942a9#egg=RhodeCodeEnterprise-dev
26 26
27 27 # TODO: This is not really a dependency, we should add it only
28 28 # into the development environment, since there it is useful.
29 29 # RhodeCodeVCSServer==3.9.0
30 30
31 31 Routes==1.13
32 32 SQLAlchemy==0.9.9
33 33 Sphinx==1.2.2
34 34 Tempita==0.5.2
35 35 URLObject==2.4.0
36 36 WebError==0.10.3
37 37
38 38 # TODO: This is modified by us, needs a better integration. For now
39 39 # using the latest version before.
40 40 # WebHelpers==1.3.dev20150807
41 41 WebHelpers==1.3
42 42
43 43 WebHelpers2==2.0
44 44 WebOb==1.3.1
45 45 WebTest==1.4.3
46 46 Whoosh==2.7.0
47 47 alembic==0.8.4
48 48 amqplib==1.0.2
49 49 anyjson==0.3.3
50 50 appenlight-client==0.6.14
51 51 authomatic==0.1.0.post1;
52 52 backport-ipaddress==0.1
53 53 bottle==0.12.8
54 54 bumpversion==0.5.3
55 55 celery==2.2.10
56 56 click==5.1
57 57 colander==1.2
58 58 configobj==5.0.6
59 59 cov-core==1.15.0
60 60 coverage==3.7.1
61 61 cssselect==0.9.1
62 62 decorator==3.4.2
63 63 docutils==0.12
64 64 dogpile.cache==0.5.7
65 65 dogpile.core==0.4.1
66 66 dulwich==0.12.0
67 67 ecdsa==0.11
68 68 flake8==2.4.1
69 69 future==0.14.3
70 70 futures==3.0.2
71 71 gprof2dot==2015.12.1
72 greenlet==0.4.9
73 72 gunicorn==19.6.0
74 73
75 74 # TODO: Needs subvertpy and blows up without Subversion headers,
76 75 # actually we should not need this for Enterprise at all.
77 76 # hgsubversion==1.8.2
78 77
79 78 gnureadline==6.3.3
80 79 infrae.cache==1.0.1
81 invoke==0.11.1
80 invoke==0.13.0
82 81 ipdb==0.8
83 82 ipython==3.1.0
84 83 iso8601==0.1.11
85 84 itsdangerous==0.24
86 85 kombu==1.5.1
87 86 lxml==3.4.4
88 87 mccabe==0.3
89 88 meld3==1.0.2
90 89 mock==1.0.1
91 90 msgpack-python==0.4.6
92 91 nose==1.3.6
93 92 objgraph==2.0.0
94 93 packaging==15.2
95 94 paramiko==1.15.1
96 95 pep8==1.5.7
97 96 psutil==2.2.1
98 97 psycopg2==2.6
99 98 py==1.4.29
100 99 py-bcrypt==0.4
101 100 pycrypto==2.6.1
102 101 pycurl==7.19.5
103 102 pyflakes==0.8.1
104 103 pyparsing==1.5.7
105 104 pyramid==1.6.1
106 105 pyramid-beaker==0.8
107 106 pyramid-debugtoolbar==2.4.2
108 107 pyramid-jinja2==2.5
109 108 pyramid-mako==1.0.2
110 109 pysqlite==2.6.3
111 110 pytest==2.8.5
112 111 pytest-runner==2.7.1
113 112 pytest-catchlog==1.2.2
114 113 pytest-cov==1.8.1
115 114 pytest-profiling==1.0.1
116 115 pytest-timeout==0.4
117 116 python-dateutil==1.5
118 117 python-ldap==2.4.19
119 118 python-memcached==1.57
120 119 python-pam==1.8.2
121 120 pytz==2015.4
122 121 pyzmq==14.6.0
123 122
124 123 # TODO: This is not available in public
125 124 # rc-testdata==0.2.0
126 125
127 126 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.8.3.zip#md5=9acdfd71b8ddf4056057065f37ab9ccb
128 127
129 128
130 129 recaptcha-client==1.0.6
131 130 repoze.lru==0.6
132 131 requests==2.9.1
133 132 serpent==1.12
134 133 setproctitle==1.1.8
135 134 setuptools==20.8.1
136 135 setuptools-scm==1.11.0
137 136 simplejson==3.7.2
138 137 six==1.9.0
139 138 subprocess32==3.2.6
140 139 supervisor==3.1.3
141 140 transifex-client==0.10
142 141 translationstring==1.3
143 142 trollius==1.0.4
144 143 uWSGI==2.0.11.2
145 144 venusian==1.0
146 145 waitress==0.8.9
147 146 wsgiref==0.1.2
148 147 zope.cachedescriptors==4.0.0
149 148 zope.deprecation==4.1.2
150 149 zope.event==4.0.3
151 150 zope.interface==4.1.3
@@ -1,1 +1,1 b''
1 4.1.2 No newline at end of file
1 4.2.0 No newline at end of file
@@ -1,609 +1,615 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Authentication modules
23 23 """
24 24
25 import colander
25 26 import logging
26 27 import time
27 28 import traceback
28 29 import warnings
29 30
30 31 from pyramid.threadlocal import get_current_registry
31 32 from sqlalchemy.ext.hybrid import hybrid_property
32 33
33 34 from rhodecode.authentication.interface import IAuthnPluginRegistry
34 35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 36 from rhodecode.lib import caches
36 37 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
37 38 from rhodecode.lib.utils2 import md5_safe, safe_int
38 39 from rhodecode.lib.utils2 import safe_str
39 40 from rhodecode.model.db import User
40 41 from rhodecode.model.meta import Session
41 42 from rhodecode.model.settings import SettingsModel
42 43 from rhodecode.model.user import UserModel
43 44 from rhodecode.model.user_group import UserGroupModel
44 45
45 46
46 47 log = logging.getLogger(__name__)
47 48
48 49 # auth types that authenticate() function can receive
49 50 VCS_TYPE = 'vcs'
50 51 HTTP_TYPE = 'http'
51 52
52 53
53 54 class LazyFormencode(object):
54 55 def __init__(self, formencode_obj, *args, **kwargs):
55 56 self.formencode_obj = formencode_obj
56 57 self.args = args
57 58 self.kwargs = kwargs
58 59
59 60 def __call__(self, *args, **kwargs):
60 61 from inspect import isfunction
61 62 formencode_obj = self.formencode_obj
62 63 if isfunction(formencode_obj):
63 64 # case we wrap validators into functions
64 65 formencode_obj = self.formencode_obj(*args, **kwargs)
65 66 return formencode_obj(*self.args, **self.kwargs)
66 67
67 68
68 69 class RhodeCodeAuthPluginBase(object):
69 70 # cache the authentication request for N amount of seconds. Some kind
70 71 # of authentication methods are very heavy and it's very efficient to cache
71 72 # the result of a call. If it's set to None (default) cache is off
72 73 AUTH_CACHE_TTL = None
73 74 AUTH_CACHE = {}
74 75
75 76 auth_func_attrs = {
76 77 "username": "unique username",
77 78 "firstname": "first name",
78 79 "lastname": "last name",
79 80 "email": "email address",
80 81 "groups": '["list", "of", "groups"]',
81 82 "extern_name": "name in external source of record",
82 83 "extern_type": "type of external source of record",
83 84 "admin": 'True|False defines if user should be RhodeCode super admin',
84 85 "active":
85 86 'True|False defines active state of user internally for RhodeCode',
86 87 "active_from_extern":
87 88 "True|False\None, active state from the external auth, "
88 89 "None means use definition from RhodeCode extern_type active value"
89 90 }
90 91 # set on authenticate() method and via set_auth_type func.
91 92 auth_type = None
92 93
93 94 # List of setting names to store encrypted. Plugins may override this list
94 95 # to store settings encrypted.
95 96 _settings_encrypted = []
96 97
97 98 # Mapping of python to DB settings model types. Plugins may override or
98 99 # extend this mapping.
99 100 _settings_type_map = {
100 str: 'str',
101 int: 'int',
102 unicode: 'unicode',
103 bool: 'bool',
104 list: 'list',
101 colander.String: 'unicode',
102 colander.Integer: 'int',
103 colander.Boolean: 'bool',
104 colander.List: 'list',
105 105 }
106 106
107 107 def __init__(self, plugin_id):
108 108 self._plugin_id = plugin_id
109 109
110 def __str__(self):
111 return self.get_id()
112
110 113 def _get_setting_full_name(self, name):
111 114 """
112 115 Return the full setting name used for storing values in the database.
113 116 """
114 117 # TODO: johbo: Using the name here is problematic. It would be good to
115 118 # introduce either new models in the database to hold Plugin and
116 119 # PluginSetting or to use the plugin id here.
117 120 return 'auth_{}_{}'.format(self.name, name)
118 121
119 def _get_setting_type(self, name, value):
122 def _get_setting_type(self, name):
123 """
124 Return the type of a setting. This type is defined by the SettingsModel
125 and determines how the setting is stored in DB. Optionally the suffix
126 `.encrypted` is appended to instruct SettingsModel to store it
127 encrypted.
120 128 """
121 Get the type as used by the SettingsModel accordingly to type of passed
122 value. Optionally the suffix `.encrypted` is appended to instruct
123 SettingsModel to store it encrypted.
124 """
125 type_ = self._settings_type_map.get(type(value), 'unicode')
129 schema_node = self.get_settings_schema().get(name)
130 db_type = self._settings_type_map.get(
131 type(schema_node.typ), 'unicode')
126 132 if name in self._settings_encrypted:
127 type_ = '{}.encrypted'.format(type_)
128 return type_
133 db_type = '{}.encrypted'.format(db_type)
134 return db_type
129 135
130 136 def is_enabled(self):
131 137 """
132 138 Returns true if this plugin is enabled. An enabled plugin can be
133 139 configured in the admin interface but it is not consulted during
134 140 authentication.
135 141 """
136 142 auth_plugins = SettingsModel().get_auth_plugins()
137 143 return self.get_id() in auth_plugins
138 144
139 145 def is_active(self):
140 146 """
141 147 Returns true if the plugin is activated. An activated plugin is
142 148 consulted during authentication, assumed it is also enabled.
143 149 """
144 150 return self.get_setting_by_name('enabled')
145 151
146 152 def get_id(self):
147 153 """
148 154 Returns the plugin id.
149 155 """
150 156 return self._plugin_id
151 157
152 158 def get_display_name(self):
153 159 """
154 160 Returns a translation string for displaying purposes.
155 161 """
156 162 raise NotImplementedError('Not implemented in base class')
157 163
158 164 def get_settings_schema(self):
159 165 """
160 166 Returns a colander schema, representing the plugin settings.
161 167 """
162 168 return AuthnPluginSettingsSchemaBase()
163 169
164 def get_setting_by_name(self, name):
170 def get_setting_by_name(self, name, default=None):
165 171 """
166 172 Returns a plugin setting by name.
167 173 """
168 174 full_name = self._get_setting_full_name(name)
169 175 db_setting = SettingsModel().get_setting_by_name(full_name)
170 return db_setting.app_settings_value if db_setting else None
176 return db_setting.app_settings_value if db_setting else default
171 177
172 178 def create_or_update_setting(self, name, value):
173 179 """
174 180 Create or update a setting for this plugin in the persistent storage.
175 181 """
176 182 full_name = self._get_setting_full_name(name)
177 type_ = self._get_setting_type(name, value)
183 type_ = self._get_setting_type(name)
178 184 db_setting = SettingsModel().create_or_update_setting(
179 185 full_name, value, type_)
180 186 return db_setting.app_settings_value
181 187
182 188 def get_settings(self):
183 189 """
184 190 Returns the plugin settings as dictionary.
185 191 """
186 192 settings = {}
187 193 for node in self.get_settings_schema():
188 194 settings[node.name] = self.get_setting_by_name(node.name)
189 195 return settings
190 196
191 197 @property
192 198 def validators(self):
193 199 """
194 200 Exposes RhodeCode validators modules
195 201 """
196 202 # this is a hack to overcome issues with pylons threadlocals and
197 203 # translator object _() not beein registered properly.
198 204 class LazyCaller(object):
199 205 def __init__(self, name):
200 206 self.validator_name = name
201 207
202 208 def __call__(self, *args, **kwargs):
203 209 from rhodecode.model import validators as v
204 210 obj = getattr(v, self.validator_name)
205 211 # log.debug('Initializing lazy formencode object: %s', obj)
206 212 return LazyFormencode(obj, *args, **kwargs)
207 213
208 214 class ProxyGet(object):
209 215 def __getattribute__(self, name):
210 216 return LazyCaller(name)
211 217
212 218 return ProxyGet()
213 219
214 220 @hybrid_property
215 221 def name(self):
216 222 """
217 223 Returns the name of this authentication plugin.
218 224
219 225 :returns: string
220 226 """
221 227 raise NotImplementedError("Not implemented in base class")
222 228
223 229 @property
224 230 def is_headers_auth(self):
225 231 """
226 232 Returns True if this authentication plugin uses HTTP headers as
227 233 authentication method.
228 234 """
229 235 return False
230 236
231 237 @hybrid_property
232 238 def is_container_auth(self):
233 239 """
234 240 Deprecated method that indicates if this authentication plugin uses
235 241 HTTP headers as authentication method.
236 242 """
237 243 warnings.warn(
238 244 'Use is_headers_auth instead.', category=DeprecationWarning)
239 245 return self.is_headers_auth
240 246
241 247 @hybrid_property
242 248 def allows_creating_users(self):
243 249 """
244 250 Defines if Plugin allows users to be created on-the-fly when
245 251 authentication is called. Controls how external plugins should behave
246 252 in terms if they are allowed to create new users, or not. Base plugins
247 253 should not be allowed to, but External ones should be !
248 254
249 255 :return: bool
250 256 """
251 257 return False
252 258
253 259 def set_auth_type(self, auth_type):
254 260 self.auth_type = auth_type
255 261
256 262 def allows_authentication_from(
257 263 self, user, allows_non_existing_user=True,
258 264 allowed_auth_plugins=None, allowed_auth_sources=None):
259 265 """
260 266 Checks if this authentication module should accept a request for
261 267 the current user.
262 268
263 269 :param user: user object fetched using plugin's get_user() method.
264 270 :param allows_non_existing_user: if True, don't allow the
265 271 user to be empty, meaning not existing in our database
266 272 :param allowed_auth_plugins: if provided, users extern_type will be
267 273 checked against a list of provided extern types, which are plugin
268 274 auth_names in the end
269 275 :param allowed_auth_sources: authentication type allowed,
270 276 `http` or `vcs` default is both.
271 277 defines if plugin will accept only http authentication vcs
272 278 authentication(git/hg) or both
273 279 :returns: boolean
274 280 """
275 281 if not user and not allows_non_existing_user:
276 282 log.debug('User is empty but plugin does not allow empty users,'
277 283 'not allowed to authenticate')
278 284 return False
279 285
280 286 expected_auth_plugins = allowed_auth_plugins or [self.name]
281 287 if user and (user.extern_type and
282 288 user.extern_type not in expected_auth_plugins):
283 289 log.debug(
284 290 'User `%s` is bound to `%s` auth type. Plugin allows only '
285 291 '%s, skipping', user, user.extern_type, expected_auth_plugins)
286 292
287 293 return False
288 294
289 295 # by default accept both
290 296 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
291 297 if self.auth_type not in expected_auth_from:
292 298 log.debug('Current auth source is %s but plugin only allows %s',
293 299 self.auth_type, expected_auth_from)
294 300 return False
295 301
296 302 return True
297 303
298 304 def get_user(self, username=None, **kwargs):
299 305 """
300 306 Helper method for user fetching in plugins, by default it's using
301 307 simple fetch by username, but this method can be custimized in plugins
302 308 eg. headers auth plugin to fetch user by environ params
303 309
304 310 :param username: username if given to fetch from database
305 311 :param kwargs: extra arguments needed for user fetching.
306 312 """
307 313 user = None
308 314 log.debug(
309 315 'Trying to fetch user `%s` from RhodeCode database', username)
310 316 if username:
311 317 user = User.get_by_username(username)
312 318 if not user:
313 319 log.debug('User not found, fallback to fetch user in '
314 320 'case insensitive mode')
315 321 user = User.get_by_username(username, case_insensitive=True)
316 322 else:
317 323 log.debug('provided username:`%s` is empty skipping...', username)
318 324 if not user:
319 325 log.debug('User `%s` not found in database', username)
320 326 return user
321 327
322 328 def user_activation_state(self):
323 329 """
324 330 Defines user activation state when creating new users
325 331
326 332 :returns: boolean
327 333 """
328 334 raise NotImplementedError("Not implemented in base class")
329 335
330 336 def auth(self, userobj, username, passwd, settings, **kwargs):
331 337 """
332 338 Given a user object (which may be null), username, a plaintext
333 339 password, and a settings object (containing all the keys needed as
334 340 listed in settings()), authenticate this user's login attempt.
335 341
336 342 Return None on failure. On success, return a dictionary of the form:
337 343
338 344 see: RhodeCodeAuthPluginBase.auth_func_attrs
339 345 This is later validated for correctness
340 346 """
341 347 raise NotImplementedError("not implemented in base class")
342 348
343 349 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
344 350 """
345 351 Wrapper to call self.auth() that validates call on it
346 352
347 353 :param userobj: userobj
348 354 :param username: username
349 355 :param passwd: plaintext password
350 356 :param settings: plugin settings
351 357 """
352 358 auth = self.auth(userobj, username, passwd, settings, **kwargs)
353 359 if auth:
354 360 # check if hash should be migrated ?
355 361 new_hash = auth.get('_hash_migrate')
356 362 if new_hash:
357 363 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
358 364 return self._validate_auth_return(auth)
359 365 return auth
360 366
361 367 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
362 368 new_hash_cypher = _RhodeCodeCryptoBCrypt()
363 369 # extra checks, so make sure new hash is correct.
364 370 password_encoded = safe_str(password)
365 371 if new_hash and new_hash_cypher.hash_check(
366 372 password_encoded, new_hash):
367 373 cur_user = User.get_by_username(username)
368 374 cur_user.password = new_hash
369 375 Session().add(cur_user)
370 376 Session().flush()
371 377 log.info('Migrated user %s hash to bcrypt', cur_user)
372 378
373 379 def _validate_auth_return(self, ret):
374 380 if not isinstance(ret, dict):
375 381 raise Exception('returned value from auth must be a dict')
376 382 for k in self.auth_func_attrs:
377 383 if k not in ret:
378 384 raise Exception('Missing %s attribute from returned data' % k)
379 385 return ret
380 386
381 387
382 388 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
383 389
384 390 @hybrid_property
385 391 def allows_creating_users(self):
386 392 return True
387 393
388 394 def use_fake_password(self):
389 395 """
390 396 Return a boolean that indicates whether or not we should set the user's
391 397 password to a random value when it is authenticated by this plugin.
392 398 If your plugin provides authentication, then you will generally
393 399 want this.
394 400
395 401 :returns: boolean
396 402 """
397 403 raise NotImplementedError("Not implemented in base class")
398 404
399 405 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
400 406 # at this point _authenticate calls plugin's `auth()` function
401 407 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
402 408 userobj, username, passwd, settings, **kwargs)
403 409 if auth:
404 410 # maybe plugin will clean the username ?
405 411 # we should use the return value
406 412 username = auth['username']
407 413
408 414 # if external source tells us that user is not active, we should
409 415 # skip rest of the process. This can prevent from creating users in
410 416 # RhodeCode when using external authentication, but if it's
411 417 # inactive user we shouldn't create that user anyway
412 418 if auth['active_from_extern'] is False:
413 419 log.warning(
414 420 "User %s authenticated against %s, but is inactive",
415 421 username, self.__module__)
416 422 return None
417 423
418 424 cur_user = User.get_by_username(username, case_insensitive=True)
419 425 is_user_existing = cur_user is not None
420 426
421 427 if is_user_existing:
422 428 log.debug('Syncing user `%s` from '
423 429 '`%s` plugin', username, self.name)
424 430 else:
425 431 log.debug('Creating non existing user `%s` from '
426 432 '`%s` plugin', username, self.name)
427 433
428 434 if self.allows_creating_users:
429 435 log.debug('Plugin `%s` allows to '
430 436 'create new users', self.name)
431 437 else:
432 438 log.debug('Plugin `%s` does not allow to '
433 439 'create new users', self.name)
434 440
435 441 user_parameters = {
436 442 'username': username,
437 443 'email': auth["email"],
438 444 'firstname': auth["firstname"],
439 445 'lastname': auth["lastname"],
440 446 'active': auth["active"],
441 447 'admin': auth["admin"],
442 448 'extern_name': auth["extern_name"],
443 449 'extern_type': self.name,
444 450 'plugin': self,
445 451 'allow_to_create_user': self.allows_creating_users,
446 452 }
447 453
448 454 if not is_user_existing:
449 455 if self.use_fake_password():
450 456 # Randomize the PW because we don't need it, but don't want
451 457 # them blank either
452 458 passwd = PasswordGenerator().gen_password(length=16)
453 459 user_parameters['password'] = passwd
454 460 else:
455 461 # Since the password is required by create_or_update method of
456 462 # UserModel, we need to set it explicitly.
457 463 # The create_or_update method is smart and recognises the
458 464 # password hashes as well.
459 465 user_parameters['password'] = cur_user.password
460 466
461 467 # we either create or update users, we also pass the flag
462 468 # that controls if this method can actually do that.
463 469 # raises NotAllowedToCreateUserError if it cannot, and we try to.
464 470 user = UserModel().create_or_update(**user_parameters)
465 471 Session().flush()
466 472 # enforce user is just in given groups, all of them has to be ones
467 473 # created from plugins. We store this info in _group_data JSON
468 474 # field
469 475 try:
470 476 groups = auth['groups'] or []
471 477 UserGroupModel().enforce_groups(user, groups, self.name)
472 478 except Exception:
473 479 # for any reason group syncing fails, we should
474 480 # proceed with login
475 481 log.error(traceback.format_exc())
476 482 Session().commit()
477 483 return auth
478 484
479 485
480 486 def loadplugin(plugin_id):
481 487 """
482 488 Loads and returns an instantiated authentication plugin.
483 489 Returns the RhodeCodeAuthPluginBase subclass on success,
484 490 or None on failure.
485 491 """
486 492 # TODO: Disusing pyramids thread locals to retrieve the registry.
487 493 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
488 494 plugin = authn_registry.get_plugin(plugin_id)
489 495 if plugin is None:
490 496 log.error('Authentication plugin not found: "%s"', plugin_id)
491 497 return plugin
492 498
493 499
494 500 def get_auth_cache_manager(custom_ttl=None):
495 501 return caches.get_cache_manager(
496 502 'auth_plugins', 'rhodecode.authentication', custom_ttl)
497 503
498 504
499 505 def authenticate(username, password, environ=None, auth_type=None,
500 506 skip_missing=False):
501 507 """
502 508 Authentication function used for access control,
503 509 It tries to authenticate based on enabled authentication modules.
504 510
505 511 :param username: username can be empty for headers auth
506 512 :param password: password can be empty for headers auth
507 513 :param environ: environ headers passed for headers auth
508 514 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
509 515 :param skip_missing: ignores plugins that are in db but not in environment
510 516 :returns: None if auth failed, plugin_user dict if auth is correct
511 517 """
512 518 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
513 519 raise ValueError('auth type must be on of http, vcs got "%s" instead'
514 520 % auth_type)
515 521 headers_only = environ and not (username and password)
516 522
517 523 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
518 524 for plugin in authn_registry.get_plugins_for_authentication():
519 525 plugin.set_auth_type(auth_type)
520 526 user = plugin.get_user(username)
521 527 display_user = user.username if user else username
522 528
523 529 if headers_only and not plugin.is_headers_auth:
524 530 log.debug('Auth type is for headers only and plugin `%s` is not '
525 531 'headers plugin, skipping...', plugin.get_id())
526 532 continue
527 533
528 534 # load plugin settings from RhodeCode database
529 535 plugin_settings = plugin.get_settings()
530 536 log.debug('Plugin settings:%s', plugin_settings)
531 537
532 538 log.debug('Trying authentication using ** %s **', plugin.get_id())
533 539 # use plugin's method of user extraction.
534 540 user = plugin.get_user(username, environ=environ,
535 541 settings=plugin_settings)
536 542 display_user = user.username if user else username
537 543 log.debug(
538 544 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
539 545
540 546 if not plugin.allows_authentication_from(user):
541 547 log.debug('Plugin %s does not accept user `%s` for authentication',
542 548 plugin.get_id(), display_user)
543 549 continue
544 550 else:
545 551 log.debug('Plugin %s accepted user `%s` for authentication',
546 552 plugin.get_id(), display_user)
547 553
548 554 log.info('Authenticating user `%s` using %s plugin',
549 555 display_user, plugin.get_id())
550 556
551 557 _cache_ttl = 0
552 558
553 559 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
554 560 # plugin cache set inside is more important than the settings value
555 561 _cache_ttl = plugin.AUTH_CACHE_TTL
556 562 elif plugin_settings.get('auth_cache_ttl'):
557 563 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
558 564
559 565 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
560 566
561 567 # get instance of cache manager configured for a namespace
562 568 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
563 569
564 570 log.debug('Cache for plugin `%s` active: %s', plugin.get_id(),
565 571 plugin_cache_active)
566 572
567 573 # for environ based password can be empty, but then the validation is
568 574 # on the server that fills in the env data needed for authentication
569 575 _password_hash = md5_safe(plugin.name + username + (password or ''))
570 576
571 577 # _authenticate is a wrapper for .auth() method of plugin.
572 578 # it checks if .auth() sends proper data.
573 579 # For RhodeCodeExternalAuthPlugin it also maps users to
574 580 # Database and maps the attributes returned from .auth()
575 581 # to RhodeCode database. If this function returns data
576 582 # then auth is correct.
577 583 start = time.time()
578 584 log.debug('Running plugin `%s` _authenticate method',
579 585 plugin.get_id())
580 586
581 587 def auth_func():
582 588 """
583 589 This function is used internally in Cache of Beaker to calculate
584 590 Results
585 591 """
586 592 return plugin._authenticate(
587 593 user, username, password, plugin_settings,
588 594 environ=environ or {})
589 595
590 596 if plugin_cache_active:
591 597 plugin_user = cache_manager.get(
592 598 _password_hash, createfunc=auth_func)
593 599 else:
594 600 plugin_user = auth_func()
595 601
596 602 auth_time = time.time() - start
597 603 log.debug('Authentication for plugin `%s` completed in %.3fs, '
598 604 'expiration time of fetched cache %.1fs.',
599 605 plugin.get_id(), auth_time, _cache_ttl)
600 606
601 607 log.debug('PLUGIN USER DATA: %s', plugin_user)
602 608
603 609 if plugin_user:
604 610 log.debug('Plugin returned proper authentication data')
605 611 return plugin_user
606 612 # we failed to Auth because .auth() method didn't return proper user
607 613 log.debug("User `%s` failed to authenticate against %s",
608 614 display_user, plugin.get_id())
609 615 return None
@@ -1,188 +1,192 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import colander
22 22 import formencode.htmlfill
23 23 import logging
24 24
25 25 from pyramid.httpexceptions import HTTPFound
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.authentication.base import get_auth_cache_manager
30 30 from rhodecode.authentication.interface import IAuthnPluginRegistry
31 31 from rhodecode.lib import auth
32 32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 33 from rhodecode.model.forms import AuthSettingsForm
34 34 from rhodecode.model.meta import Session
35 35 from rhodecode.model.settings import SettingsModel
36 36 from rhodecode.translation import _
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class AuthnPluginViewBase(object):
42 42
43 43 def __init__(self, context, request):
44 44 self.request = request
45 45 self.context = context
46 46 self.plugin = context.plugin
47 47 self._rhodecode_user = request.user
48 48
49 49 @LoginRequired()
50 50 @HasPermissionAllDecorator('hg.admin')
51 51 def settings_get(self, defaults=None, errors=None):
52 52 """
53 53 View that displays the plugin settings as a form.
54 54 """
55 55 defaults = defaults or {}
56 56 errors = errors or {}
57 57 schema = self.plugin.get_settings_schema()
58 58
59 # Get default values for the form.
59 # Compute default values for the form. Priority is:
60 # 1. Passed to this method 2. DB value 3. Schema default
60 61 for node in schema:
61 db_value = self.plugin.get_setting_by_name(node.name)
62 defaults.setdefault(node.name, db_value)
62 if node.name not in defaults:
63 defaults[node.name] = self.plugin.get_setting_by_name(
64 node.name, node.default)
63 65
64 66 template_context = {
65 67 'defaults': defaults,
66 68 'errors': errors,
67 69 'plugin': self.context.plugin,
68 70 'resource': self.context,
69 71 }
70 72
71 73 return template_context
72 74
73 75 @LoginRequired()
74 76 @HasPermissionAllDecorator('hg.admin')
75 77 @auth.CSRFRequired()
76 78 def settings_post(self):
77 79 """
78 80 View that validates and stores the plugin settings.
79 81 """
80 82 schema = self.plugin.get_settings_schema()
83 data = self.request.params
84
81 85 try:
82 valid_data = schema.deserialize(self.request.params)
86 valid_data = schema.deserialize(data)
83 87 except colander.Invalid, e:
84 88 # Display error message and display form again.
85 89 self.request.session.flash(
86 90 _('Errors exist when saving plugin settings. '
87 91 'Please check the form inputs.'),
88 92 queue='error')
89 defaults = schema.flatten(self.request.params)
93 defaults = {key: data[key] for key in data if key in schema}
90 94 return self.settings_get(errors=e.asdict(), defaults=defaults)
91 95
92 96 # Store validated data.
93 97 for name, value in valid_data.items():
94 98 self.plugin.create_or_update_setting(name, value)
95 99 Session.commit()
96 100
97 101 # Display success message and redirect.
98 102 self.request.session.flash(
99 103 _('Auth settings updated successfully.'),
100 104 queue='success')
101 105 redirect_to = self.request.resource_path(
102 106 self.context, route_name='auth_home')
103 107 return HTTPFound(redirect_to)
104 108
105 109
106 110 # TODO: Ongoing migration in these views.
107 111 # - Maybe we should also use a colander schema for these views.
108 112 class AuthSettingsView(object):
109 113 def __init__(self, context, request):
110 114 self.context = context
111 115 self.request = request
112 116
113 117 # TODO: Move this into a utility function. It is needed in all view
114 118 # classes during migration. Maybe a mixin?
115 119
116 120 # Some of the decorators rely on this attribute to be present on the
117 121 # class of the decorated method.
118 122 self._rhodecode_user = request.user
119 123
120 124 @LoginRequired()
121 125 @HasPermissionAllDecorator('hg.admin')
122 126 def index(self, defaults=None, errors=None, prefix_error=False):
123 127 defaults = defaults or {}
124 128 authn_registry = self.request.registry.getUtility(IAuthnPluginRegistry)
125 129 enabled_plugins = SettingsModel().get_auth_plugins()
126 130
127 131 # Create template context and render it.
128 132 template_context = {
129 133 'resource': self.context,
130 134 'available_plugins': authn_registry.get_plugins(),
131 135 'enabled_plugins': enabled_plugins,
132 136 }
133 137 html = render('rhodecode:templates/admin/auth/auth_settings.html',
134 138 template_context,
135 139 request=self.request)
136 140
137 141 # Create form default values and fill the form.
138 142 form_defaults = {
139 143 'auth_plugins': ','.join(enabled_plugins)
140 144 }
141 145 form_defaults.update(defaults)
142 146 html = formencode.htmlfill.render(
143 147 html,
144 148 defaults=form_defaults,
145 149 errors=errors,
146 150 prefix_error=prefix_error,
147 151 encoding="UTF-8",
148 152 force_defaults=False)
149 153
150 154 return Response(html)
151 155
152 156 @LoginRequired()
153 157 @HasPermissionAllDecorator('hg.admin')
154 158 @auth.CSRFRequired()
155 159 def auth_settings(self):
156 160 try:
157 161 form = AuthSettingsForm()()
158 162 form_result = form.to_python(self.request.params)
159 163 plugins = ','.join(form_result['auth_plugins'])
160 164 setting = SettingsModel().create_or_update_setting(
161 165 'auth_plugins', plugins)
162 166 Session().add(setting)
163 167 Session().commit()
164 168
165 169 cache_manager = get_auth_cache_manager()
166 170 cache_manager.clear()
167 171 self.request.session.flash(
168 172 _('Auth settings updated successfully.'),
169 173 queue='success')
170 174 except formencode.Invalid as errors:
171 175 e = errors.error_dict or {}
172 176 self.request.session.flash(
173 177 _('Errors exist when saving plugin setting. '
174 178 'Please check the form inputs.'),
175 179 queue='error')
176 180 return self.index(
177 181 defaults=errors.value,
178 182 errors=e,
179 183 prefix_error=False)
180 184 except Exception:
181 185 log.exception('Exception in auth_settings')
182 186 self.request.session.flash(
183 187 _('Error occurred during update of auth settings.'),
184 188 queue='error')
185 189
186 190 redirect_to = self.request.resource_path(
187 191 self.context, route_name='auth_home')
188 192 return HTTPFound(redirect_to)
@@ -1,192 +1,192 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons environment configuration
23 23 """
24 24
25 25 import os
26 26 import logging
27 27 import rhodecode
28 28 import platform
29 29 import re
30 30 import io
31 31
32 32 from mako.lookup import TemplateLookup
33 33 from pylons.configuration import PylonsConfig
34 34 from pylons.error import handle_mako_error
35 35 from pyramid.settings import asbool
36 36
37 37 # don't remove this import it does magic for celery
38 38 from rhodecode.lib import celerypylons # noqa
39 39
40 40 import rhodecode.lib.app_globals as app_globals
41 41
42 42 from rhodecode.config import utils
43 43 from rhodecode.config.routing import make_map
44 44 from rhodecode.config.jsroutes import generate_jsroutes_content
45 45
46 46 from rhodecode.lib import helpers
47 47 from rhodecode.lib.auth import set_available_permissions
48 48 from rhodecode.lib.utils import (
49 49 repo2db_mapper, make_db_config, set_rhodecode_config,
50 50 load_rcextensions)
51 51 from rhodecode.lib.utils2 import str2bool, aslist
52 52 from rhodecode.lib.vcs import connect_vcs, start_vcs_server
53 53 from rhodecode.model.scm import ScmModel
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57 def load_environment(global_conf, app_conf, initial=False,
58 58 test_env=None, test_index=None):
59 59 """
60 60 Configure the Pylons environment via the ``pylons.config``
61 61 object
62 62 """
63 63 config = PylonsConfig()
64 64
65 65
66 66 # Pylons paths
67 67 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
68 68 paths = {
69 69 'root': root,
70 70 'controllers': os.path.join(root, 'controllers'),
71 71 'static_files': os.path.join(root, 'public'),
72 72 'templates': [os.path.join(root, 'templates')],
73 73 }
74 74
75 75 # Initialize config with the basic options
76 76 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
77 77
78 78 # store some globals into rhodecode
79 79 rhodecode.CELERY_ENABLED = str2bool(config['app_conf'].get('use_celery'))
80 80 rhodecode.CELERY_EAGER = str2bool(
81 81 config['app_conf'].get('celery.always.eager'))
82 82
83 83 config['routes.map'] = make_map(config)
84 84
85 if asbool(config['debug']):
85 if asbool(config.get('generate_js_files', 'false')):
86 86 jsroutes = config['routes.map'].jsroutes()
87 87 jsroutes_file_content = generate_jsroutes_content(jsroutes)
88 88 jsroutes_file_path = os.path.join(
89 89 paths['static_files'], 'js', 'rhodecode', 'routes.js')
90 90
91 91 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
92 92 f.write(jsroutes_file_content)
93 93
94 94 config['pylons.app_globals'] = app_globals.Globals(config)
95 95 config['pylons.h'] = helpers
96 96 rhodecode.CONFIG = config
97 97
98 98 load_rcextensions(root_path=config['here'])
99 99
100 100 # Setup cache object as early as possible
101 101 import pylons
102 102 pylons.cache._push_object(config['pylons.app_globals'].cache)
103 103
104 104 # Create the Mako TemplateLookup, with the default auto-escaping
105 105 config['pylons.app_globals'].mako_lookup = TemplateLookup(
106 106 directories=paths['templates'],
107 107 error_handler=handle_mako_error,
108 108 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
109 109 input_encoding='utf-8', default_filters=['escape'],
110 110 imports=['from webhelpers.html import escape'])
111 111
112 112 # sets the c attribute access when don't existing attribute are accessed
113 113 config['pylons.strict_tmpl_context'] = True
114 114
115 115 # Limit backends to "vcs.backends" from configuration
116 116 backends = config['vcs.backends'] = aslist(
117 117 config.get('vcs.backends', 'hg,git'), sep=',')
118 118 for alias in rhodecode.BACKENDS.keys():
119 119 if alias not in backends:
120 120 del rhodecode.BACKENDS[alias]
121 121 log.info("Enabled backends: %s", backends)
122 122
123 123 # initialize vcs client and optionally run the server if enabled
124 124 vcs_server_uri = config.get('vcs.server', '')
125 125 vcs_server_enabled = str2bool(config.get('vcs.server.enable', 'true'))
126 126 start_server = (
127 127 str2bool(config.get('vcs.start_server', 'false')) and
128 128 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
129 129 if vcs_server_enabled and start_server:
130 130 log.info("Starting vcsserver")
131 131 start_vcs_server(server_and_port=vcs_server_uri,
132 132 protocol=utils.get_vcs_server_protocol(config),
133 133 log_level=config['vcs.server.log_level'])
134 134
135 135 set_available_permissions(config)
136 136 db_cfg = make_db_config(clear_session=True)
137 137
138 138 repos_path = list(db_cfg.items('paths'))[0][1]
139 139 config['base_path'] = repos_path
140 140
141 141 config['vcs.hooks.direct_calls'] = _use_direct_hook_calls(config)
142 142 config['vcs.hooks.protocol'] = _get_vcs_hooks_protocol(config)
143 143
144 144 # store db config also in main global CONFIG
145 145 set_rhodecode_config(config)
146 146
147 147 # configure instance id
148 148 utils.set_instance_id(config)
149 149
150 150 # CONFIGURATION OPTIONS HERE (note: all config options will override
151 151 # any Pylons config options)
152 152
153 153 # store config reference into our module to skip import magic of pylons
154 154 rhodecode.CONFIG.update(config)
155 155
156 156 utils.configure_pyro4(config)
157 157 utils.configure_vcs(config)
158 158 if vcs_server_enabled:
159 159 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(config))
160 160
161 161 import_on_startup = str2bool(config.get('startup.import_repos', False))
162 162 if vcs_server_enabled and import_on_startup:
163 163 repo2db_mapper(ScmModel().repo_scan(repos_path), remove_obsolete=False)
164 164 return config
165 165
166 166
167 167 def _use_direct_hook_calls(config):
168 168 default_direct_hook_calls = 'false'
169 169 direct_hook_calls = str2bool(
170 170 config.get('vcs.hooks.direct_calls', default_direct_hook_calls))
171 171 return direct_hook_calls
172 172
173 173
174 174 def _get_vcs_hooks_protocol(config):
175 175 protocol = config.get('vcs.hooks.protocol', 'pyro4').lower()
176 176 return protocol
177 177
178 178
179 179 def load_pyramid_environment(global_config, settings):
180 180 # Some parts of the code expect a merge of global and app settings.
181 181 settings_merged = global_config.copy()
182 182 settings_merged.update(settings)
183 183
184 184 # If this is a test run we prepare the test environment like
185 185 # creating a test database, test search index and test repositories.
186 186 # This has to be done before the database connection is initialized.
187 187 if settings['is_test']:
188 188 rhodecode.is_test = True
189 189 utils.initialize_test_environment(settings_merged)
190 190
191 191 # Initialize the database connection.
192 192 utils.initialize_database(settings_merged)
@@ -1,42 +1,43 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 def generate_jsroutes_content(jsroutes):
22 22 statements = []
23 23 for url_name, url, fields in jsroutes:
24 24 statements.append(
25 25 "pyroutes.register('%s', '%s', %s);" % (url_name, url, fields))
26 26 return u'''
27 27 /******************************************************************************
28 28 * *
29 29 * DO NOT CHANGE THIS FILE MANUALLY *
30 30 * *
31 31 * *
32 * This file is automatically generated when the app starts up. *
32 * This file is automatically generated when the app starts up with *
33 * generate_js_files = true *
33 34 * *
34 35 * To add a route here pass jsroute=True to the route definition in the app *
35 36 * *
36 37 ******************************************************************************/
37 38 function registerRCRoutes() {
38 39 // routes registration
39 40 %s
40 41 }
41 42 ''' % '\n '.join(statements)
42 43
@@ -1,217 +1,256 b''
1 1 {
2 "cyrus-sasl-2.1.26": {
3 "cyrus": "http://cyrusimap.web.cmu.edu/mediawiki/index.php/Downloads#Licensing"
2 "nodejs-4.3.1": {
3 "MIT License": "http://spdx.org/licenses/MIT"
4 4 },
5 "openldap-2.4.41": {
6 "OLDAP-2.8": "http://spdx.org/licenses/OLDAP-2.8"
5 "postgresql-9.5.1": {
6 "PostgreSQL License": "http://spdx.org/licenses/PostgreSQL"
7 7 },
8 "openssl-1.0.1p": {
9 "OpenSSL": "http://spdx.org/licenses/OpenSSL"
10 },
11 "python-2.7.10": {
12 "Python-2.0": "http://spdx.org/licenses/Python-2.0"
8 "python-2.7.11": {
9 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
13 10 },
14 11 "python2.7-Babel-1.3": {
15 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
12 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
16 13 },
17 14 "python2.7-Beaker-1.7.0": {
18 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
15 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
19 16 },
20 17 "python2.7-FormEncode-1.2.4": {
21 "Python-2.0": "http://spdx.org/licenses/Python-2.0"
18 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
22 19 },
23 20 "python2.7-Mako-1.0.1": {
24 "MIT": "http://spdx.org/licenses/MIT"
21 "MIT License": "http://spdx.org/licenses/MIT"
25 22 },
26 23 "python2.7-Markdown-2.6.2": {
27 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
24 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
28 25 },
29 26 "python2.7-MarkupSafe-0.23": {
30 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
27 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
31 28 },
32 29 "python2.7-Paste-2.0.2": {
33 "MIT": "http://spdx.org/licenses/MIT"
30 "MIT License": "http://spdx.org/licenses/MIT"
34 31 },
35 32 "python2.7-PasteDeploy-1.5.2": {
36 "MIT": "http://spdx.org/licenses/MIT"
33 "MIT License": "http://spdx.org/licenses/MIT"
37 34 },
38 35 "python2.7-PasteScript-1.7.5": {
39 "MIT": "http://spdx.org/licenses/MIT"
36 "MIT License": "http://spdx.org/licenses/MIT"
40 37 },
41 38 "python2.7-Pygments-2.0.2": {
42 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
39 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
43 40 },
44 "python2.7-Pylons-1.0.2-patch1": {
45 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
41 "python2.7-Pylons-1.0.1-patch1": {
42 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
46 43 },
47 44 "python2.7-Pyro4-4.35": {
48 "MIT": "http://spdx.org/licenses/MIT"
45 "MIT License": "http://spdx.org/licenses/MIT"
49 46 },
50 47 "python2.7-Routes-1.13": {
51 "MIT": "http://spdx.org/licenses/MIT"
48 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
52 49 },
53 50 "python2.7-SQLAlchemy-0.9.9": {
54 "MIT": "http://spdx.org/licenses/MIT"
51 "MIT License": "http://spdx.org/licenses/MIT"
55 52 },
56 53 "python2.7-Tempita-0.5.2": {
57 "MIT": "http://spdx.org/licenses/MIT"
54 "MIT License": "http://spdx.org/licenses/MIT"
58 55 },
59 56 "python2.7-URLObject-2.4.0": {
60 "Unlicense": "http://spdx.org/licenses/Unlicense"
57 "The Unlicense": "http://unlicense.org/"
61 58 },
62 59 "python2.7-WebError-0.10.3": {
63 "MIT": "http://spdx.org/licenses/MIT"
60 "MIT License": "http://spdx.org/licenses/MIT"
64 61 },
65 "python2.7-WebHelpers-1.3-cust1": {
66 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
62 "python2.7-WebHelpers-1.3": {
63 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
67 64 },
68 65 "python2.7-WebHelpers2-2.0": {
69 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
66 "MIT License": "http://spdx.org/licenses/MIT"
70 67 },
71 68 "python2.7-WebOb-1.3.1": {
72 "MIT": "http://spdx.org/licenses/MIT"
69 "MIT License": "http://spdx.org/licenses/MIT"
73 70 },
74 "python2.7-Whoosh-2.7.0-patch1": {
75 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
71 "python2.7-Whoosh-2.7.0": {
72 "BSD 2-clause \"Simplified\" License": "http://spdx.org/licenses/BSD-2-Clause",
73 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
76 74 },
77 75 "python2.7-alembic-0.8.4": {
78 "MIT": "http://spdx.org/licenses/MIT"
76 "MIT License": "http://spdx.org/licenses/MIT"
79 77 },
80 78 "python2.7-amqplib-1.0.2": {
81 "LGPL-3.0": "http://spdx.org/licenses/LGPL-3.0"
79 "GNU Lesser General Public License v3.0 only": "http://spdx.org/licenses/LGPL-3.0"
82 80 },
83 81 "python2.7-anyjson-0.3.3": {
84 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
82 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
83 },
84 "python2.7-appenlight-client-0.6.14": {
85 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
85 86 },
86 "python2.7-appenlight_client-0.6.14": {
87 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
87 "python2.7-authomatic-0.1.0.post1": {
88 "MIT License": "http://spdx.org/licenses/MIT"
88 89 },
89 "python2.7-backport_ipaddress-0.1": {
90 "Python-2.0": "http://spdx.org/licenses/Python-2.0"
90 "python2.7-backport-ipaddress-0.1": {
91 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
91 92 },
92 93 "python2.7-celery-2.2.10": {
93 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
94 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
94 95 },
95 "python2.7-click-4.0": {
96 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
96 "python2.7-click-5.1": {
97 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
98 },
99 "python2.7-colander-1.2": {
100 "Repoze License": "http://www.repoze.org/LICENSE.txt"
97 101 },
98 102 "python2.7-configobj-5.0.6": {
99 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
103 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
100 104 },
101 105 "python2.7-cssselect-0.9.1": {
102 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
106 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
103 107 },
104 108 "python2.7-decorator-3.4.2": {
105 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
109 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
106 110 },
107 111 "python2.7-docutils-0.12": {
108 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
112 "BSD 2-clause \"Simplified\" License": "http://spdx.org/licenses/BSD-2-Clause"
113 },
114 "python2.7-elasticsearch-2.3.0": {
115 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
116 },
117 "python2.7-elasticsearch-dsl-2.0.0": {
118 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
109 119 },
110 120 "python2.7-future-0.14.3": {
111 "MIT": "http://spdx.org/licenses/MIT"
121 "MIT License": "http://spdx.org/licenses/MIT"
112 122 },
113 123 "python2.7-futures-3.0.2": {
114 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
124 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
125 },
126 "python2.7-gnureadline-6.3.3": {
127 "GNU General Public License v1.0 only": "http://spdx.org/licenses/GPL-1.0"
115 128 },
116 "python2.7-greenlet-0.4.7": {
117 "MIT": "http://spdx.org/licenses/MIT"
129 "python2.7-gunicorn-19.6.0": {
130 "MIT License": "http://spdx.org/licenses/MIT"
118 131 },
119 "python2.7-gunicorn-19.3.0": {
120 "MIT": "http://spdx.org/licenses/MIT"
132 "python2.7-infrae.cache-1.0.1": {
133 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
121 134 },
122 135 "python2.7-ipython-3.1.0": {
123 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
124 },
125 "python2.7-kombu-1.5.1-patch1": {
126 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
136 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
127 137 },
128 "python2.7-mccabe-0.3": {
129 "expat": "http://directory.fsf.org/wiki/License:Expat"
138 "python2.7-iso8601-0.1.11": {
139 "MIT License": "http://spdx.org/licenses/MIT"
130 140 },
131 "python2.7-meld3-1.0.2": {
132 "repoze": "http://repoze.org/license.html"
141 "python2.7-kombu-1.5.1": {
142 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
133 143 },
134 144 "python2.7-msgpack-python-0.4.6": {
135 "Apache-2.0": "http://spdx.org/licenses/Apache-2.0"
136 },
137 "python2.7-objgraph-2.0.0": {
138 "MIT": "http://spdx.org/licenses/MIT"
145 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
139 146 },
140 147 "python2.7-packaging-15.2": {
141 "Apache-2.0": "http://spdx.org/licenses/Apache-2.0"
148 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
142 149 },
143 150 "python2.7-psutil-2.2.1": {
144 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
151 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
145 152 },
146 153 "python2.7-psycopg2-2.6": {
147 "LGPL-3.0+": "http://spdx.org/licenses/LGPL-3.0+"
154 "GNU Lesser General Public License v3.0 or later": "http://spdx.org/licenses/LGPL-3.0+"
148 155 },
149 156 "python2.7-py-1.4.29": {
150 "MIT": "http://spdx.org/licenses/MIT"
157 "MIT License": "http://spdx.org/licenses/MIT"
151 158 },
152 159 "python2.7-py-bcrypt-0.4": {
153 "BSD-4-Clause": "http://spdx.org/licenses/BSD-4-Clause"
160 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
154 161 },
155 162 "python2.7-pycrypto-2.6.1": {
156 "publicDomain": null
163 "Public Domain": null
164 },
165 "python2.7-pycurl-7.19.5": {
166 "MIT License": "http://spdx.org/licenses/MIT"
157 167 },
158 168 "python2.7-pyparsing-1.5.7": {
159 "MIT": "http://spdx.org/licenses/MIT"
169 "MIT License": "http://spdx.org/licenses/MIT"
170 },
171 "python2.7-pyramid-1.6.1": {
172 "Repoze License": "http://www.repoze.org/LICENSE.txt"
173 },
174 "python2.7-pyramid-beaker-0.8": {
175 "Repoze License": "http://www.repoze.org/LICENSE.txt"
176 },
177 "python2.7-pyramid-debugtoolbar-2.4.2": {
178 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause",
179 "Repoze License": "http://www.repoze.org/LICENSE.txt"
180 },
181 "python2.7-pyramid-mako-1.0.2": {
182 "Repoze License": "http://www.repoze.org/LICENSE.txt"
160 183 },
161 184 "python2.7-pysqlite-2.6.3": {
162 "Libpng": "http://spdx.org/licenses/Libpng",
163 "Zlib": "http://spdx.org/licenses/Zlib"
185 "libpng License": "http://spdx.org/licenses/Libpng",
186 "zlib License": "http://spdx.org/licenses/Zlib"
164 187 },
165 188 "python2.7-pytest-2.8.5": {
166 "MIT": "http://spdx.org/licenses/MIT"
189 "MIT License": "http://spdx.org/licenses/MIT"
190 },
191 "python2.7-pytest-runner-2.7.1": {
192 "MIT License": "http://spdx.org/licenses/MIT"
167 193 },
168 194 "python2.7-python-dateutil-1.5": {
169 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
195 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
196 },
197 "python2.7-python-editor-1.0.1": {
198 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
170 199 },
171 200 "python2.7-python-ldap-2.4.19": {
172 "Python-2.0": "http://spdx.org/licenses/Python-2.0"
201 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
202 },
203 "python2.7-python-memcached-1.57": {
204 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
173 205 },
174 206 "python2.7-pytz-2015.4": {
175 "MIT": "http://spdx.org/licenses/MIT"
207 "MIT License": "http://spdx.org/licenses/MIT"
176 208 },
177 209 "python2.7-recaptcha-client-1.0.6": {
178 "MIT": "http://spdx.org/licenses/MIT"
210 "MIT License": "http://spdx.org/licenses/MIT"
179 211 },
180 212 "python2.7-repoze.lru-0.6": {
181 "repoze": "http://repoze.org/license.html"
213 "Repoze License": "http://www.repoze.org/LICENSE.txt"
182 214 },
183 "python2.7-requests-2.5.1": {
184 "APSL-2.0": "http://spdx.org/licenses/APSL-2.0"
215 "python2.7-requests-2.9.1": {
216 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
185 217 },
186 "python2.7-serpent-1.11": {
187 "MIT": "http://spdx.org/licenses/MIT"
218 "python2.7-serpent-1.12": {
219 "MIT License": "http://spdx.org/licenses/MIT"
188 220 },
189 "python2.7-setproctitle-1.1.8": {
190 "BSD-2-Clause": "http://spdx.org/licenses/BSD-2-Clause"
221 "python2.7-setuptools-19.4": {
222 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0",
223 "Zope Public License 2.0": "http://spdx.org/licenses/ZPL-2.0"
191 224 },
192 "python2.7-setuptools-18.0.1": {
193 "PSF": null,
194 "ZPL": null
225 "python2.7-setuptools-scm-1.11.0": {
226 "MIT License": "http://spdx.org/licenses/MIT"
195 227 },
196 228 "python2.7-simplejson-3.7.2": {
197 "MIT": "http://spdx.org/licenses/MIT"
229 "Academic Free License": "http://spdx.org/licenses/AFL-2.1",
230 "MIT License": "http://spdx.org/licenses/MIT"
198 231 },
199 232 "python2.7-six-1.9.0": {
200 "MIT": "http://spdx.org/licenses/MIT"
233 "MIT License": "http://spdx.org/licenses/MIT"
201 234 },
202 "python2.7-subprocess32-3.2.6": {
203 "Python-2.0": "http://spdx.org/licenses/Python-2.0"
235 "python2.7-translationstring-1.3": {
236 "Repoze License": "http://www.repoze.org/LICENSE.txt"
204 237 },
205 "python2.7-supervisor-3.1.3": {
206 "repoze": "http://repoze.org/license.html"
238 "python2.7-urllib3-1.16": {
239 "MIT License": "http://spdx.org/licenses/MIT"
207 240 },
208 "python2.7-trollius-1.0.4": {
209 "APSL-2.0": "http://spdx.org/licenses/APSL-2.0"
241 "python2.7-venusian-1.0": {
242 "Repoze License": "http://www.repoze.org/LICENSE.txt"
210 243 },
211 244 "python2.7-waitress-0.8.9": {
212 "ZPL-2.1": "http://spdx.org/licenses/ZPL-2.1"
245 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
213 246 },
214 247 "python2.7-zope.cachedescriptors-4.0.0": {
215 "ZPL-2.1": "http://spdx.org/licenses/ZPL-2.1"
248 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
249 },
250 "python2.7-zope.deprecation-4.1.2": {
251 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
252 },
253 "python2.7-zope.interface-4.1.3": {
254 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
216 255 }
217 }
256 } No newline at end of file
@@ -1,316 +1,387 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25
26 26 from paste.registry import RegistryManager
27 27 from paste.gzipper import make_gzip_middleware
28 from pylons.middleware import ErrorHandler, StatusCodeRedirect
29 28 from pylons.wsgiapp import PylonsApp
30 29 from pyramid.authorization import ACLAuthorizationPolicy
31 30 from pyramid.config import Configurator
32 31 from pyramid.static import static_view
33 32 from pyramid.settings import asbool, aslist
34 33 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
35 import pyramid.httpexceptions as httpexceptions
36 from pyramid.renderers import render_to_response, render
35 37 from routes.middleware import RoutesMiddleware
36 38 import routes.util
37 39
38 40 import rhodecode
39 41 from rhodecode.config import patches
40 42 from rhodecode.config.environment import (
41 43 load_environment, load_pyramid_environment)
42 44 from rhodecode.lib.middleware import csrf
43 45 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
44 46 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
45 47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
46 48 from rhodecode.lib.middleware.vcs import VCSMiddleware
47 49 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
48 50
49 51
50 52 log = logging.getLogger(__name__)
51 53
52 54
53 55 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
54 56 """Create a Pylons WSGI application and return it
55 57
56 58 ``global_conf``
57 59 The inherited configuration for this application. Normally from
58 60 the [DEFAULT] section of the Paste ini file.
59 61
60 62 ``full_stack``
61 63 Whether or not this application provides a full WSGI stack (by
62 64 default, meaning it handles its own exceptions and errors).
63 65 Disable full_stack when this application is "managed" by
64 66 another WSGI middleware.
65 67
66 68 ``app_conf``
67 69 The application's local configuration. Normally specified in
68 70 the [app:<name>] section of the Paste ini file (where <name>
69 71 defaults to main).
70 72
71 73 """
72 74 # Apply compatibility patches
73 75 patches.kombu_1_5_1_python_2_7_11()
74 76 patches.inspect_getargspec()
75 77
76 78 # Configure the Pylons environment
77 79 config = load_environment(global_conf, app_conf)
78 80
79 81 # The Pylons WSGI app
80 82 app = PylonsApp(config=config)
81 83 if rhodecode.is_test:
82 84 app = csrf.CSRFDetector(app)
83 85
84 86 expected_origin = config.get('expected_origin')
85 87 if expected_origin:
86 88 # The API can be accessed from other Origins.
87 89 app = csrf.OriginChecker(app, expected_origin,
88 90 skip_urls=[routes.util.url_for('api')])
89 91
90 # Add RoutesMiddleware. Currently we have two instances in the stack. This
91 # is the lower one to make the StatusCodeRedirect middleware happy.
92 # TODO: johbo: This is not optimal, search for a better solution.
93 app = RoutesMiddleware(app, config['routes.map'])
94
95 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
96 if asbool(config['pdebug']):
97 from rhodecode.lib.profiler import ProfilingMiddleware
98 app = ProfilingMiddleware(app)
99
100 # Protect from VCS Server error related pages when server is not available
101 vcs_server_enabled = asbool(config.get('vcs.server.enable', 'true'))
102 if not vcs_server_enabled:
103 app = DisableVCSPagesWrapper(app)
104 92
105 93 if asbool(full_stack):
106 94
107 95 # Appenlight monitoring and error handler
108 96 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
109 97
110 # Handle Python exceptions
111 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
112
113 98 # we want our low level middleware to get to the request ASAP. We don't
114 99 # need any pylons stack middleware in them
115 100 app = VCSMiddleware(app, config, appenlight_client)
116 # Display error documents for 401, 403, 404 status codes (and
117 # 500 when debug is disabled)
118 if asbool(config['debug']):
119 app = StatusCodeRedirect(app)
120 else:
121 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
122 101
123 102 # Establish the Registry for this application
124 103 app = RegistryManager(app)
125 104
126 105 app.config = config
127 106
128 107 return app
129 108
130 109
131 110 def make_pyramid_app(global_config, **settings):
132 111 """
133 112 Constructs the WSGI application based on Pyramid and wraps the Pylons based
134 113 application.
135 114
136 115 Specials:
137 116
138 117 * We migrate from Pylons to Pyramid. While doing this, we keep both
139 118 frameworks functional. This involves moving some WSGI middlewares around
140 119 and providing access to some data internals, so that the old code is
141 120 still functional.
142 121
143 122 * The application can also be integrated like a plugin via the call to
144 123 `includeme`. This is accompanied with the other utility functions which
145 124 are called. Changing this should be done with great care to not break
146 125 cases when these fragments are assembled from another place.
147 126
148 127 """
149 128 # The edition string should be available in pylons too, so we add it here
150 129 # before copying the settings.
151 130 settings.setdefault('rhodecode.edition', 'Community Edition')
152 131
153 132 # As long as our Pylons application does expect "unprepared" settings, make
154 133 # sure that we keep an unmodified copy. This avoids unintentional change of
155 134 # behavior in the old application.
156 135 settings_pylons = settings.copy()
157 136
158 137 sanitize_settings_and_apply_defaults(settings)
159 138 config = Configurator(settings=settings)
160 139 add_pylons_compat_data(config.registry, global_config, settings_pylons)
161 140
162 141 load_pyramid_environment(global_config, settings)
163 142
164 143 includeme(config)
165 144 includeme_last(config)
166 145 pyramid_app = config.make_wsgi_app()
167 146 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
168 147 return pyramid_app
169 148
170 149
171 150 def add_pylons_compat_data(registry, global_config, settings):
172 151 """
173 152 Attach data to the registry to support the Pylons integration.
174 153 """
175 154 registry._pylons_compat_global_config = global_config
176 155 registry._pylons_compat_settings = settings
177 156
178 157
158 def webob_to_pyramid_http_response(webob_response):
159 ResponseClass = httpexceptions.status_map[webob_response.status_int]
160 pyramid_response = ResponseClass(webob_response.status)
161 pyramid_response.status = webob_response.status
162 pyramid_response.headers.update(webob_response.headers)
163 if pyramid_response.headers['content-type'] == 'text/html':
164 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
165 return pyramid_response
166
167
168 def error_handler(exception, request):
169 # TODO: dan: replace the old pylons error controller with this
170 from rhodecode.model.settings import SettingsModel
171 from rhodecode.lib.utils2 import AttributeDict
172
173 try:
174 rc_config = SettingsModel().get_all_settings()
175 except Exception:
176 log.exception('failed to fetch settings')
177 rc_config = {}
178
179 base_response = HTTPInternalServerError()
180 # prefer original exception for the response since it may have headers set
181 if isinstance(exception, HTTPError):
182 base_response = exception
183
184 c = AttributeDict()
185 c.error_message = base_response.status
186 c.error_explanation = base_response.explanation or str(base_response)
187 c.visual = AttributeDict()
188
189 c.visual.rhodecode_support_url = (
190 request.registry.settings.get('rhodecode_support_url') or
191 request.route_url('rhodecode_support')
192 )
193 c.redirect_time = 0
194 c.rhodecode_name = rc_config.get('rhodecode_title', '')
195 if not c.rhodecode_name:
196 c.rhodecode_name = 'Rhodecode'
197
198 response = render_to_response(
199 '/errors/error_document.html', {'c': c}, request=request,
200 response=base_response)
201
202 return response
203
204
179 205 def includeme(config):
180 206 settings = config.registry.settings
181 207
208 if asbool(settings.get('appenlight', 'false')):
209 config.include('appenlight_client.ext.pyramid_tween')
210
182 211 # Includes which are required. The application would fail without them.
183 212 config.include('pyramid_mako')
184 213 config.include('pyramid_beaker')
214 config.include('rhodecode.admin')
185 215 config.include('rhodecode.authentication')
186 216 config.include('rhodecode.login')
187 217 config.include('rhodecode.tweens')
188 218 config.include('rhodecode.api')
219 config.add_route(
220 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
189 221
190 222 # Set the authorization policy.
191 223 authz_policy = ACLAuthorizationPolicy()
192 224 config.set_authorization_policy(authz_policy)
193 225
194 226 # Set the default renderer for HTML templates to mako.
195 227 config.add_mako_renderer('.html')
196 228
197 229 # plugin information
198 230 config.registry.rhodecode_plugins = {}
199 231
200 232 config.add_directive(
201 233 'register_rhodecode_plugin', register_rhodecode_plugin)
202 234 # include RhodeCode plugins
203 235 includes = aslist(settings.get('rhodecode.includes', []))
204 236 for inc in includes:
205 237 config.include(inc)
206 238
239 pylons_app = make_app(
240 config.registry._pylons_compat_global_config,
241 **config.registry._pylons_compat_settings)
242 config.registry._pylons_compat_config = pylons_app.config
243
244 pylons_app_as_view = wsgiapp(pylons_app)
245
246 # Protect from VCS Server error related pages when server is not available
247 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
248 if not vcs_server_enabled:
249 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
250
251
252 def pylons_app_with_error_handler(context, request):
253 """
254 Handle exceptions from rc pylons app:
255
256 - old webob type exceptions get converted to pyramid exceptions
257 - pyramid exceptions are passed to the error handler view
258 """
259 try:
260 response = pylons_app_as_view(context, request)
261 if 400 <= response.status_int <= 599: # webob type error responses
262 return error_handler(
263 webob_to_pyramid_http_response(response), request)
264 except HTTPError as e: # pyramid type exceptions
265 return error_handler(e, request)
266 except Exception:
267 if settings.get('debugtoolbar.enabled', False):
268 raise
269 return error_handler(HTTPInternalServerError(), request)
270 return response
271
207 272 # This is the glue which allows us to migrate in chunks. By registering the
208 273 # pylons based application as the "Not Found" view in Pyramid, we will
209 274 # fallback to the old application each time the new one does not yet know
210 275 # how to handle a request.
211 pylons_app = make_app(
212 config.registry._pylons_compat_global_config,
213 **config.registry._pylons_compat_settings)
214 config.registry._pylons_compat_config = pylons_app.config
215 pylons_app_as_view = wsgiapp(pylons_app)
216 config.add_notfound_view(pylons_app_as_view)
276 config.add_notfound_view(pylons_app_with_error_handler)
277
278 if settings.get('debugtoolbar.enabled', False):
279 # if toolbar, then only http type exceptions get caught and rendered
280 ExcClass = HTTPError
281 else:
282 # if no toolbar, then any exception gets caught and rendered
283 ExcClass = Exception
284 config.add_view(error_handler, context=ExcClass)
217 285
218 286
219 287 def includeme_last(config):
220 288 """
221 289 The static file catchall needs to be last in the view configuration.
222 290 """
223 291 settings = config.registry.settings
224 292
225 293 # Note: johbo: I would prefer to register a prefix for static files at some
226 294 # point, e.g. move them under '_static/'. This would fully avoid that we
227 295 # can have name clashes with a repository name. Imaging someone calling his
228 296 # repo "css" ;-) Also having an external web server to serve out the static
229 297 # files seems to be easier to set up if they have a common prefix.
230 298 #
231 299 # Example: config.add_static_view('_static', path='rhodecode:public')
232 300 #
233 301 # It might be an option to register both paths for a while and then migrate
234 302 # over to the new location.
235 303
236 304 # Serving static files with a catchall.
237 305 if settings['static_files']:
238 306 config.add_route('catchall_static', '/*subpath')
239 307 config.add_view(
240 308 static_view('rhodecode:public'), route_name='catchall_static')
241 309
242 310
243 311 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
244 312 """
245 313 Apply outer WSGI middlewares around the application.
246 314
247 315 Part of this has been moved up from the Pylons layer, so that the
248 316 data is also available if old Pylons code is hit through an already ported
249 317 view.
250 318 """
251 319 settings = config.registry.settings
252 320
253 321 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
254 322 pyramid_app = HttpsFixup(pyramid_app, settings)
255 323
256 # Add RoutesMiddleware. Currently we have two instances in the stack. This
257 # is the upper one to support the pylons compatibility tween during
324 # Add RoutesMiddleware to support the pylons compatibility tween during
258 325
259 326 # migration to pyramid.
260 327 pyramid_app = RoutesMiddleware(
261 328 pyramid_app, config.registry._pylons_compat_config['routes.map'])
262 329
330 if asbool(settings.get('appenlight', 'false')):
331 pyramid_app, _ = wrap_in_appenlight_if_enabled(
332 pyramid_app, config.registry._pylons_compat_config)
333
263 334 # TODO: johbo: Don't really see why we enable the gzip middleware when
264 335 # serving static files, might be something that should have its own setting
265 336 # as well?
266 337 if settings['static_files']:
267 338 pyramid_app = make_gzip_middleware(
268 339 pyramid_app, settings, compress_level=1)
269 340
270 341 return pyramid_app
271 342
272 343
273 344 def sanitize_settings_and_apply_defaults(settings):
274 345 """
275 346 Applies settings defaults and does all type conversion.
276 347
277 348 We would move all settings parsing and preparation into this place, so that
278 349 we have only one place left which deals with this part. The remaining parts
279 350 of the application would start to rely fully on well prepared settings.
280 351
281 352 This piece would later be split up per topic to avoid a big fat monster
282 353 function.
283 354 """
284 355
285 356 # Pyramid's mako renderer has to search in the templates folder so that the
286 357 # old templates still work. Ported and new templates are expected to use
287 358 # real asset specifications for the includes.
288 359 mako_directories = settings.setdefault('mako.directories', [
289 360 # Base templates of the original Pylons application
290 361 'rhodecode:templates',
291 362 ])
292 363 log.debug(
293 364 "Using the following Mako template directories: %s",
294 365 mako_directories)
295 366
296 367 # Default includes, possible to change as a user
297 368 pyramid_includes = settings.setdefault('pyramid.includes', [
298 369 'rhodecode.lib.middleware.request_wrapper',
299 370 ])
300 371 log.debug(
301 372 "Using the following pyramid.includes: %s",
302 373 pyramid_includes)
303 374
304 375 # TODO: johbo: Re-think this, usually the call to config.include
305 376 # should allow to pass in a prefix.
306 377 settings.setdefault('rhodecode.api.url', '/_admin/api')
307 378
308 379 _bool_setting(settings, 'vcs.server.enable', 'true')
309 380 _bool_setting(settings, 'static_files', 'true')
310 381 _bool_setting(settings, 'is_test', 'false')
311 382
312 383 return settings
313 384
314 385
315 386 def _bool_setting(settings, name, default):
316 387 settings[name] = asbool(settings.get(name, default))
@@ -1,1149 +1,1141 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 from rhodecode.config import routing_links
36 36
37 37 # prefix for non repository related links needs to be prefixed with `/`
38 38 ADMIN_PREFIX = '/_admin'
39 39
40 40 # Default requirements for URL parts
41 41 URL_NAME_REQUIREMENTS = {
42 42 # group name can have a slash in them, but they must not end with a slash
43 43 'group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 class JSRoutesMapper(Mapper):
55 55 """
56 56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 57 """
58 58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 60 def __init__(self, *args, **kw):
61 61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 62 self._jsroutes = []
63 63
64 64 def connect(self, *args, **kw):
65 65 """
66 66 Wrapper for connect to take an extra argument jsroute=True
67 67
68 68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 69 """
70 70 if kw.pop('jsroute', False):
71 71 if not self._named_route_regex.match(args[0]):
72 72 raise Exception('only named routes can be added to pyroutes')
73 73 self._jsroutes.append(args[0])
74 74
75 75 super(JSRoutesMapper, self).connect(*args, **kw)
76 76
77 77 def _extract_route_information(self, route):
78 78 """
79 79 Convert a route into tuple(name, path, args), eg:
80 80 ('user_profile', '/profile/%(username)s', ['username'])
81 81 """
82 82 routepath = route.routepath
83 83 def replace(matchobj):
84 84 if matchobj.group(1):
85 85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 86 else:
87 87 return "%%(%s)s" % matchobj.group(2)
88 88
89 89 routepath = self._argument_prog.sub(replace, routepath)
90 90 return (
91 91 route.name,
92 92 routepath,
93 93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 94 for arg in self._argument_prog.findall(route.routepath)]
95 95 )
96 96
97 97 def jsroutes(self):
98 98 """
99 99 Return a list of pyroutes.js compatible routes
100 100 """
101 101 for route_name in self._jsroutes:
102 102 yield self._extract_route_information(self._routenames[route_name])
103 103
104 104
105 105 def make_map(config):
106 106 """Create, configure and return the routes Mapper"""
107 107 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
108 108 always_scan=config['debug'])
109 109 rmap.minimization = False
110 110 rmap.explicit = False
111 111
112 112 from rhodecode.lib.utils2 import str2bool
113 113 from rhodecode.model import repo, repo_group
114 114
115 115 def check_repo(environ, match_dict):
116 116 """
117 117 check for valid repository for proper 404 handling
118 118
119 119 :param environ:
120 120 :param match_dict:
121 121 """
122 122 repo_name = match_dict.get('repo_name')
123 123
124 124 if match_dict.get('f_path'):
125 125 # fix for multiple initial slashes that causes errors
126 126 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 127 repo_model = repo.RepoModel()
128 128 by_name_match = repo_model.get_by_repo_name(repo_name)
129 129 # if we match quickly from database, short circuit the operation,
130 130 # and validate repo based on the type.
131 131 if by_name_match:
132 132 return True
133 133
134 134 by_id_match = repo_model.get_repo_by_id(repo_name)
135 135 if by_id_match:
136 136 repo_name = by_id_match.repo_name
137 137 match_dict['repo_name'] = repo_name
138 138 return True
139 139
140 140 return False
141 141
142 142 def check_group(environ, match_dict):
143 143 """
144 144 check for valid repository group path for proper 404 handling
145 145
146 146 :param environ:
147 147 :param match_dict:
148 148 """
149 149 repo_group_name = match_dict.get('group_name')
150 150 repo_group_model = repo_group.RepoGroupModel()
151 151 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 152 if by_name_match:
153 153 return True
154 154
155 155 return False
156 156
157 157 def check_user_group(environ, match_dict):
158 158 """
159 159 check for valid user group for proper 404 handling
160 160
161 161 :param environ:
162 162 :param match_dict:
163 163 """
164 164 return True
165 165
166 166 def check_int(environ, match_dict):
167 167 return match_dict.get('id').isdigit()
168 168
169 # The ErrorController route (handles 404/500 error pages); it should
170 # likely stay at the top, ensuring it can always be resolved
171 rmap.connect('/error/{action}', controller='error')
172 rmap.connect('/error/{action}/{id}', controller='error')
173 169
174 170 #==========================================================================
175 171 # CUSTOM ROUTES HERE
176 172 #==========================================================================
177 173
178 174 # MAIN PAGE
179 175 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
180 176 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
181 177 action='goto_switcher_data')
182 178 rmap.connect('repo_list_data', '/_repos', controller='home',
183 179 action='repo_list_data')
184 180
185 181 rmap.connect('user_autocomplete_data', '/_users', controller='home',
186 182 action='user_autocomplete_data', jsroute=True)
187 183 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
188 184 action='user_group_autocomplete_data')
189 185
190 186 rmap.connect(
191 187 'user_profile', '/_profiles/{username}', controller='users',
192 188 action='user_profile')
193 189
194 190 # TODO: johbo: Static links, to be replaced by our redirection mechanism
195 191 rmap.connect('rst_help',
196 192 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
197 193 _static=True)
198 194 rmap.connect('markdown_help',
199 195 'http://daringfireball.net/projects/markdown/syntax',
200 196 _static=True)
201 197 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
202 198 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
203 199 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
204 200 # TODO: anderson - making this a static link since redirect won't play
205 201 # nice with POST requests
206 202 rmap.connect('enterprise_license_convert_from_old',
207 203 'https://rhodecode.com/u/license-upgrade',
208 204 _static=True)
209 205
210 206 routing_links.connect_redirection_links(rmap)
211 207
212 208 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
213 209 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
214 210
215 211 # ADMIN REPOSITORY ROUTES
216 212 with rmap.submapper(path_prefix=ADMIN_PREFIX,
217 213 controller='admin/repos') as m:
218 214 m.connect('repos', '/repos',
219 215 action='create', conditions={'method': ['POST']})
220 216 m.connect('repos', '/repos',
221 217 action='index', conditions={'method': ['GET']})
222 218 m.connect('new_repo', '/create_repository', jsroute=True,
223 219 action='create_repository', conditions={'method': ['GET']})
224 220 m.connect('/repos/{repo_name}',
225 221 action='update', conditions={'method': ['PUT'],
226 222 'function': check_repo},
227 223 requirements=URL_NAME_REQUIREMENTS)
228 224 m.connect('delete_repo', '/repos/{repo_name}',
229 225 action='delete', conditions={'method': ['DELETE']},
230 226 requirements=URL_NAME_REQUIREMENTS)
231 227 m.connect('repo', '/repos/{repo_name}',
232 228 action='show', conditions={'method': ['GET'],
233 229 'function': check_repo},
234 230 requirements=URL_NAME_REQUIREMENTS)
235 231
236 232 # ADMIN REPOSITORY GROUPS ROUTES
237 233 with rmap.submapper(path_prefix=ADMIN_PREFIX,
238 234 controller='admin/repo_groups') as m:
239 235 m.connect('repo_groups', '/repo_groups',
240 236 action='create', conditions={'method': ['POST']})
241 237 m.connect('repo_groups', '/repo_groups',
242 238 action='index', conditions={'method': ['GET']})
243 239 m.connect('new_repo_group', '/repo_groups/new',
244 240 action='new', conditions={'method': ['GET']})
245 241 m.connect('update_repo_group', '/repo_groups/{group_name}',
246 242 action='update', conditions={'method': ['PUT'],
247 243 'function': check_group},
248 244 requirements=URL_NAME_REQUIREMENTS)
249 245
250 246 # EXTRAS REPO GROUP ROUTES
251 247 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
252 248 action='edit',
253 249 conditions={'method': ['GET'], 'function': check_group},
254 250 requirements=URL_NAME_REQUIREMENTS)
255 251 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
256 252 action='edit',
257 253 conditions={'method': ['PUT'], 'function': check_group},
258 254 requirements=URL_NAME_REQUIREMENTS)
259 255
260 256 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
261 257 action='edit_repo_group_advanced',
262 258 conditions={'method': ['GET'], 'function': check_group},
263 259 requirements=URL_NAME_REQUIREMENTS)
264 260 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
265 261 action='edit_repo_group_advanced',
266 262 conditions={'method': ['PUT'], 'function': check_group},
267 263 requirements=URL_NAME_REQUIREMENTS)
268 264
269 265 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
270 266 action='edit_repo_group_perms',
271 267 conditions={'method': ['GET'], 'function': check_group},
272 268 requirements=URL_NAME_REQUIREMENTS)
273 269 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
274 270 action='update_perms',
275 271 conditions={'method': ['PUT'], 'function': check_group},
276 272 requirements=URL_NAME_REQUIREMENTS)
277 273
278 274 m.connect('delete_repo_group', '/repo_groups/{group_name}',
279 275 action='delete', conditions={'method': ['DELETE'],
280 276 'function': check_group},
281 277 requirements=URL_NAME_REQUIREMENTS)
282 278
283 279 # ADMIN USER ROUTES
284 280 with rmap.submapper(path_prefix=ADMIN_PREFIX,
285 281 controller='admin/users') as m:
286 282 m.connect('users', '/users',
287 283 action='create', conditions={'method': ['POST']})
288 284 m.connect('users', '/users',
289 285 action='index', conditions={'method': ['GET']})
290 286 m.connect('new_user', '/users/new',
291 287 action='new', conditions={'method': ['GET']})
292 288 m.connect('update_user', '/users/{user_id}',
293 289 action='update', conditions={'method': ['PUT']})
294 290 m.connect('delete_user', '/users/{user_id}',
295 291 action='delete', conditions={'method': ['DELETE']})
296 292 m.connect('edit_user', '/users/{user_id}/edit',
297 293 action='edit', conditions={'method': ['GET']})
298 294 m.connect('user', '/users/{user_id}',
299 295 action='show', conditions={'method': ['GET']})
300 296 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
301 297 action='reset_password', conditions={'method': ['POST']})
302 298 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
303 299 action='create_personal_repo_group', conditions={'method': ['POST']})
304 300
305 301 # EXTRAS USER ROUTES
306 302 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
307 303 action='edit_advanced', conditions={'method': ['GET']})
308 304 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
309 305 action='update_advanced', conditions={'method': ['PUT']})
310 306
311 307 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
312 308 action='edit_auth_tokens', conditions={'method': ['GET']})
313 309 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
314 310 action='add_auth_token', conditions={'method': ['PUT']})
315 311 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
316 312 action='delete_auth_token', conditions={'method': ['DELETE']})
317 313
318 314 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
319 315 action='edit_global_perms', conditions={'method': ['GET']})
320 316 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
321 317 action='update_global_perms', conditions={'method': ['PUT']})
322 318
323 319 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
324 320 action='edit_perms_summary', conditions={'method': ['GET']})
325 321
326 322 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
327 323 action='edit_emails', conditions={'method': ['GET']})
328 324 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
329 325 action='add_email', conditions={'method': ['PUT']})
330 326 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
331 327 action='delete_email', conditions={'method': ['DELETE']})
332 328
333 329 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
334 330 action='edit_ips', conditions={'method': ['GET']})
335 331 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
336 332 action='add_ip', conditions={'method': ['PUT']})
337 333 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
338 334 action='delete_ip', conditions={'method': ['DELETE']})
339 335
340 336 # ADMIN USER GROUPS REST ROUTES
341 337 with rmap.submapper(path_prefix=ADMIN_PREFIX,
342 338 controller='admin/user_groups') as m:
343 339 m.connect('users_groups', '/user_groups',
344 340 action='create', conditions={'method': ['POST']})
345 341 m.connect('users_groups', '/user_groups',
346 342 action='index', conditions={'method': ['GET']})
347 343 m.connect('new_users_group', '/user_groups/new',
348 344 action='new', conditions={'method': ['GET']})
349 345 m.connect('update_users_group', '/user_groups/{user_group_id}',
350 346 action='update', conditions={'method': ['PUT']})
351 347 m.connect('delete_users_group', '/user_groups/{user_group_id}',
352 348 action='delete', conditions={'method': ['DELETE']})
353 349 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
354 350 action='edit', conditions={'method': ['GET']},
355 351 function=check_user_group)
356 352
357 353 # EXTRAS USER GROUP ROUTES
358 354 m.connect('edit_user_group_global_perms',
359 355 '/user_groups/{user_group_id}/edit/global_permissions',
360 356 action='edit_global_perms', conditions={'method': ['GET']})
361 357 m.connect('edit_user_group_global_perms',
362 358 '/user_groups/{user_group_id}/edit/global_permissions',
363 359 action='update_global_perms', conditions={'method': ['PUT']})
364 360 m.connect('edit_user_group_perms_summary',
365 361 '/user_groups/{user_group_id}/edit/permissions_summary',
366 362 action='edit_perms_summary', conditions={'method': ['GET']})
367 363
368 364 m.connect('edit_user_group_perms',
369 365 '/user_groups/{user_group_id}/edit/permissions',
370 366 action='edit_perms', conditions={'method': ['GET']})
371 367 m.connect('edit_user_group_perms',
372 368 '/user_groups/{user_group_id}/edit/permissions',
373 369 action='update_perms', conditions={'method': ['PUT']})
374 370
375 371 m.connect('edit_user_group_advanced',
376 372 '/user_groups/{user_group_id}/edit/advanced',
377 373 action='edit_advanced', conditions={'method': ['GET']})
378 374
379 375 m.connect('edit_user_group_members',
380 376 '/user_groups/{user_group_id}/edit/members', jsroute=True,
381 377 action='edit_members', conditions={'method': ['GET']})
382 378
383 379 # ADMIN PERMISSIONS ROUTES
384 380 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 381 controller='admin/permissions') as m:
386 382 m.connect('admin_permissions_application', '/permissions/application',
387 383 action='permission_application_update', conditions={'method': ['POST']})
388 384 m.connect('admin_permissions_application', '/permissions/application',
389 385 action='permission_application', conditions={'method': ['GET']})
390 386
391 387 m.connect('admin_permissions_global', '/permissions/global',
392 388 action='permission_global_update', conditions={'method': ['POST']})
393 389 m.connect('admin_permissions_global', '/permissions/global',
394 390 action='permission_global', conditions={'method': ['GET']})
395 391
396 392 m.connect('admin_permissions_object', '/permissions/object',
397 393 action='permission_objects_update', conditions={'method': ['POST']})
398 394 m.connect('admin_permissions_object', '/permissions/object',
399 395 action='permission_objects', conditions={'method': ['GET']})
400 396
401 397 m.connect('admin_permissions_ips', '/permissions/ips',
402 398 action='permission_ips', conditions={'method': ['POST']})
403 399 m.connect('admin_permissions_ips', '/permissions/ips',
404 400 action='permission_ips', conditions={'method': ['GET']})
405 401
406 402 m.connect('admin_permissions_overview', '/permissions/overview',
407 403 action='permission_perms', conditions={'method': ['GET']})
408 404
409 405 # ADMIN DEFAULTS REST ROUTES
410 406 with rmap.submapper(path_prefix=ADMIN_PREFIX,
411 407 controller='admin/defaults') as m:
412 408 m.connect('admin_defaults_repositories', '/defaults/repositories',
413 409 action='update_repository_defaults', conditions={'method': ['POST']})
414 410 m.connect('admin_defaults_repositories', '/defaults/repositories',
415 411 action='index', conditions={'method': ['GET']})
416 412
417 413 # ADMIN DEBUG STYLE ROUTES
418 414 if str2bool(config.get('debug_style')):
419 415 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
420 416 controller='debug_style') as m:
421 417 m.connect('debug_style_home', '',
422 418 action='index', conditions={'method': ['GET']})
423 419 m.connect('debug_style_template', '/t/{t_path}',
424 420 action='template', conditions={'method': ['GET']})
425 421
426 422 # ADMIN SETTINGS ROUTES
427 423 with rmap.submapper(path_prefix=ADMIN_PREFIX,
428 424 controller='admin/settings') as m:
429 425
430 426 # default
431 427 m.connect('admin_settings', '/settings',
432 428 action='settings_global_update',
433 429 conditions={'method': ['POST']})
434 430 m.connect('admin_settings', '/settings',
435 431 action='settings_global', conditions={'method': ['GET']})
436 432
437 433 m.connect('admin_settings_vcs', '/settings/vcs',
438 434 action='settings_vcs_update',
439 435 conditions={'method': ['POST']})
440 436 m.connect('admin_settings_vcs', '/settings/vcs',
441 437 action='settings_vcs',
442 438 conditions={'method': ['GET']})
443 439 m.connect('admin_settings_vcs', '/settings/vcs',
444 440 action='delete_svn_pattern',
445 441 conditions={'method': ['DELETE']})
446 442
447 443 m.connect('admin_settings_mapping', '/settings/mapping',
448 444 action='settings_mapping_update',
449 445 conditions={'method': ['POST']})
450 446 m.connect('admin_settings_mapping', '/settings/mapping',
451 447 action='settings_mapping', conditions={'method': ['GET']})
452 448
453 449 m.connect('admin_settings_global', '/settings/global',
454 450 action='settings_global_update',
455 451 conditions={'method': ['POST']})
456 452 m.connect('admin_settings_global', '/settings/global',
457 453 action='settings_global', conditions={'method': ['GET']})
458 454
459 455 m.connect('admin_settings_visual', '/settings/visual',
460 456 action='settings_visual_update',
461 457 conditions={'method': ['POST']})
462 458 m.connect('admin_settings_visual', '/settings/visual',
463 459 action='settings_visual', conditions={'method': ['GET']})
464 460
465 461 m.connect('admin_settings_issuetracker',
466 462 '/settings/issue-tracker', action='settings_issuetracker',
467 463 conditions={'method': ['GET']})
468 464 m.connect('admin_settings_issuetracker_save',
469 465 '/settings/issue-tracker/save',
470 466 action='settings_issuetracker_save',
471 467 conditions={'method': ['POST']})
472 468 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
473 469 action='settings_issuetracker_test',
474 470 conditions={'method': ['POST']})
475 471 m.connect('admin_issuetracker_delete',
476 472 '/settings/issue-tracker/delete',
477 473 action='settings_issuetracker_delete',
478 474 conditions={'method': ['DELETE']})
479 475
480 476 m.connect('admin_settings_email', '/settings/email',
481 477 action='settings_email_update',
482 478 conditions={'method': ['POST']})
483 479 m.connect('admin_settings_email', '/settings/email',
484 480 action='settings_email', conditions={'method': ['GET']})
485 481
486 482 m.connect('admin_settings_hooks', '/settings/hooks',
487 483 action='settings_hooks_update',
488 484 conditions={'method': ['POST', 'DELETE']})
489 485 m.connect('admin_settings_hooks', '/settings/hooks',
490 486 action='settings_hooks', conditions={'method': ['GET']})
491 487
492 488 m.connect('admin_settings_search', '/settings/search',
493 489 action='settings_search', conditions={'method': ['GET']})
494 490
495 491 m.connect('admin_settings_system', '/settings/system',
496 492 action='settings_system', conditions={'method': ['GET']})
497 493
498 494 m.connect('admin_settings_system_update', '/settings/system/updates',
499 495 action='settings_system_update', conditions={'method': ['GET']})
500 496
501 497 m.connect('admin_settings_supervisor', '/settings/supervisor',
502 498 action='settings_supervisor', conditions={'method': ['GET']})
503 499 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
504 500 action='settings_supervisor_log', conditions={'method': ['GET']})
505 501
506 502 m.connect('admin_settings_labs', '/settings/labs',
507 503 action='settings_labs_update',
508 504 conditions={'method': ['POST']})
509 505 m.connect('admin_settings_labs', '/settings/labs',
510 506 action='settings_labs', conditions={'method': ['GET']})
511 507
512 m.connect('admin_settings_open_source', '/settings/open_source',
513 action='settings_open_source',
514 conditions={'method': ['GET']})
515
516 508 # ADMIN MY ACCOUNT
517 509 with rmap.submapper(path_prefix=ADMIN_PREFIX,
518 510 controller='admin/my_account') as m:
519 511
520 512 m.connect('my_account', '/my_account',
521 513 action='my_account', conditions={'method': ['GET']})
522 514 m.connect('my_account_edit', '/my_account/edit',
523 515 action='my_account_edit', conditions={'method': ['GET']})
524 516 m.connect('my_account', '/my_account',
525 517 action='my_account_update', conditions={'method': ['POST']})
526 518
527 519 m.connect('my_account_password', '/my_account/password',
528 520 action='my_account_password', conditions={'method': ['GET']})
529 521 m.connect('my_account_password', '/my_account/password',
530 522 action='my_account_password_update', conditions={'method': ['POST']})
531 523
532 524 m.connect('my_account_repos', '/my_account/repos',
533 525 action='my_account_repos', conditions={'method': ['GET']})
534 526
535 527 m.connect('my_account_watched', '/my_account/watched',
536 528 action='my_account_watched', conditions={'method': ['GET']})
537 529
538 530 m.connect('my_account_pullrequests', '/my_account/pull_requests',
539 531 action='my_account_pullrequests', conditions={'method': ['GET']})
540 532
541 533 m.connect('my_account_perms', '/my_account/perms',
542 534 action='my_account_perms', conditions={'method': ['GET']})
543 535
544 536 m.connect('my_account_emails', '/my_account/emails',
545 537 action='my_account_emails', conditions={'method': ['GET']})
546 538 m.connect('my_account_emails', '/my_account/emails',
547 539 action='my_account_emails_add', conditions={'method': ['POST']})
548 540 m.connect('my_account_emails', '/my_account/emails',
549 541 action='my_account_emails_delete', conditions={'method': ['DELETE']})
550 542
551 543 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
552 544 action='my_account_auth_tokens', conditions={'method': ['GET']})
553 545 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
554 546 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
555 547 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
556 548 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
557 549
558 550 # NOTIFICATION REST ROUTES
559 551 with rmap.submapper(path_prefix=ADMIN_PREFIX,
560 552 controller='admin/notifications') as m:
561 553 m.connect('notifications', '/notifications',
562 554 action='index', conditions={'method': ['GET']})
563 555 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
564 556 action='mark_all_read', conditions={'method': ['POST']})
565 557
566 558 m.connect('/notifications/{notification_id}',
567 559 action='update', conditions={'method': ['PUT']})
568 560 m.connect('/notifications/{notification_id}',
569 561 action='delete', conditions={'method': ['DELETE']})
570 562 m.connect('notification', '/notifications/{notification_id}',
571 563 action='show', conditions={'method': ['GET']})
572 564
573 565 # ADMIN GIST
574 566 with rmap.submapper(path_prefix=ADMIN_PREFIX,
575 567 controller='admin/gists') as m:
576 568 m.connect('gists', '/gists',
577 569 action='create', conditions={'method': ['POST']})
578 570 m.connect('gists', '/gists', jsroute=True,
579 571 action='index', conditions={'method': ['GET']})
580 572 m.connect('new_gist', '/gists/new', jsroute=True,
581 573 action='new', conditions={'method': ['GET']})
582 574
583 575 m.connect('/gists/{gist_id}',
584 576 action='delete', conditions={'method': ['DELETE']})
585 577 m.connect('edit_gist', '/gists/{gist_id}/edit',
586 578 action='edit_form', conditions={'method': ['GET']})
587 579 m.connect('edit_gist', '/gists/{gist_id}/edit',
588 580 action='edit', conditions={'method': ['POST']})
589 581 m.connect(
590 582 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
591 583 action='check_revision', conditions={'method': ['GET']})
592 584
593 585 m.connect('gist', '/gists/{gist_id}',
594 586 action='show', conditions={'method': ['GET']})
595 587 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
596 588 revision='tip',
597 589 action='show', conditions={'method': ['GET']})
598 590 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
599 591 revision='tip',
600 592 action='show', conditions={'method': ['GET']})
601 593 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
602 594 revision='tip',
603 595 action='show', conditions={'method': ['GET']},
604 596 requirements=URL_NAME_REQUIREMENTS)
605 597
606 598 # ADMIN MAIN PAGES
607 599 with rmap.submapper(path_prefix=ADMIN_PREFIX,
608 600 controller='admin/admin') as m:
609 601 m.connect('admin_home', '', action='index')
610 602 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
611 603 action='add_repo')
612 604 m.connect(
613 605 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
614 606 action='pull_requests')
615 607 m.connect(
616 608 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
617 609 action='pull_requests')
618 610
619 611
620 612 # USER JOURNAL
621 613 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
622 614 controller='journal', action='index')
623 615 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
624 616 controller='journal', action='journal_rss')
625 617 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
626 618 controller='journal', action='journal_atom')
627 619
628 620 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
629 621 controller='journal', action='public_journal')
630 622
631 623 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
632 624 controller='journal', action='public_journal_rss')
633 625
634 626 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
635 627 controller='journal', action='public_journal_rss')
636 628
637 629 rmap.connect('public_journal_atom',
638 630 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
639 631 action='public_journal_atom')
640 632
641 633 rmap.connect('public_journal_atom_old',
642 634 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
643 635 action='public_journal_atom')
644 636
645 637 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
646 638 controller='journal', action='toggle_following', jsroute=True,
647 639 conditions={'method': ['POST']})
648 640
649 641 # FULL TEXT SEARCH
650 642 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
651 643 controller='search')
652 644 rmap.connect('search_repo_home', '/{repo_name}/search',
653 645 controller='search',
654 646 action='index',
655 647 conditions={'function': check_repo},
656 648 requirements=URL_NAME_REQUIREMENTS)
657 649
658 650 # FEEDS
659 651 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
660 652 controller='feed', action='rss',
661 653 conditions={'function': check_repo},
662 654 requirements=URL_NAME_REQUIREMENTS)
663 655
664 656 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
665 657 controller='feed', action='atom',
666 658 conditions={'function': check_repo},
667 659 requirements=URL_NAME_REQUIREMENTS)
668 660
669 661 #==========================================================================
670 662 # REPOSITORY ROUTES
671 663 #==========================================================================
672 664
673 665 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
674 666 controller='admin/repos', action='repo_creating',
675 667 requirements=URL_NAME_REQUIREMENTS)
676 668 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
677 669 controller='admin/repos', action='repo_check',
678 670 requirements=URL_NAME_REQUIREMENTS)
679 671
680 672 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
681 673 controller='summary', action='repo_stats',
682 674 conditions={'function': check_repo},
683 675 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
684 676
685 677 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
686 678 controller='summary', action='repo_refs_data', jsroute=True,
687 679 requirements=URL_NAME_REQUIREMENTS)
688 680 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
689 681 controller='summary', action='repo_refs_changelog_data',
690 682 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
691 683
692 684 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
693 685 controller='changeset', revision='tip', jsroute=True,
694 686 conditions={'function': check_repo},
695 687 requirements=URL_NAME_REQUIREMENTS)
696 688 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
697 689 controller='changeset', revision='tip', action='changeset_children',
698 690 conditions={'function': check_repo},
699 691 requirements=URL_NAME_REQUIREMENTS)
700 692 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
701 693 controller='changeset', revision='tip', action='changeset_parents',
702 694 conditions={'function': check_repo},
703 695 requirements=URL_NAME_REQUIREMENTS)
704 696
705 697 # repo edit options
706 698 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
707 699 controller='admin/repos', action='edit',
708 700 conditions={'method': ['GET'], 'function': check_repo},
709 701 requirements=URL_NAME_REQUIREMENTS)
710 702
711 703 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
712 704 jsroute=True,
713 705 controller='admin/repos', action='edit_permissions',
714 706 conditions={'method': ['GET'], 'function': check_repo},
715 707 requirements=URL_NAME_REQUIREMENTS)
716 708 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
717 709 controller='admin/repos', action='edit_permissions_update',
718 710 conditions={'method': ['PUT'], 'function': check_repo},
719 711 requirements=URL_NAME_REQUIREMENTS)
720 712
721 713 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
722 714 controller='admin/repos', action='edit_fields',
723 715 conditions={'method': ['GET'], 'function': check_repo},
724 716 requirements=URL_NAME_REQUIREMENTS)
725 717 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
726 718 controller='admin/repos', action='create_repo_field',
727 719 conditions={'method': ['PUT'], 'function': check_repo},
728 720 requirements=URL_NAME_REQUIREMENTS)
729 721 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
730 722 controller='admin/repos', action='delete_repo_field',
731 723 conditions={'method': ['DELETE'], 'function': check_repo},
732 724 requirements=URL_NAME_REQUIREMENTS)
733 725
734 726 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
735 727 controller='admin/repos', action='edit_advanced',
736 728 conditions={'method': ['GET'], 'function': check_repo},
737 729 requirements=URL_NAME_REQUIREMENTS)
738 730
739 731 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
740 732 controller='admin/repos', action='edit_advanced_locking',
741 733 conditions={'method': ['PUT'], 'function': check_repo},
742 734 requirements=URL_NAME_REQUIREMENTS)
743 735 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
744 736 controller='admin/repos', action='toggle_locking',
745 737 conditions={'method': ['GET'], 'function': check_repo},
746 738 requirements=URL_NAME_REQUIREMENTS)
747 739
748 740 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
749 741 controller='admin/repos', action='edit_advanced_journal',
750 742 conditions={'method': ['PUT'], 'function': check_repo},
751 743 requirements=URL_NAME_REQUIREMENTS)
752 744
753 745 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
754 746 controller='admin/repos', action='edit_advanced_fork',
755 747 conditions={'method': ['PUT'], 'function': check_repo},
756 748 requirements=URL_NAME_REQUIREMENTS)
757 749
758 750 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
759 751 controller='admin/repos', action='edit_caches_form',
760 752 conditions={'method': ['GET'], 'function': check_repo},
761 753 requirements=URL_NAME_REQUIREMENTS)
762 754 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
763 755 controller='admin/repos', action='edit_caches',
764 756 conditions={'method': ['PUT'], 'function': check_repo},
765 757 requirements=URL_NAME_REQUIREMENTS)
766 758
767 759 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
768 760 controller='admin/repos', action='edit_remote_form',
769 761 conditions={'method': ['GET'], 'function': check_repo},
770 762 requirements=URL_NAME_REQUIREMENTS)
771 763 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
772 764 controller='admin/repos', action='edit_remote',
773 765 conditions={'method': ['PUT'], 'function': check_repo},
774 766 requirements=URL_NAME_REQUIREMENTS)
775 767
776 768 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
777 769 controller='admin/repos', action='edit_statistics_form',
778 770 conditions={'method': ['GET'], 'function': check_repo},
779 771 requirements=URL_NAME_REQUIREMENTS)
780 772 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
781 773 controller='admin/repos', action='edit_statistics',
782 774 conditions={'method': ['PUT'], 'function': check_repo},
783 775 requirements=URL_NAME_REQUIREMENTS)
784 776 rmap.connect('repo_settings_issuetracker',
785 777 '/{repo_name}/settings/issue-tracker',
786 778 controller='admin/repos', action='repo_issuetracker',
787 779 conditions={'method': ['GET'], 'function': check_repo},
788 780 requirements=URL_NAME_REQUIREMENTS)
789 781 rmap.connect('repo_issuetracker_test',
790 782 '/{repo_name}/settings/issue-tracker/test',
791 783 controller='admin/repos', action='repo_issuetracker_test',
792 784 conditions={'method': ['POST'], 'function': check_repo},
793 785 requirements=URL_NAME_REQUIREMENTS)
794 786 rmap.connect('repo_issuetracker_delete',
795 787 '/{repo_name}/settings/issue-tracker/delete',
796 788 controller='admin/repos', action='repo_issuetracker_delete',
797 789 conditions={'method': ['DELETE'], 'function': check_repo},
798 790 requirements=URL_NAME_REQUIREMENTS)
799 791 rmap.connect('repo_issuetracker_save',
800 792 '/{repo_name}/settings/issue-tracker/save',
801 793 controller='admin/repos', action='repo_issuetracker_save',
802 794 conditions={'method': ['POST'], 'function': check_repo},
803 795 requirements=URL_NAME_REQUIREMENTS)
804 796 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
805 797 controller='admin/repos', action='repo_settings_vcs_update',
806 798 conditions={'method': ['POST'], 'function': check_repo},
807 799 requirements=URL_NAME_REQUIREMENTS)
808 800 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
809 801 controller='admin/repos', action='repo_settings_vcs',
810 802 conditions={'method': ['GET'], 'function': check_repo},
811 803 requirements=URL_NAME_REQUIREMENTS)
812 804 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
813 805 controller='admin/repos', action='repo_delete_svn_pattern',
814 806 conditions={'method': ['DELETE'], 'function': check_repo},
815 807 requirements=URL_NAME_REQUIREMENTS)
816 808
817 809 # still working url for backward compat.
818 810 rmap.connect('raw_changeset_home_depraced',
819 811 '/{repo_name}/raw-changeset/{revision}',
820 812 controller='changeset', action='changeset_raw',
821 813 revision='tip', conditions={'function': check_repo},
822 814 requirements=URL_NAME_REQUIREMENTS)
823 815
824 816 # new URLs
825 817 rmap.connect('changeset_raw_home',
826 818 '/{repo_name}/changeset-diff/{revision}',
827 819 controller='changeset', action='changeset_raw',
828 820 revision='tip', conditions={'function': check_repo},
829 821 requirements=URL_NAME_REQUIREMENTS)
830 822
831 823 rmap.connect('changeset_patch_home',
832 824 '/{repo_name}/changeset-patch/{revision}',
833 825 controller='changeset', action='changeset_patch',
834 826 revision='tip', conditions={'function': check_repo},
835 827 requirements=URL_NAME_REQUIREMENTS)
836 828
837 829 rmap.connect('changeset_download_home',
838 830 '/{repo_name}/changeset-download/{revision}',
839 831 controller='changeset', action='changeset_download',
840 832 revision='tip', conditions={'function': check_repo},
841 833 requirements=URL_NAME_REQUIREMENTS)
842 834
843 835 rmap.connect('changeset_comment',
844 836 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
845 837 controller='changeset', revision='tip', action='comment',
846 838 conditions={'function': check_repo},
847 839 requirements=URL_NAME_REQUIREMENTS)
848 840
849 841 rmap.connect('changeset_comment_preview',
850 842 '/{repo_name}/changeset/comment/preview', jsroute=True,
851 843 controller='changeset', action='preview_comment',
852 844 conditions={'function': check_repo, 'method': ['POST']},
853 845 requirements=URL_NAME_REQUIREMENTS)
854 846
855 847 rmap.connect('changeset_comment_delete',
856 848 '/{repo_name}/changeset/comment/{comment_id}/delete',
857 849 controller='changeset', action='delete_comment',
858 850 conditions={'function': check_repo, 'method': ['DELETE']},
859 851 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
860 852
861 853 rmap.connect('changeset_info', '/changeset_info/{repo_name}/{revision}',
862 854 controller='changeset', action='changeset_info',
863 855 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
864 856
865 857 rmap.connect('compare_home',
866 858 '/{repo_name}/compare',
867 859 controller='compare', action='index',
868 860 conditions={'function': check_repo},
869 861 requirements=URL_NAME_REQUIREMENTS)
870 862
871 863 rmap.connect('compare_url',
872 864 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
873 865 controller='compare', action='compare',
874 866 conditions={'function': check_repo},
875 867 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
876 868
877 869 rmap.connect('pullrequest_home',
878 870 '/{repo_name}/pull-request/new', controller='pullrequests',
879 871 action='index', conditions={'function': check_repo,
880 872 'method': ['GET']},
881 873 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
882 874
883 875 rmap.connect('pullrequest',
884 876 '/{repo_name}/pull-request/new', controller='pullrequests',
885 877 action='create', conditions={'function': check_repo,
886 878 'method': ['POST']},
887 879 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
888 880
889 881 rmap.connect('pullrequest_repo_refs',
890 882 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
891 883 controller='pullrequests',
892 884 action='get_repo_refs',
893 885 conditions={'function': check_repo, 'method': ['GET']},
894 886 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
895 887
896 888 rmap.connect('pullrequest_repo_destinations',
897 889 '/{repo_name}/pull-request/repo-destinations',
898 890 controller='pullrequests',
899 891 action='get_repo_destinations',
900 892 conditions={'function': check_repo, 'method': ['GET']},
901 893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
902 894
903 895 rmap.connect('pullrequest_show',
904 896 '/{repo_name}/pull-request/{pull_request_id}',
905 897 controller='pullrequests',
906 898 action='show', conditions={'function': check_repo,
907 899 'method': ['GET']},
908 900 requirements=URL_NAME_REQUIREMENTS)
909 901
910 902 rmap.connect('pullrequest_update',
911 903 '/{repo_name}/pull-request/{pull_request_id}',
912 904 controller='pullrequests',
913 905 action='update', conditions={'function': check_repo,
914 906 'method': ['PUT']},
915 907 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
916 908
917 909 rmap.connect('pullrequest_merge',
918 910 '/{repo_name}/pull-request/{pull_request_id}',
919 911 controller='pullrequests',
920 912 action='merge', conditions={'function': check_repo,
921 913 'method': ['POST']},
922 914 requirements=URL_NAME_REQUIREMENTS)
923 915
924 916 rmap.connect('pullrequest_delete',
925 917 '/{repo_name}/pull-request/{pull_request_id}',
926 918 controller='pullrequests',
927 919 action='delete', conditions={'function': check_repo,
928 920 'method': ['DELETE']},
929 921 requirements=URL_NAME_REQUIREMENTS)
930 922
931 923 rmap.connect('pullrequest_show_all',
932 924 '/{repo_name}/pull-request',
933 925 controller='pullrequests',
934 926 action='show_all', conditions={'function': check_repo,
935 927 'method': ['GET']},
936 928 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
937 929
938 930 rmap.connect('pullrequest_comment',
939 931 '/{repo_name}/pull-request-comment/{pull_request_id}',
940 932 controller='pullrequests',
941 933 action='comment', conditions={'function': check_repo,
942 934 'method': ['POST']},
943 935 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
944 936
945 937 rmap.connect('pullrequest_comment_delete',
946 938 '/{repo_name}/pull-request-comment/{comment_id}/delete',
947 939 controller='pullrequests', action='delete_comment',
948 940 conditions={'function': check_repo, 'method': ['DELETE']},
949 941 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
950 942
951 943 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
952 944 controller='summary', conditions={'function': check_repo},
953 945 requirements=URL_NAME_REQUIREMENTS)
954 946
955 947 rmap.connect('branches_home', '/{repo_name}/branches',
956 948 controller='branches', conditions={'function': check_repo},
957 949 requirements=URL_NAME_REQUIREMENTS)
958 950
959 951 rmap.connect('tags_home', '/{repo_name}/tags',
960 952 controller='tags', conditions={'function': check_repo},
961 953 requirements=URL_NAME_REQUIREMENTS)
962 954
963 955 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
964 956 controller='bookmarks', conditions={'function': check_repo},
965 957 requirements=URL_NAME_REQUIREMENTS)
966 958
967 959 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
968 960 controller='changelog', conditions={'function': check_repo},
969 961 requirements=URL_NAME_REQUIREMENTS)
970 962
971 963 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
972 964 controller='changelog', action='changelog_summary',
973 965 conditions={'function': check_repo},
974 966 requirements=URL_NAME_REQUIREMENTS)
975 967
976 968 rmap.connect('changelog_file_home',
977 969 '/{repo_name}/changelog/{revision}/{f_path}',
978 970 controller='changelog', f_path=None,
979 971 conditions={'function': check_repo},
980 972 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
981 973
982 974 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
983 975 controller='changelog', action='changelog_details',
984 976 conditions={'function': check_repo},
985 977 requirements=URL_NAME_REQUIREMENTS)
986 978
987 979 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
988 980 controller='files', revision='tip', f_path='',
989 981 conditions={'function': check_repo},
990 982 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
991 983
992 984 rmap.connect('files_home_simple_catchrev',
993 985 '/{repo_name}/files/{revision}',
994 986 controller='files', revision='tip', f_path='',
995 987 conditions={'function': check_repo},
996 988 requirements=URL_NAME_REQUIREMENTS)
997 989
998 990 rmap.connect('files_home_simple_catchall',
999 991 '/{repo_name}/files',
1000 992 controller='files', revision='tip', f_path='',
1001 993 conditions={'function': check_repo},
1002 994 requirements=URL_NAME_REQUIREMENTS)
1003 995
1004 996 rmap.connect('files_history_home',
1005 997 '/{repo_name}/history/{revision}/{f_path}',
1006 998 controller='files', action='history', revision='tip', f_path='',
1007 999 conditions={'function': check_repo},
1008 1000 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1009 1001
1010 1002 rmap.connect('files_authors_home',
1011 1003 '/{repo_name}/authors/{revision}/{f_path}',
1012 1004 controller='files', action='authors', revision='tip', f_path='',
1013 1005 conditions={'function': check_repo},
1014 1006 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1015 1007
1016 1008 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1017 1009 controller='files', action='diff', f_path='',
1018 1010 conditions={'function': check_repo},
1019 1011 requirements=URL_NAME_REQUIREMENTS)
1020 1012
1021 1013 rmap.connect('files_diff_2way_home',
1022 1014 '/{repo_name}/diff-2way/{f_path}',
1023 1015 controller='files', action='diff_2way', f_path='',
1024 1016 conditions={'function': check_repo},
1025 1017 requirements=URL_NAME_REQUIREMENTS)
1026 1018
1027 1019 rmap.connect('files_rawfile_home',
1028 1020 '/{repo_name}/rawfile/{revision}/{f_path}',
1029 1021 controller='files', action='rawfile', revision='tip',
1030 1022 f_path='', conditions={'function': check_repo},
1031 1023 requirements=URL_NAME_REQUIREMENTS)
1032 1024
1033 1025 rmap.connect('files_raw_home',
1034 1026 '/{repo_name}/raw/{revision}/{f_path}',
1035 1027 controller='files', action='raw', revision='tip', f_path='',
1036 1028 conditions={'function': check_repo},
1037 1029 requirements=URL_NAME_REQUIREMENTS)
1038 1030
1039 1031 rmap.connect('files_render_home',
1040 1032 '/{repo_name}/render/{revision}/{f_path}',
1041 1033 controller='files', action='index', revision='tip', f_path='',
1042 1034 rendered=True, conditions={'function': check_repo},
1043 1035 requirements=URL_NAME_REQUIREMENTS)
1044 1036
1045 1037 rmap.connect('files_annotate_home',
1046 1038 '/{repo_name}/annotate/{revision}/{f_path}',
1047 1039 controller='files', action='index', revision='tip',
1048 1040 f_path='', annotate=True, conditions={'function': check_repo},
1049 1041 requirements=URL_NAME_REQUIREMENTS)
1050 1042
1051 1043 rmap.connect('files_edit',
1052 1044 '/{repo_name}/edit/{revision}/{f_path}',
1053 1045 controller='files', action='edit', revision='tip',
1054 1046 f_path='',
1055 1047 conditions={'function': check_repo, 'method': ['POST']},
1056 1048 requirements=URL_NAME_REQUIREMENTS)
1057 1049
1058 1050 rmap.connect('files_edit_home',
1059 1051 '/{repo_name}/edit/{revision}/{f_path}',
1060 1052 controller='files', action='edit_home', revision='tip',
1061 1053 f_path='', conditions={'function': check_repo},
1062 1054 requirements=URL_NAME_REQUIREMENTS)
1063 1055
1064 1056 rmap.connect('files_add',
1065 1057 '/{repo_name}/add/{revision}/{f_path}',
1066 1058 controller='files', action='add', revision='tip',
1067 1059 f_path='',
1068 1060 conditions={'function': check_repo, 'method': ['POST']},
1069 1061 requirements=URL_NAME_REQUIREMENTS)
1070 1062
1071 1063 rmap.connect('files_add_home',
1072 1064 '/{repo_name}/add/{revision}/{f_path}',
1073 1065 controller='files', action='add_home', revision='tip',
1074 1066 f_path='', conditions={'function': check_repo},
1075 1067 requirements=URL_NAME_REQUIREMENTS)
1076 1068
1077 1069 rmap.connect('files_delete',
1078 1070 '/{repo_name}/delete/{revision}/{f_path}',
1079 1071 controller='files', action='delete', revision='tip',
1080 1072 f_path='',
1081 1073 conditions={'function': check_repo, 'method': ['POST']},
1082 1074 requirements=URL_NAME_REQUIREMENTS)
1083 1075
1084 1076 rmap.connect('files_delete_home',
1085 1077 '/{repo_name}/delete/{revision}/{f_path}',
1086 1078 controller='files', action='delete_home', revision='tip',
1087 1079 f_path='', conditions={'function': check_repo},
1088 1080 requirements=URL_NAME_REQUIREMENTS)
1089 1081
1090 1082 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1091 1083 controller='files', action='archivefile',
1092 1084 conditions={'function': check_repo},
1093 1085 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1094 1086
1095 1087 rmap.connect('files_nodelist_home',
1096 1088 '/{repo_name}/nodelist/{revision}/{f_path}',
1097 1089 controller='files', action='nodelist',
1098 1090 conditions={'function': check_repo},
1099 1091 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1100 1092
1101 1093 rmap.connect('files_metadata_list_home',
1102 1094 '/{repo_name}/metadata_list/{revision}/{f_path}',
1103 1095 controller='files', action='metadata_list',
1104 1096 conditions={'function': check_repo},
1105 1097 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1106 1098
1107 1099 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1108 1100 controller='forks', action='fork_create',
1109 1101 conditions={'function': check_repo, 'method': ['POST']},
1110 1102 requirements=URL_NAME_REQUIREMENTS)
1111 1103
1112 1104 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1113 1105 controller='forks', action='fork',
1114 1106 conditions={'function': check_repo},
1115 1107 requirements=URL_NAME_REQUIREMENTS)
1116 1108
1117 1109 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1118 1110 controller='forks', action='forks',
1119 1111 conditions={'function': check_repo},
1120 1112 requirements=URL_NAME_REQUIREMENTS)
1121 1113
1122 1114 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1123 1115 controller='followers', action='followers',
1124 1116 conditions={'function': check_repo},
1125 1117 requirements=URL_NAME_REQUIREMENTS)
1126 1118
1127 1119 # must be here for proper group/repo catching pattern
1128 1120 _connect_with_slash(
1129 1121 rmap, 'repo_group_home', '/{group_name}',
1130 1122 controller='home', action='index_repo_group',
1131 1123 conditions={'function': check_group},
1132 1124 requirements=URL_NAME_REQUIREMENTS)
1133 1125
1134 1126 # catch all, at the end
1135 1127 _connect_with_slash(
1136 1128 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1137 1129 controller='summary', action='index',
1138 1130 conditions={'function': check_repo},
1139 1131 requirements=URL_NAME_REQUIREMENTS)
1140 1132
1141 1133 return rmap
1142 1134
1143 1135
1144 1136 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1145 1137 """
1146 1138 Connect a route with an optional trailing slash in `path`.
1147 1139 """
1148 1140 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1149 1141 mapper.connect(name, path, *args, **kwargs)
@@ -1,99 +1,103 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import shlex
23 23 import Pyro4
24 24 import platform
25 25
26 26 from rhodecode.model import init_model
27 27
28 28
29 29 def configure_pyro4(config):
30 30 """
31 31 Configure Pyro4 based on `config`.
32 32
33 33 This will mainly set the different configuration parameters of the Pyro4
34 34 library based on the settings in our INI files. The Pyro4 documentation
35 35 lists more details about the specific settings and their meaning.
36 36 """
37 37 Pyro4.config.COMMTIMEOUT = float(config['vcs.connection_timeout'])
38 38 Pyro4.config.SERIALIZER = 'pickle'
39 39 Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
40 40
41 41 # Note: We need server configuration in the WSGI processes
42 42 # because we provide a callback server in certain vcs operations.
43 43 Pyro4.config.SERVERTYPE = "multiplex"
44 44 Pyro4.config.POLLTIMEOUT = 0.01
45 45
46 46
47 47 def configure_vcs(config):
48 48 """
49 49 Patch VCS config with some RhodeCode specific stuff
50 50 """
51 51 from rhodecode.lib.vcs import conf
52 52 from rhodecode.lib.utils2 import aslist
53 53 conf.settings.BACKENDS = {
54 54 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
55 55 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
56 56 'svn': 'rhodecode.lib.vcs.backends.svn.SubversionRepository',
57 57 }
58 58
59 59 conf.settings.HG_USE_REBASE_FOR_MERGING = config.get(
60 60 'rhodecode_hg_use_rebase_for_merging', False)
61 61 conf.settings.GIT_REV_FILTER = shlex.split(
62 62 config.get('git_rev_filter', '--all').strip())
63 63 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
64 64 'UTF-8'), sep=',')
65 65 conf.settings.ALIASES[:] = config.get('vcs.backends')
66 66 conf.settings.SVN_COMPATIBLE_VERSION = config.get(
67 67 'vcs.svn.compatible_version')
68 68
69 69
70 70 def initialize_database(config):
71 from rhodecode.lib.utils2 import engine_from_config
71 from rhodecode.lib.utils2 import engine_from_config, get_encryption_key
72 72 engine = engine_from_config(config, 'sqlalchemy.db1.')
73 init_model(engine, encryption_key=config['beaker.session.secret'])
73 init_model(engine, encryption_key=get_encryption_key(config))
74 74
75 75
76 76 def initialize_test_environment(settings, test_env=None):
77 77 if test_env is None:
78 78 test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0))
79 79
80 from rhodecode.lib.utils import create_test_env, create_test_index
80 from rhodecode.lib.utils import (
81 create_test_directory, create_test_database, create_test_repositories,
82 create_test_index)
81 83 from rhodecode.tests import TESTS_TMP_PATH
82 84 # test repos
83 85 if test_env:
84 create_test_env(TESTS_TMP_PATH, settings)
85 create_test_index(TESTS_TMP_PATH, settings, True)
86 create_test_directory(TESTS_TMP_PATH)
87 create_test_database(TESTS_TMP_PATH, settings)
88 create_test_repositories(TESTS_TMP_PATH, settings)
89 create_test_index(TESTS_TMP_PATH, settings)
86 90
87 91
88 92 def get_vcs_server_protocol(config):
89 93 protocol = config.get('vcs.server.protocol', 'pyro4')
90 94 return protocol
91 95
92 96
93 97 def set_instance_id(config):
94 98 """ Sets a dynamic generated config['instance_id'] if missing or '*' """
95 99
96 100 config['instance_id'] = config.get('instance_id') or ''
97 101 if config['instance_id'] == '*' or not config['instance_id']:
98 102 _platform_id = platform.uname()[1] or 'instance'
99 103 config['instance_id'] = '%s-%s' % (_platform_id, os.getpid())
@@ -1,407 +1,406 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repository groups controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import formencode
28 28
29 29 from formencode import htmlfill
30 30
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _, ungettext
34 34
35 35 from rhodecode.lib import auth
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, NotAnonymous, HasPermissionAll,
40 40 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.model.db import RepoGroup, User
43 43 from rhodecode.model.scm import RepoGroupList
44 44 from rhodecode.model.repo_group import RepoGroupModel
45 45 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.lib.utils2 import safe_int
48 48
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class RepoGroupsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55
56 56 @LoginRequired()
57 57 def __before__(self):
58 58 super(RepoGroupsController, self).__before__()
59 59
60 60 def __load_defaults(self, allow_empty_group=False, repo_group=None):
61 61 if self._can_create_repo_group():
62 62 # we're global admin, we're ok and we can create TOP level groups
63 63 allow_empty_group = True
64 64
65 65 # override the choices for this form, we need to filter choices
66 66 # and display only those we have ADMIN right
67 67 groups_with_admin_rights = RepoGroupList(
68 68 RepoGroup.query().all(),
69 69 perm_set=['group.admin'])
70 70 c.repo_groups = RepoGroup.groups_choices(
71 71 groups=groups_with_admin_rights,
72 72 show_empty_group=allow_empty_group)
73 73
74 74 if repo_group:
75 75 # exclude filtered ids
76 76 exclude_group_ids = [repo_group.group_id]
77 77 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
78 78 c.repo_groups)
79 79 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
80 80 parent_group = repo_group.parent_group
81 81
82 82 add_parent_group = (parent_group and (
83 83 unicode(parent_group.group_id) not in c.repo_groups_choices))
84 84 if add_parent_group:
85 85 c.repo_groups_choices.append(unicode(parent_group.group_id))
86 86 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
87 87
88 88 def __load_data(self, group_id):
89 89 """
90 90 Load defaults settings for edit, and update
91 91
92 92 :param group_id:
93 93 """
94 94 repo_group = RepoGroup.get_or_404(group_id)
95 95 data = repo_group.get_dict()
96 96 data['group_name'] = repo_group.name
97 97
98 98 # fill owner
99 99 if repo_group.user:
100 100 data.update({'user': repo_group.user.username})
101 101 else:
102 replacement_user = User.get_first_admin().username
102 replacement_user = User.get_first_super_admin().username
103 103 data.update({'user': replacement_user})
104 104
105 105 # fill repository group users
106 106 for p in repo_group.repo_group_to_perm:
107 107 data.update({
108 108 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
109 109
110 110 # fill repository group user groups
111 111 for p in repo_group.users_group_to_perm:
112 112 data.update({
113 113 'g_perm_%s' % p.users_group.users_group_id:
114 114 p.permission.permission_name})
115 115 # html and form expects -1 as empty parent group
116 116 data['group_parent_id'] = data['group_parent_id'] or -1
117 117 return data
118 118
119 119 def _revoke_perms_on_yourself(self, form_result):
120 120 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
121 121 form_result['perm_updates'])
122 122 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
123 123 form_result['perm_additions'])
124 124 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
125 125 form_result['perm_deletions'])
126 126 admin_perm = 'group.admin'
127 127 if _updates and _updates[0][1] != admin_perm or \
128 128 _additions and _additions[0][1] != admin_perm or \
129 129 _deletions and _deletions[0][1] != admin_perm:
130 130 return True
131 131 return False
132 132
133 133 def _can_create_repo_group(self, parent_group_id=None):
134 134 is_admin = HasPermissionAll('hg.admin')('group create controller')
135 135 create_repo_group = HasPermissionAll(
136 136 'hg.repogroup.create.true')('group create controller')
137 137 if is_admin or (create_repo_group and not parent_group_id):
138 138 # we're global admin, or we have global repo group create
139 139 # permission
140 140 # we're ok and we can create TOP level groups
141 141 return True
142 142 elif parent_group_id:
143 143 # we check the permission if we can write to parent group
144 144 group = RepoGroup.get(parent_group_id)
145 145 group_name = group.group_name if group else None
146 146 if HasRepoGroupPermissionAll('group.admin')(
147 147 group_name, 'check if user is an admin of group'):
148 148 # we're an admin of passed in group, we're ok.
149 149 return True
150 150 else:
151 151 return False
152 152 return False
153 153
154 154 @NotAnonymous()
155 155 def index(self):
156 156 """GET /repo_groups: All items in the collection"""
157 157 # url('repo_groups')
158 158
159 159 repo_group_list = RepoGroup.get_all_repo_groups()
160 160 _perms = ['group.admin']
161 161 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
162 162 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
163 163 repo_group_list=repo_group_list_acl, admin=True)
164 164 c.data = json.dumps(repo_group_data)
165 165 return render('admin/repo_groups/repo_groups.html')
166 166
167 167 # perm checks inside
168 168 @NotAnonymous()
169 169 @auth.CSRFRequired()
170 170 def create(self):
171 171 """POST /repo_groups: Create a new item"""
172 172 # url('repo_groups')
173 173
174 174 parent_group_id = safe_int(request.POST.get('group_parent_id'))
175 175 can_create = self._can_create_repo_group(parent_group_id)
176 176
177 177 self.__load_defaults()
178 178 # permissions for can create group based on parent_id are checked
179 179 # here in the Form
180 180 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
181 181 repo_group_form = RepoGroupForm(available_groups=available_groups,
182 182 can_create_in_root=can_create)()
183 183 try:
184 184 owner = c.rhodecode_user
185 185 form_result = repo_group_form.to_python(dict(request.POST))
186 186 RepoGroupModel().create(
187 187 group_name=form_result['group_name_full'],
188 188 group_description=form_result['group_description'],
189 189 owner=owner.user_id,
190 190 copy_permissions=form_result['group_copy_permissions']
191 191 )
192 192 Session().commit()
193 193 _new_group_name = form_result['group_name_full']
194 194 repo_group_url = h.link_to(
195 195 _new_group_name,
196 196 h.url('repo_group_home', group_name=_new_group_name))
197 197 h.flash(h.literal(_('Created repository group %s')
198 198 % repo_group_url), category='success')
199 199 # TODO: in futureaction_logger(, '', '', '', self.sa)
200 200 except formencode.Invalid as errors:
201 201 return htmlfill.render(
202 202 render('admin/repo_groups/repo_group_add.html'),
203 203 defaults=errors.value,
204 204 errors=errors.error_dict or {},
205 205 prefix_error=False,
206 206 encoding="UTF-8",
207 207 force_defaults=False)
208 208 except Exception:
209 209 log.exception("Exception during creation of repository group")
210 210 h.flash(_('Error occurred during creation of repository group %s')
211 211 % request.POST.get('group_name'), category='error')
212 212
213 213 # TODO: maybe we should get back to the main view, not the admin one
214 214 return redirect(url('repo_groups', parent_group=parent_group_id))
215 215
216 216 # perm checks inside
217 217 @NotAnonymous()
218 218 def new(self):
219 219 """GET /repo_groups/new: Form to create a new item"""
220 220 # url('new_repo_group')
221 221 # perm check for admin, create_group perm or admin of parent_group
222 222 parent_group_id = safe_int(request.GET.get('parent_group'))
223 223 if not self._can_create_repo_group(parent_group_id):
224 224 return abort(403)
225 225
226 226 self.__load_defaults()
227 227 return render('admin/repo_groups/repo_group_add.html')
228 228
229 229 @HasRepoGroupPermissionAnyDecorator('group.admin')
230 230 @auth.CSRFRequired()
231 231 def update(self, group_name):
232 232 """PUT /repo_groups/group_name: Update an existing item"""
233 233 # Forms posted to this method should contain a hidden field:
234 234 # <input type="hidden" name="_method" value="PUT" />
235 235 # Or using helpers:
236 236 # h.form(url('repos_group', group_name=GROUP_NAME), method='put')
237 237 # url('repo_group_home', group_name=GROUP_NAME)
238 238
239 239 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
240 240 can_create_in_root = self._can_create_repo_group()
241 241 show_root_location = can_create_in_root
242 242 if not c.repo_group.parent_group:
243 243 # this group don't have a parrent so we should show empty value
244 244 show_root_location = True
245 245 self.__load_defaults(allow_empty_group=show_root_location,
246 246 repo_group=c.repo_group)
247 247
248 248 repo_group_form = RepoGroupForm(
249 edit=True,
250 old_data=c.repo_group.get_dict(),
249 edit=True, old_data=c.repo_group.get_dict(),
251 250 available_groups=c.repo_groups_choices,
252 can_create_in_root=can_create_in_root,
253 )()
251 can_create_in_root=can_create_in_root, allow_disabled=True)()
252
254 253 try:
255 254 form_result = repo_group_form.to_python(dict(request.POST))
256 255 gr_name = form_result['group_name']
257 256 new_gr = RepoGroupModel().update(group_name, form_result)
258 257 Session().commit()
259 258 h.flash(_('Updated repository group %s') % (gr_name,),
260 259 category='success')
261 260 # we now have new name !
262 261 group_name = new_gr.group_name
263 262 # TODO: in future action_logger(, '', '', '', self.sa)
264 263 except formencode.Invalid as errors:
265 264 c.active = 'settings'
266 265 return htmlfill.render(
267 266 render('admin/repo_groups/repo_group_edit.html'),
268 267 defaults=errors.value,
269 268 errors=errors.error_dict or {},
270 269 prefix_error=False,
271 270 encoding="UTF-8",
272 271 force_defaults=False)
273 272 except Exception:
274 273 log.exception("Exception during update or repository group")
275 274 h.flash(_('Error occurred during update of repository group %s')
276 275 % request.POST.get('group_name'), category='error')
277 276
278 277 return redirect(url('edit_repo_group', group_name=group_name))
279 278
280 279 @HasRepoGroupPermissionAnyDecorator('group.admin')
281 280 @auth.CSRFRequired()
282 281 def delete(self, group_name):
283 282 """DELETE /repo_groups/group_name: Delete an existing item"""
284 283 # Forms posted to this method should contain a hidden field:
285 284 # <input type="hidden" name="_method" value="DELETE" />
286 285 # Or using helpers:
287 286 # h.form(url('repos_group', group_name=GROUP_NAME), method='delete')
288 287 # url('repo_group_home', group_name=GROUP_NAME)
289 288
290 289 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
291 290 repos = gr.repositories.all()
292 291 if repos:
293 292 msg = ungettext(
294 293 'This group contains %(num)d repository and cannot be deleted',
295 294 'This group contains %(num)d repositories and cannot be'
296 295 ' deleted',
297 296 len(repos)) % {'num': len(repos)}
298 297 h.flash(msg, category='warning')
299 298 return redirect(url('repo_groups'))
300 299
301 300 children = gr.children.all()
302 301 if children:
303 302 msg = ungettext(
304 303 'This group contains %(num)d subgroup and cannot be deleted',
305 304 'This group contains %(num)d subgroups and cannot be deleted',
306 305 len(children)) % {'num': len(children)}
307 306 h.flash(msg, category='warning')
308 307 return redirect(url('repo_groups'))
309 308
310 309 try:
311 310 RepoGroupModel().delete(group_name)
312 311 Session().commit()
313 312 h.flash(_('Removed repository group %s') % group_name,
314 313 category='success')
315 314 # TODO: in future action_logger(, '', '', '', self.sa)
316 315 except Exception:
317 316 log.exception("Exception during deletion of repository group")
318 317 h.flash(_('Error occurred during deletion of repository group %s')
319 318 % group_name, category='error')
320 319
321 320 return redirect(url('repo_groups'))
322 321
323 322 @HasRepoGroupPermissionAnyDecorator('group.admin')
324 323 def edit(self, group_name):
325 324 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
326 325 # url('edit_repo_group', group_name=GROUP_NAME)
327 326 c.active = 'settings'
328 327
329 328 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
330 329 # we can only allow moving empty group if it's already a top-level
331 330 # group, ie has no parents, or we're admin
332 331 can_create_in_root = self._can_create_repo_group()
333 332 show_root_location = can_create_in_root
334 333 if not c.repo_group.parent_group:
335 334 # this group don't have a parrent so we should show empty value
336 335 show_root_location = True
337 336 self.__load_defaults(allow_empty_group=show_root_location,
338 337 repo_group=c.repo_group)
339 338 defaults = self.__load_data(c.repo_group.group_id)
340 339
341 340 return htmlfill.render(
342 341 render('admin/repo_groups/repo_group_edit.html'),
343 342 defaults=defaults,
344 343 encoding="UTF-8",
345 344 force_defaults=False
346 345 )
347 346
348 347 @HasRepoGroupPermissionAnyDecorator('group.admin')
349 348 def edit_repo_group_advanced(self, group_name):
350 349 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
351 350 # url('edit_repo_group', group_name=GROUP_NAME)
352 351 c.active = 'advanced'
353 352 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
354 353
355 354 return render('admin/repo_groups/repo_group_edit.html')
356 355
357 356 @HasRepoGroupPermissionAnyDecorator('group.admin')
358 357 def edit_repo_group_perms(self, group_name):
359 358 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
360 359 # url('edit_repo_group', group_name=GROUP_NAME)
361 360 c.active = 'perms'
362 361 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
363 362 self.__load_defaults()
364 363 defaults = self.__load_data(c.repo_group.group_id)
365 364
366 365 return htmlfill.render(
367 366 render('admin/repo_groups/repo_group_edit.html'),
368 367 defaults=defaults,
369 368 encoding="UTF-8",
370 369 force_defaults=False
371 370 )
372 371
373 372 @HasRepoGroupPermissionAnyDecorator('group.admin')
374 373 @auth.CSRFRequired()
375 374 def update_perms(self, group_name):
376 375 """
377 376 Update permissions for given repository group
378 377
379 378 :param group_name:
380 379 """
381 380
382 381 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
383 382 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
384 383 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
385 384 request.POST)
386 385
387 386 if not c.rhodecode_user.is_admin:
388 387 if self._revoke_perms_on_yourself(form):
389 388 msg = _('Cannot change permission for yourself as admin')
390 389 h.flash(msg, category='warning')
391 390 return redirect(
392 391 url('edit_repo_group_perms', group_name=group_name))
393 392
394 393 # iterate over all members(if in recursive mode) of this groups and
395 394 # set the permissions !
396 395 # this can be potentially heavy operation
397 396 RepoGroupModel().update_permissions(
398 397 c.repo_group,
399 398 form['perm_additions'], form['perm_updates'],
400 399 form['perm_deletions'], form['recursive'])
401 400
402 401 # TODO: implement this
403 402 # action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
404 403 # repo_name, self.ip_addr, self.sa)
405 404 Session().commit()
406 405 h.flash(_('Repository Group permissions updated'), category='success')
407 406 return redirect(url('edit_repo_group_perms', group_name=group_name))
@@ -1,878 +1,878 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repositories controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
35 35
36 36 from rhodecode.lib import auth, helpers as h
37 37 from rhodecode.lib.auth import (
38 38 LoginRequired, HasPermissionAllDecorator,
39 39 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
40 40 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
41 41 from rhodecode.lib.base import BaseRepoController, render
42 42 from rhodecode.lib.ext_json import json
43 43 from rhodecode.lib.exceptions import AttachedForksError
44 44 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
45 45 from rhodecode.lib.utils2 import safe_int
46 46 from rhodecode.lib.vcs import RepositoryError
47 47 from rhodecode.model.db import (
48 48 User, Repository, UserFollowing, RepoGroup, RepositoryField)
49 49 from rhodecode.model.forms import (
50 50 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
51 51 IssueTrackerPatternsForm)
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.repo import RepoModel
54 54 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
55 55 from rhodecode.model.settings import (
56 56 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
57 57 SettingNotFound)
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class ReposController(BaseRepoController):
63 63 """
64 64 REST Controller styled on the Atom Publishing Protocol"""
65 65 # To properly map this controller, ensure your config/routing.py
66 66 # file has a resource setup:
67 67 # map.resource('repo', 'repos')
68 68
69 69 @LoginRequired()
70 70 def __before__(self):
71 71 super(ReposController, self).__before__()
72 72
73 73 def _load_repo(self, repo_name):
74 74 repo_obj = Repository.get_by_repo_name(repo_name)
75 75
76 76 if repo_obj is None:
77 77 h.not_mapped_error(repo_name)
78 78 return redirect(url('repos'))
79 79
80 80 return repo_obj
81 81
82 82 def __load_defaults(self, repo=None):
83 83 acl_groups = RepoGroupList(RepoGroup.query().all(),
84 84 perm_set=['group.write', 'group.admin'])
85 85 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
86 86 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
87 87
88 88 # in case someone no longer have a group.write access to a repository
89 89 # pre fill the list with this entry, we don't care if this is the same
90 90 # but it will allow saving repo data properly.
91 91
92 92 repo_group = None
93 93 if repo:
94 94 repo_group = repo.group
95 95 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
96 96 c.repo_groups_choices.append(unicode(repo_group.group_id))
97 97 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
98 98
99 99 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
100 100 c.landing_revs_choices = choices
101 101
102 102 def __load_data(self, repo_name=None):
103 103 """
104 104 Load defaults settings for edit, and update
105 105
106 106 :param repo_name:
107 107 """
108 108 c.repo_info = self._load_repo(repo_name)
109 109 self.__load_defaults(c.repo_info)
110 110
111 111 # override defaults for exact repo info here git/hg etc
112 112 if not c.repository_requirements_missing:
113 113 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
114 114 c.repo_info)
115 115 c.landing_revs_choices = choices
116 116 defaults = RepoModel()._get_defaults(repo_name)
117 117
118 118 return defaults
119 119
120 120 def _log_creation_exception(self, e, repo_name):
121 121 reason = None
122 122 if len(e.args) == 2:
123 123 reason = e.args[1]
124 124
125 125 if reason == 'INVALID_CERTIFICATE':
126 126 log.exception(
127 127 'Exception creating a repository: invalid certificate')
128 128 msg = (_('Error creating repository %s: invalid certificate')
129 129 % repo_name)
130 130 else:
131 131 log.exception("Exception creating a repository")
132 132 msg = (_('Error creating repository %s')
133 133 % repo_name)
134 134
135 135 return msg
136 136
137 137 @NotAnonymous()
138 138 def index(self, format='html'):
139 139 """GET /repos: All items in the collection"""
140 140 # url('repos')
141 141
142 142 repo_list = Repository.get_all_repos()
143 143 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
144 144 repos_data = RepoModel().get_repos_as_dict(
145 145 repo_list=c.repo_list, admin=True, super_user_actions=True)
146 146 # json used to render the grid
147 147 c.data = json.dumps(repos_data)
148 148
149 149 return render('admin/repos/repos.html')
150 150
151 151 # perms check inside
152 152 @NotAnonymous()
153 153 @auth.CSRFRequired()
154 154 def create(self):
155 155 """
156 156 POST /repos: Create a new item"""
157 157 # url('repos')
158 158
159 159 self.__load_defaults()
160 160 form_result = {}
161 161 task_id = None
162 162 try:
163 163 # CanWriteToGroup validators checks permissions of this POST
164 164 form_result = RepoForm(repo_groups=c.repo_groups_choices,
165 165 landing_revs=c.landing_revs_choices)()\
166 166 .to_python(dict(request.POST))
167 167
168 168 # create is done sometimes async on celery, db transaction
169 169 # management is handled there.
170 170 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
171 171 from celery.result import BaseAsyncResult
172 172 if isinstance(task, BaseAsyncResult):
173 173 task_id = task.task_id
174 174 except formencode.Invalid as errors:
175 175 c.personal_repo_group = RepoGroup.get_by_group_name(
176 176 c.rhodecode_user.username)
177 177 return htmlfill.render(
178 178 render('admin/repos/repo_add.html'),
179 179 defaults=errors.value,
180 180 errors=errors.error_dict or {},
181 181 prefix_error=False,
182 182 encoding="UTF-8",
183 183 force_defaults=False)
184 184
185 185 except Exception as e:
186 186 msg = self._log_creation_exception(e, form_result.get('repo_name'))
187 187 h.flash(msg, category='error')
188 188 return redirect(url('home'))
189 189
190 190 return redirect(h.url('repo_creating_home',
191 191 repo_name=form_result['repo_name_full'],
192 192 task_id=task_id))
193 193
194 194 # perms check inside
195 195 @NotAnonymous()
196 196 def create_repository(self):
197 197 """GET /_admin/create_repository: Form to create a new item"""
198 198 new_repo = request.GET.get('repo', '')
199 199 parent_group = request.GET.get('parent_group')
200 200 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
201 201 # you're not super admin nor have global create permissions,
202 202 # but maybe you have at least write permission to a parent group ?
203 203 _gr = RepoGroup.get(parent_group)
204 204 gr_name = _gr.group_name if _gr else None
205 205 # create repositories with write permission on group is set to true
206 206 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
207 207 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
208 208 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
209 209 if not (group_admin or (group_write and create_on_write)):
210 210 raise HTTPForbidden
211 211
212 212 acl_groups = RepoGroupList(RepoGroup.query().all(),
213 213 perm_set=['group.write', 'group.admin'])
214 214 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
215 215 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
216 216 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
217 217 c.personal_repo_group = RepoGroup.get_by_group_name(c.rhodecode_user.username)
218 218 c.new_repo = repo_name_slug(new_repo)
219 219
220 220 ## apply the defaults from defaults page
221 221 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
222 222 # set checkbox to autochecked
223 223 defaults['repo_copy_permissions'] = True
224 224 if parent_group:
225 225 defaults.update({'repo_group': parent_group})
226 226
227 227 return htmlfill.render(
228 228 render('admin/repos/repo_add.html'),
229 229 defaults=defaults,
230 230 errors={},
231 231 prefix_error=False,
232 232 encoding="UTF-8",
233 233 force_defaults=False
234 234 )
235 235
236 236 @NotAnonymous()
237 237 def repo_creating(self, repo_name):
238 238 c.repo = repo_name
239 239 c.task_id = request.GET.get('task_id')
240 240 if not c.repo:
241 241 raise HTTPNotFound()
242 242 return render('admin/repos/repo_creating.html')
243 243
244 244 @NotAnonymous()
245 245 @jsonify
246 246 def repo_check(self, repo_name):
247 247 c.repo = repo_name
248 248 task_id = request.GET.get('task_id')
249 249
250 250 if task_id and task_id not in ['None']:
251 from rhodecode import CELERY_ENABLED
251 import rhodecode
252 252 from celery.result import AsyncResult
253 if CELERY_ENABLED:
253 if rhodecode.CELERY_ENABLED:
254 254 task = AsyncResult(task_id)
255 255 if task.failed():
256 256 msg = self._log_creation_exception(task.result, c.repo)
257 257 h.flash(msg, category='error')
258 258 return redirect(url('home'), code=501)
259 259
260 260 repo = Repository.get_by_repo_name(repo_name)
261 261 if repo and repo.repo_state == Repository.STATE_CREATED:
262 262 if repo.clone_uri:
263 263 clone_uri = repo.clone_uri_hidden
264 264 h.flash(_('Created repository %s from %s')
265 265 % (repo.repo_name, clone_uri), category='success')
266 266 else:
267 267 repo_url = h.link_to(repo.repo_name,
268 268 h.url('summary_home',
269 269 repo_name=repo.repo_name))
270 270 fork = repo.fork
271 271 if fork:
272 272 fork_name = fork.repo_name
273 273 h.flash(h.literal(_('Forked repository %s as %s')
274 274 % (fork_name, repo_url)), category='success')
275 275 else:
276 276 h.flash(h.literal(_('Created repository %s') % repo_url),
277 277 category='success')
278 278 return {'result': True}
279 279 return {'result': False}
280 280
281 281 @HasRepoPermissionAllDecorator('repository.admin')
282 282 @auth.CSRFRequired()
283 283 def update(self, repo_name):
284 284 """
285 285 PUT /repos/repo_name: Update an existing item"""
286 286 # Forms posted to this method should contain a hidden field:
287 287 # <input type="hidden" name="_method" value="PUT" />
288 288 # Or using helpers:
289 289 # h.form(url('repo', repo_name=ID),
290 290 # method='put')
291 291 # url('repo', repo_name=ID)
292 292
293 293 self.__load_data(repo_name)
294 294 c.active = 'settings'
295 295 c.repo_fields = RepositoryField.query()\
296 296 .filter(RepositoryField.repository == c.repo_info).all()
297 297
298 298 repo_model = RepoModel()
299 299 changed_name = repo_name
300 300
301 301 # override the choices with extracted revisions !
302 302 c.personal_repo_group = RepoGroup.get_by_group_name(
303 303 c.rhodecode_user.username)
304 304 repo = Repository.get_by_repo_name(repo_name)
305 305 old_data = {
306 306 'repo_name': repo_name,
307 307 'repo_group': repo.group.get_dict() if repo.group else {},
308 308 'repo_type': repo.repo_type,
309 309 }
310 _form = RepoForm(edit=True, old_data=old_data,
311 repo_groups=c.repo_groups_choices,
312 landing_revs=c.landing_revs_choices)()
310 _form = RepoForm(
311 edit=True, old_data=old_data, repo_groups=c.repo_groups_choices,
312 landing_revs=c.landing_revs_choices, allow_disabled=True)()
313 313
314 314 try:
315 315 form_result = _form.to_python(dict(request.POST))
316 316 repo = repo_model.update(repo_name, **form_result)
317 317 ScmModel().mark_for_invalidation(repo_name)
318 318 h.flash(_('Repository %s updated successfully') % repo_name,
319 319 category='success')
320 320 changed_name = repo.repo_name
321 321 action_logger(c.rhodecode_user, 'admin_updated_repo',
322 322 changed_name, self.ip_addr, self.sa)
323 323 Session().commit()
324 324 except formencode.Invalid as errors:
325 325 defaults = self.__load_data(repo_name)
326 326 defaults.update(errors.value)
327 327 return htmlfill.render(
328 328 render('admin/repos/repo_edit.html'),
329 329 defaults=defaults,
330 330 errors=errors.error_dict or {},
331 331 prefix_error=False,
332 332 encoding="UTF-8",
333 333 force_defaults=False)
334 334
335 335 except Exception:
336 336 log.exception("Exception during update of repository")
337 337 h.flash(_('Error occurred during update of repository %s') \
338 338 % repo_name, category='error')
339 339 return redirect(url('edit_repo', repo_name=changed_name))
340 340
341 341 @HasRepoPermissionAllDecorator('repository.admin')
342 342 @auth.CSRFRequired()
343 343 def delete(self, repo_name):
344 344 """
345 345 DELETE /repos/repo_name: Delete an existing item"""
346 346 # Forms posted to this method should contain a hidden field:
347 347 # <input type="hidden" name="_method" value="DELETE" />
348 348 # Or using helpers:
349 349 # h.form(url('repo', repo_name=ID),
350 350 # method='delete')
351 351 # url('repo', repo_name=ID)
352 352
353 353 repo_model = RepoModel()
354 354 repo = repo_model.get_by_repo_name(repo_name)
355 355 if not repo:
356 356 h.not_mapped_error(repo_name)
357 357 return redirect(url('repos'))
358 358 try:
359 359 _forks = repo.forks.count()
360 360 handle_forks = None
361 361 if _forks and request.POST.get('forks'):
362 362 do = request.POST['forks']
363 363 if do == 'detach_forks':
364 364 handle_forks = 'detach'
365 365 h.flash(_('Detached %s forks') % _forks, category='success')
366 366 elif do == 'delete_forks':
367 367 handle_forks = 'delete'
368 368 h.flash(_('Deleted %s forks') % _forks, category='success')
369 369 repo_model.delete(repo, forks=handle_forks)
370 370 action_logger(c.rhodecode_user, 'admin_deleted_repo',
371 371 repo_name, self.ip_addr, self.sa)
372 372 ScmModel().mark_for_invalidation(repo_name)
373 373 h.flash(_('Deleted repository %s') % repo_name, category='success')
374 374 Session().commit()
375 375 except AttachedForksError:
376 376 h.flash(_('Cannot delete %s it still contains attached forks')
377 377 % repo_name, category='warning')
378 378
379 379 except Exception:
380 380 log.exception("Exception during deletion of repository")
381 381 h.flash(_('An error occurred during deletion of %s') % repo_name,
382 382 category='error')
383 383
384 384 return redirect(url('repos'))
385 385
386 386 @HasPermissionAllDecorator('hg.admin')
387 387 def show(self, repo_name, format='html'):
388 388 """GET /repos/repo_name: Show a specific item"""
389 389 # url('repo', repo_name=ID)
390 390
391 391 @HasRepoPermissionAllDecorator('repository.admin')
392 392 def edit(self, repo_name):
393 393 """GET /repo_name/settings: Form to edit an existing item"""
394 394 # url('edit_repo', repo_name=ID)
395 395 defaults = self.__load_data(repo_name)
396 396 if 'clone_uri' in defaults:
397 397 del defaults['clone_uri']
398 398
399 399 c.repo_fields = RepositoryField.query()\
400 400 .filter(RepositoryField.repository == c.repo_info).all()
401 401 c.personal_repo_group = RepoGroup.get_by_group_name(
402 402 c.rhodecode_user.username)
403 403 c.active = 'settings'
404 404 return htmlfill.render(
405 405 render('admin/repos/repo_edit.html'),
406 406 defaults=defaults,
407 407 encoding="UTF-8",
408 408 force_defaults=False)
409 409
410 410 @HasRepoPermissionAllDecorator('repository.admin')
411 411 def edit_permissions(self, repo_name):
412 412 """GET /repo_name/settings: Form to edit an existing item"""
413 413 # url('edit_repo', repo_name=ID)
414 414 c.repo_info = self._load_repo(repo_name)
415 415 c.active = 'permissions'
416 416 defaults = RepoModel()._get_defaults(repo_name)
417 417
418 418 return htmlfill.render(
419 419 render('admin/repos/repo_edit.html'),
420 420 defaults=defaults,
421 421 encoding="UTF-8",
422 422 force_defaults=False)
423 423
424 424 @HasRepoPermissionAllDecorator('repository.admin')
425 425 @auth.CSRFRequired()
426 426 def edit_permissions_update(self, repo_name):
427 427 form = RepoPermsForm()().to_python(request.POST)
428 428 RepoModel().update_permissions(repo_name,
429 429 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
430 430
431 431 #TODO: implement this
432 432 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
433 433 # repo_name, self.ip_addr, self.sa)
434 434 Session().commit()
435 435 h.flash(_('Repository permissions updated'), category='success')
436 436 return redirect(url('edit_repo_perms', repo_name=repo_name))
437 437
438 438 @HasRepoPermissionAllDecorator('repository.admin')
439 439 def edit_fields(self, repo_name):
440 440 """GET /repo_name/settings: Form to edit an existing item"""
441 441 # url('edit_repo', repo_name=ID)
442 442 c.repo_info = self._load_repo(repo_name)
443 443 c.repo_fields = RepositoryField.query()\
444 444 .filter(RepositoryField.repository == c.repo_info).all()
445 445 c.active = 'fields'
446 446 if request.POST:
447 447
448 448 return redirect(url('repo_edit_fields'))
449 449 return render('admin/repos/repo_edit.html')
450 450
451 451 @HasRepoPermissionAllDecorator('repository.admin')
452 452 @auth.CSRFRequired()
453 453 def create_repo_field(self, repo_name):
454 454 try:
455 455 form_result = RepoFieldForm()().to_python(dict(request.POST))
456 456 RepoModel().add_repo_field(
457 457 repo_name, form_result['new_field_key'],
458 458 field_type=form_result['new_field_type'],
459 459 field_value=form_result['new_field_value'],
460 460 field_label=form_result['new_field_label'],
461 461 field_desc=form_result['new_field_desc'])
462 462
463 463 Session().commit()
464 464 except Exception as e:
465 465 log.exception("Exception creating field")
466 466 msg = _('An error occurred during creation of field')
467 467 if isinstance(e, formencode.Invalid):
468 468 msg += ". " + e.msg
469 469 h.flash(msg, category='error')
470 470 return redirect(url('edit_repo_fields', repo_name=repo_name))
471 471
472 472 @HasRepoPermissionAllDecorator('repository.admin')
473 473 @auth.CSRFRequired()
474 474 def delete_repo_field(self, repo_name, field_id):
475 475 field = RepositoryField.get_or_404(field_id)
476 476 try:
477 477 RepoModel().delete_repo_field(repo_name, field.field_key)
478 478 Session().commit()
479 479 except Exception as e:
480 480 log.exception("Exception during removal of field")
481 481 msg = _('An error occurred during removal of field')
482 482 h.flash(msg, category='error')
483 483 return redirect(url('edit_repo_fields', repo_name=repo_name))
484 484
485 485 @HasRepoPermissionAllDecorator('repository.admin')
486 486 def edit_advanced(self, repo_name):
487 487 """GET /repo_name/settings: Form to edit an existing item"""
488 488 # url('edit_repo', repo_name=ID)
489 489 c.repo_info = self._load_repo(repo_name)
490 490 c.default_user_id = User.get_default_user().user_id
491 491 c.in_public_journal = UserFollowing.query()\
492 492 .filter(UserFollowing.user_id == c.default_user_id)\
493 493 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
494 494
495 495 c.active = 'advanced'
496 496 c.has_origin_repo_read_perm = False
497 497 if c.repo_info.fork:
498 498 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
499 499 'repository.write', 'repository.read', 'repository.admin')(
500 500 c.repo_info.fork.repo_name, 'repo set as fork page')
501 501
502 502 if request.POST:
503 503 return redirect(url('repo_edit_advanced'))
504 504 return render('admin/repos/repo_edit.html')
505 505
506 506 @HasRepoPermissionAllDecorator('repository.admin')
507 507 @auth.CSRFRequired()
508 508 def edit_advanced_journal(self, repo_name):
509 509 """
510 510 Set's this repository to be visible in public journal,
511 511 in other words assing default user to follow this repo
512 512
513 513 :param repo_name:
514 514 """
515 515
516 516 try:
517 517 repo_id = Repository.get_by_repo_name(repo_name).repo_id
518 518 user_id = User.get_default_user().user_id
519 519 self.scm_model.toggle_following_repo(repo_id, user_id)
520 520 h.flash(_('Updated repository visibility in public journal'),
521 521 category='success')
522 522 Session().commit()
523 523 except Exception:
524 524 h.flash(_('An error occurred during setting this'
525 525 ' repository in public journal'),
526 526 category='error')
527 527
528 528 return redirect(url('edit_repo_advanced', repo_name=repo_name))
529 529
530 530 @HasRepoPermissionAllDecorator('repository.admin')
531 531 @auth.CSRFRequired()
532 532 def edit_advanced_fork(self, repo_name):
533 533 """
534 534 Mark given repository as a fork of another
535 535
536 536 :param repo_name:
537 537 """
538 538
539 539 new_fork_id = request.POST.get('id_fork_of')
540 540 try:
541 541
542 542 if new_fork_id and not new_fork_id.isdigit():
543 543 log.error('Given fork id %s is not an INT', new_fork_id)
544 544
545 545 fork_id = safe_int(new_fork_id)
546 546 repo = ScmModel().mark_as_fork(repo_name, fork_id,
547 547 c.rhodecode_user.username)
548 548 fork = repo.fork.repo_name if repo.fork else _('Nothing')
549 549 Session().commit()
550 550 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
551 551 category='success')
552 552 except RepositoryError as e:
553 553 log.exception("Repository Error occurred")
554 554 h.flash(str(e), category='error')
555 555 except Exception as e:
556 556 log.exception("Exception while editing fork")
557 557 h.flash(_('An error occurred during this operation'),
558 558 category='error')
559 559
560 560 return redirect(url('edit_repo_advanced', repo_name=repo_name))
561 561
562 562 @HasRepoPermissionAllDecorator('repository.admin')
563 563 @auth.CSRFRequired()
564 564 def edit_advanced_locking(self, repo_name):
565 565 """
566 566 Unlock repository when it is locked !
567 567
568 568 :param repo_name:
569 569 """
570 570 try:
571 571 repo = Repository.get_by_repo_name(repo_name)
572 572 if request.POST.get('set_lock'):
573 573 Repository.lock(repo, c.rhodecode_user.user_id,
574 574 lock_reason=Repository.LOCK_WEB)
575 575 h.flash(_('Locked repository'), category='success')
576 576 elif request.POST.get('set_unlock'):
577 577 Repository.unlock(repo)
578 578 h.flash(_('Unlocked repository'), category='success')
579 579 except Exception as e:
580 580 log.exception("Exception during unlocking")
581 581 h.flash(_('An error occurred during unlocking'),
582 582 category='error')
583 583 return redirect(url('edit_repo_advanced', repo_name=repo_name))
584 584
585 585 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
586 586 @auth.CSRFRequired()
587 587 def toggle_locking(self, repo_name):
588 588 """
589 589 Toggle locking of repository by simple GET call to url
590 590
591 591 :param repo_name:
592 592 """
593 593
594 594 try:
595 595 repo = Repository.get_by_repo_name(repo_name)
596 596
597 597 if repo.enable_locking:
598 598 if repo.locked[0]:
599 599 Repository.unlock(repo)
600 600 action = _('Unlocked')
601 601 else:
602 602 Repository.lock(repo, c.rhodecode_user.user_id,
603 603 lock_reason=Repository.LOCK_WEB)
604 604 action = _('Locked')
605 605
606 606 h.flash(_('Repository has been %s') % action,
607 607 category='success')
608 608 except Exception:
609 609 log.exception("Exception during unlocking")
610 610 h.flash(_('An error occurred during unlocking'),
611 611 category='error')
612 612 return redirect(url('summary_home', repo_name=repo_name))
613 613
614 614 @HasRepoPermissionAllDecorator('repository.admin')
615 615 @auth.CSRFRequired()
616 616 def edit_caches(self, repo_name):
617 617 """PUT /{repo_name}/settings/caches: invalidate the repo caches."""
618 618 try:
619 619 ScmModel().mark_for_invalidation(repo_name, delete=True)
620 620 Session().commit()
621 621 h.flash(_('Cache invalidation successful'),
622 622 category='success')
623 623 except Exception:
624 624 log.exception("Exception during cache invalidation")
625 625 h.flash(_('An error occurred during cache invalidation'),
626 626 category='error')
627 627
628 628 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
629 629
630 630 @HasRepoPermissionAllDecorator('repository.admin')
631 631 def edit_caches_form(self, repo_name):
632 632 """GET /repo_name/settings: Form to edit an existing item"""
633 633 # url('edit_repo', repo_name=ID)
634 634 c.repo_info = self._load_repo(repo_name)
635 635 c.active = 'caches'
636 636
637 637 return render('admin/repos/repo_edit.html')
638 638
639 639 @HasRepoPermissionAllDecorator('repository.admin')
640 640 @auth.CSRFRequired()
641 641 def edit_remote(self, repo_name):
642 642 """PUT /{repo_name}/settings/remote: edit the repo remote."""
643 643 try:
644 644 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
645 645 h.flash(_('Pulled from remote location'), category='success')
646 646 except Exception:
647 647 log.exception("Exception during pull from remote")
648 648 h.flash(_('An error occurred during pull from remote location'),
649 649 category='error')
650 650 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
651 651
652 652 @HasRepoPermissionAllDecorator('repository.admin')
653 653 def edit_remote_form(self, repo_name):
654 654 """GET /repo_name/settings: Form to edit an existing item"""
655 655 # url('edit_repo', repo_name=ID)
656 656 c.repo_info = self._load_repo(repo_name)
657 657 c.active = 'remote'
658 658
659 659 return render('admin/repos/repo_edit.html')
660 660
661 661 @HasRepoPermissionAllDecorator('repository.admin')
662 662 @auth.CSRFRequired()
663 663 def edit_statistics(self, repo_name):
664 664 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
665 665 try:
666 666 RepoModel().delete_stats(repo_name)
667 667 Session().commit()
668 668 except Exception as e:
669 669 log.error(traceback.format_exc())
670 670 h.flash(_('An error occurred during deletion of repository stats'),
671 671 category='error')
672 672 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
673 673
674 674 @HasRepoPermissionAllDecorator('repository.admin')
675 675 def edit_statistics_form(self, repo_name):
676 676 """GET /repo_name/settings: Form to edit an existing item"""
677 677 # url('edit_repo', repo_name=ID)
678 678 c.repo_info = self._load_repo(repo_name)
679 679 repo = c.repo_info.scm_instance()
680 680
681 681 if c.repo_info.stats:
682 682 # this is on what revision we ended up so we add +1 for count
683 683 last_rev = c.repo_info.stats.stat_on_revision + 1
684 684 else:
685 685 last_rev = 0
686 686 c.stats_revision = last_rev
687 687
688 688 c.repo_last_rev = repo.count()
689 689
690 690 if last_rev == 0 or c.repo_last_rev == 0:
691 691 c.stats_percentage = 0
692 692 else:
693 693 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
694 694
695 695 c.active = 'statistics'
696 696
697 697 return render('admin/repos/repo_edit.html')
698 698
699 699 @HasRepoPermissionAllDecorator('repository.admin')
700 700 @auth.CSRFRequired()
701 701 def repo_issuetracker_test(self, repo_name):
702 702 if request.is_xhr:
703 703 return h.urlify_commit_message(
704 704 request.POST.get('test_text', ''),
705 705 repo_name)
706 706 else:
707 707 raise HTTPBadRequest()
708 708
709 709 @HasRepoPermissionAllDecorator('repository.admin')
710 710 @auth.CSRFRequired()
711 711 def repo_issuetracker_delete(self, repo_name):
712 712 uid = request.POST.get('uid')
713 713 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
714 714 try:
715 715 repo_settings.delete_entries(uid)
716 716 except Exception:
717 717 h.flash(_('Error occurred during deleting issue tracker entry'),
718 718 category='error')
719 719 else:
720 720 h.flash(_('Removed issue tracker entry'), category='success')
721 721 return redirect(url('repo_settings_issuetracker',
722 722 repo_name=repo_name))
723 723
724 724 def _update_patterns(self, form, repo_settings):
725 725 for uid in form['delete_patterns']:
726 726 repo_settings.delete_entries(uid)
727 727
728 728 for pattern in form['patterns']:
729 729 for setting, value, type_ in pattern:
730 730 sett = repo_settings.create_or_update_setting(
731 731 setting, value, type_)
732 732 Session().add(sett)
733 733
734 734 Session().commit()
735 735
736 736 @HasRepoPermissionAllDecorator('repository.admin')
737 737 @auth.CSRFRequired()
738 738 def repo_issuetracker_save(self, repo_name):
739 739 # Save inheritance
740 740 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
741 741 inherited = (request.POST.get('inherit_global_issuetracker')
742 742 == "inherited")
743 743 repo_settings.inherit_global_settings = inherited
744 744 Session().commit()
745 745
746 746 form = IssueTrackerPatternsForm()().to_python(request.POST)
747 747 if form:
748 748 self._update_patterns(form, repo_settings)
749 749
750 750 h.flash(_('Updated issue tracker entries'), category='success')
751 751 return redirect(url('repo_settings_issuetracker',
752 752 repo_name=repo_name))
753 753
754 754 @HasRepoPermissionAllDecorator('repository.admin')
755 755 def repo_issuetracker(self, repo_name):
756 756 """GET /admin/settings/issue-tracker: All items in the collection"""
757 757 c.active = 'issuetracker'
758 758 c.data = 'data'
759 759 c.repo_info = self._load_repo(repo_name)
760 760
761 761 repo = Repository.get_by_repo_name(repo_name)
762 762 c.settings_model = IssueTrackerSettingsModel(repo=repo)
763 763 c.global_patterns = c.settings_model.get_global_settings()
764 764 c.repo_patterns = c.settings_model.get_repo_settings()
765 765
766 766 return render('admin/repos/repo_edit.html')
767 767
768 768 @HasRepoPermissionAllDecorator('repository.admin')
769 769 def repo_settings_vcs(self, repo_name):
770 770 """GET /{repo_name}/settings/vcs/: All items in the collection"""
771 771
772 772 model = VcsSettingsModel(repo=repo_name)
773 773
774 774 c.active = 'vcs'
775 775 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
776 776 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
777 777 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
778 778 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
779 779 c.repo_info = self._load_repo(repo_name)
780 780 defaults = self._vcs_form_defaults(repo_name)
781 781 c.inherit_global_settings = defaults['inherit_global_settings']
782 782
783 783 return htmlfill.render(
784 784 render('admin/repos/repo_edit.html'),
785 785 defaults=defaults,
786 786 encoding="UTF-8",
787 787 force_defaults=False)
788 788
789 789 @HasRepoPermissionAllDecorator('repository.admin')
790 790 @auth.CSRFRequired()
791 791 def repo_settings_vcs_update(self, repo_name):
792 792 """POST /{repo_name}/settings/vcs/: All items in the collection"""
793 793 c.active = 'vcs'
794 794
795 795 model = VcsSettingsModel(repo=repo_name)
796 796 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
797 797 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
798 798 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
799 799 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
800 800 c.repo_info = self._load_repo(repo_name)
801 801 defaults = self._vcs_form_defaults(repo_name)
802 802 c.inherit_global_settings = defaults['inherit_global_settings']
803 803
804 804 application_form = RepoVcsSettingsForm(repo_name)()
805 805 try:
806 806 form_result = application_form.to_python(dict(request.POST))
807 807 except formencode.Invalid as errors:
808 808 h.flash(
809 809 _("Some form inputs contain invalid data."),
810 810 category='error')
811 811 return htmlfill.render(
812 812 render('admin/repos/repo_edit.html'),
813 813 defaults=errors.value,
814 814 errors=errors.error_dict or {},
815 815 prefix_error=False,
816 816 encoding="UTF-8",
817 817 force_defaults=False
818 818 )
819 819
820 820 try:
821 821 inherit_global_settings = form_result['inherit_global_settings']
822 822 model.create_or_update_repo_settings(
823 823 form_result, inherit_global_settings=inherit_global_settings)
824 824 except Exception:
825 825 log.exception("Exception while updating settings")
826 826 h.flash(
827 827 _('Error occurred during updating repository VCS settings'),
828 828 category='error')
829 829 else:
830 830 Session().commit()
831 831 h.flash(_('Updated VCS settings'), category='success')
832 832 return redirect(url('repo_vcs_settings', repo_name=repo_name))
833 833
834 834 return htmlfill.render(
835 835 render('admin/repos/repo_edit.html'),
836 836 defaults=self._vcs_form_defaults(repo_name),
837 837 encoding="UTF-8",
838 838 force_defaults=False)
839 839
840 840 @HasRepoPermissionAllDecorator('repository.admin')
841 841 @auth.CSRFRequired()
842 842 @jsonify
843 843 def repo_delete_svn_pattern(self, repo_name):
844 844 if not request.is_xhr:
845 845 return False
846 846
847 847 delete_pattern_id = request.POST.get('delete_svn_pattern')
848 848 model = VcsSettingsModel(repo=repo_name)
849 849 try:
850 850 model.delete_repo_svn_pattern(delete_pattern_id)
851 851 except SettingNotFound:
852 852 raise HTTPBadRequest()
853 853
854 854 Session().commit()
855 855 return True
856 856
857 857 def _vcs_form_defaults(self, repo_name):
858 858 model = VcsSettingsModel(repo=repo_name)
859 859 global_defaults = model.get_global_settings()
860 860
861 861 repo_defaults = {}
862 862 repo_defaults.update(global_defaults)
863 863 repo_defaults.update(model.get_repo_settings())
864 864
865 865 global_defaults = {
866 866 '{}_inherited'.format(k): global_defaults[k]
867 867 for k in global_defaults}
868 868
869 869 defaults = {
870 870 'inherit_global_settings': model.inherit_global_settings
871 871 }
872 872 defaults.update(global_defaults)
873 873 defaults.update(repo_defaults)
874 874 defaults.update({
875 875 'new_svn_branch': '',
876 876 'new_svn_tag': '',
877 877 })
878 878 return defaults
@@ -1,866 +1,813 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 settings controller for rhodecode admin
24 24 """
25 25
26 26 import collections
27 27 import logging
28 28 import urllib2
29 29
30 30 import datetime
31 31 import formencode
32 32 from formencode import htmlfill
33 33 import packaging.version
34 34 from pylons import request, tmpl_context as c, url, config
35 35 from pylons.controllers.util import redirect
36 36 from pylons.i18n.translation import _, lazy_ugettext
37 37 from webob.exc import HTTPBadRequest
38 38
39 39 import rhodecode
40 from rhodecode.admin.navigation import navigation_list
40 41 from rhodecode.lib import auth
41 42 from rhodecode.lib import helpers as h
42 43 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
43 44 from rhodecode.lib.base import BaseController, render
44 45 from rhodecode.lib.celerylib import tasks, run_task
45 46 from rhodecode.lib.utils import repo2db_mapper
46 47 from rhodecode.lib.utils2 import (
47 48 str2bool, safe_unicode, AttributeDict, safe_int)
48 49 from rhodecode.lib.compat import OrderedDict
49 50 from rhodecode.lib.ext_json import json
50 from rhodecode.lib.utils import jsonify, read_opensource_licenses
51 from rhodecode.lib.utils import jsonify
51 52
52 53 from rhodecode.model.db import RhodeCodeUi, Repository
53 54 from rhodecode.model.forms import ApplicationSettingsForm, \
54 55 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
55 56 LabsSettingsForm, IssueTrackerPatternsForm
56 57
57 58 from rhodecode.model.scm import ScmModel
58 59 from rhodecode.model.notification import EmailNotificationModel
59 60 from rhodecode.model.meta import Session
60 61 from rhodecode.model.settings import (
61 62 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
62 63 SettingsModel)
64
63 65 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
64 from rhodecode.model.user import UserModel
66
65 67
66 68 log = logging.getLogger(__name__)
67 69
68 70
69 71 class SettingsController(BaseController):
70 72 """REST Controller styled on the Atom Publishing Protocol"""
71 73 # To properly map this controller, ensure your config/routing.py
72 74 # file has a resource setup:
73 75 # map.resource('setting', 'settings', controller='admin/settings',
74 76 # path_prefix='/admin', name_prefix='admin_')
75 77
76 78 @LoginRequired()
77 79 def __before__(self):
78 80 super(SettingsController, self).__before__()
79 81 c.labs_active = str2bool(
80 82 rhodecode.CONFIG.get('labs_settings_active', 'false'))
81 c.navlist = navigation.get_navlist(request)
83 c.navlist = navigation_list(request)
82 84
83 85 def _get_hg_ui_settings(self):
84 86 ret = RhodeCodeUi.query().all()
85 87
86 88 if not ret:
87 89 raise Exception('Could not get application ui settings !')
88 90 settings = {}
89 91 for each in ret:
90 92 k = each.ui_key
91 93 v = each.ui_value
92 94 if k == '/':
93 95 k = 'root_path'
94 96
95 97 if k in ['push_ssl', 'publish']:
96 98 v = str2bool(v)
97 99
98 100 if k.find('.') != -1:
99 101 k = k.replace('.', '_')
100 102
101 103 if each.ui_section in ['hooks', 'extensions']:
102 104 v = each.ui_active
103 105
104 106 settings[each.ui_section + '_' + k] = v
105 107 return settings
106 108
107 109 @HasPermissionAllDecorator('hg.admin')
108 110 @auth.CSRFRequired()
109 111 @jsonify
110 112 def delete_svn_pattern(self):
111 113 if not request.is_xhr:
112 114 raise HTTPBadRequest()
113 115
114 116 delete_pattern_id = request.POST.get('delete_svn_pattern')
115 117 model = VcsSettingsModel()
116 118 try:
117 119 model.delete_global_svn_pattern(delete_pattern_id)
118 120 except SettingNotFound:
119 121 raise HTTPBadRequest()
120 122
121 123 Session().commit()
122 124 return True
123 125
124 126 @HasPermissionAllDecorator('hg.admin')
125 127 @auth.CSRFRequired()
126 128 def settings_vcs_update(self):
127 129 """POST /admin/settings: All items in the collection"""
128 130 # url('admin_settings_vcs')
129 131 c.active = 'vcs'
130 132
131 133 model = VcsSettingsModel()
132 134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
133 135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
134 136
135 137 application_form = ApplicationUiSettingsForm()()
136 138 try:
137 139 form_result = application_form.to_python(dict(request.POST))
138 140 except formencode.Invalid as errors:
139 141 h.flash(
140 142 _("Some form inputs contain invalid data."),
141 143 category='error')
142 144 return htmlfill.render(
143 145 render('admin/settings/settings.html'),
144 146 defaults=errors.value,
145 147 errors=errors.error_dict or {},
146 148 prefix_error=False,
147 149 encoding="UTF-8",
148 150 force_defaults=False
149 151 )
150 152
151 153 try:
152 154 model.update_global_ssl_setting(form_result['web_push_ssl'])
153 155 if c.visual.allow_repo_location_change:
154 156 model.update_global_path_setting(
155 157 form_result['paths_root_path'])
156 158 model.update_global_hook_settings(form_result)
157 159 model.create_global_svn_settings(form_result)
158 160 model.create_or_update_global_hg_settings(form_result)
159 161 model.create_or_update_global_pr_settings(form_result)
160 162 except Exception:
161 163 log.exception("Exception while updating settings")
162 164 h.flash(_('Error occurred during updating '
163 165 'application settings'), category='error')
164 166 else:
165 167 Session().commit()
166 168 h.flash(_('Updated VCS settings'), category='success')
167 169 return redirect(url('admin_settings_vcs'))
168 170
169 171 return htmlfill.render(
170 172 render('admin/settings/settings.html'),
171 173 defaults=self._form_defaults(),
172 174 encoding="UTF-8",
173 175 force_defaults=False)
174 176
175 177 @HasPermissionAllDecorator('hg.admin')
176 178 def settings_vcs(self):
177 179 """GET /admin/settings: All items in the collection"""
178 180 # url('admin_settings_vcs')
179 181 c.active = 'vcs'
180 182 model = VcsSettingsModel()
181 183 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
182 184 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
183 185
184 186 return htmlfill.render(
185 187 render('admin/settings/settings.html'),
186 188 defaults=self._form_defaults(),
187 189 encoding="UTF-8",
188 190 force_defaults=False)
189 191
190 192 @HasPermissionAllDecorator('hg.admin')
191 193 @auth.CSRFRequired()
192 194 def settings_mapping_update(self):
193 195 """POST /admin/settings/mapping: All items in the collection"""
194 196 # url('admin_settings_mapping')
195 197 c.active = 'mapping'
196 198 rm_obsolete = request.POST.get('destroy', False)
197 199 invalidate_cache = request.POST.get('invalidate', False)
198 200 log.debug(
199 201 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
200 202
201 203 if invalidate_cache:
202 204 log.debug('invalidating all repositories cache')
203 205 for repo in Repository.get_all():
204 206 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
205 207
206 208 filesystem_repos = ScmModel().repo_scan()
207 209 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
208 210 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
209 211 h.flash(_('Repositories successfully '
210 212 'rescanned added: %s ; removed: %s') %
211 213 (_repr(added), _repr(removed)),
212 214 category='success')
213 215 return redirect(url('admin_settings_mapping'))
214 216
215 217 @HasPermissionAllDecorator('hg.admin')
216 218 def settings_mapping(self):
217 219 """GET /admin/settings/mapping: All items in the collection"""
218 220 # url('admin_settings_mapping')
219 221 c.active = 'mapping'
220 222
221 223 return htmlfill.render(
222 224 render('admin/settings/settings.html'),
223 225 defaults=self._form_defaults(),
224 226 encoding="UTF-8",
225 227 force_defaults=False)
226 228
227 229 @HasPermissionAllDecorator('hg.admin')
228 230 @auth.CSRFRequired()
229 231 def settings_global_update(self):
230 232 """POST /admin/settings/global: All items in the collection"""
231 233 # url('admin_settings_global')
232 234 c.active = 'global'
233 235 application_form = ApplicationSettingsForm()()
234 236 try:
235 237 form_result = application_form.to_python(dict(request.POST))
236 238 except formencode.Invalid as errors:
237 239 return htmlfill.render(
238 240 render('admin/settings/settings.html'),
239 241 defaults=errors.value,
240 242 errors=errors.error_dict or {},
241 243 prefix_error=False,
242 244 encoding="UTF-8",
243 245 force_defaults=False)
244 246
245 247 try:
246 248 settings = [
247 249 ('title', 'rhodecode_title'),
248 250 ('realm', 'rhodecode_realm'),
249 251 ('pre_code', 'rhodecode_pre_code'),
250 252 ('post_code', 'rhodecode_post_code'),
251 253 ('captcha_public_key', 'rhodecode_captcha_public_key'),
252 254 ('captcha_private_key', 'rhodecode_captcha_private_key'),
253 255 ]
254 256 for setting, form_key in settings:
255 257 sett = SettingsModel().create_or_update_setting(
256 258 setting, form_result[form_key])
257 259 Session().add(sett)
258 260
259 261 Session().commit()
262 SettingsModel().invalidate_settings_cache()
260 263 h.flash(_('Updated application settings'), category='success')
261
262 264 except Exception:
263 265 log.exception("Exception while updating application settings")
264 266 h.flash(
265 267 _('Error occurred during updating application settings'),
266 268 category='error')
267 269
268 270 return redirect(url('admin_settings_global'))
269 271
270 272 @HasPermissionAllDecorator('hg.admin')
271 273 def settings_global(self):
272 274 """GET /admin/settings/global: All items in the collection"""
273 275 # url('admin_settings_global')
274 276 c.active = 'global'
275 277
276 278 return htmlfill.render(
277 279 render('admin/settings/settings.html'),
278 280 defaults=self._form_defaults(),
279 281 encoding="UTF-8",
280 282 force_defaults=False)
281 283
282 284 @HasPermissionAllDecorator('hg.admin')
283 285 @auth.CSRFRequired()
284 286 def settings_visual_update(self):
285 287 """POST /admin/settings/visual: All items in the collection"""
286 288 # url('admin_settings_visual')
287 289 c.active = 'visual'
288 290 application_form = ApplicationVisualisationForm()()
289 291 try:
290 292 form_result = application_form.to_python(dict(request.POST))
291 293 except formencode.Invalid as errors:
292 294 return htmlfill.render(
293 295 render('admin/settings/settings.html'),
294 296 defaults=errors.value,
295 297 errors=errors.error_dict or {},
296 298 prefix_error=False,
297 299 encoding="UTF-8",
298 300 force_defaults=False
299 301 )
300 302
301 303 try:
302 304 settings = [
303 305 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
304 306 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
305 307 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
306 308 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
307 309 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
308 310 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
309 311 ('show_version', 'rhodecode_show_version', 'bool'),
310 312 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
311 313 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
312 314 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
313 315 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
314 316 ('support_url', 'rhodecode_support_url', 'unicode'),
315 317 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
316 318 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
317 319 ]
318 320 for setting, form_key, type_ in settings:
319 321 sett = SettingsModel().create_or_update_setting(
320 322 setting, form_result[form_key], type_)
321 323 Session().add(sett)
322 324
323 325 Session().commit()
324
326 SettingsModel().invalidate_settings_cache()
325 327 h.flash(_('Updated visualisation settings'), category='success')
326 328 except Exception:
327 329 log.exception("Exception updating visualization settings")
328 330 h.flash(_('Error occurred during updating '
329 331 'visualisation settings'),
330 332 category='error')
331 333
332 334 return redirect(url('admin_settings_visual'))
333 335
334 336 @HasPermissionAllDecorator('hg.admin')
335 337 def settings_visual(self):
336 338 """GET /admin/settings/visual: All items in the collection"""
337 339 # url('admin_settings_visual')
338 340 c.active = 'visual'
339 341
340 342 return htmlfill.render(
341 343 render('admin/settings/settings.html'),
342 344 defaults=self._form_defaults(),
343 345 encoding="UTF-8",
344 346 force_defaults=False)
345 347
346 348 @HasPermissionAllDecorator('hg.admin')
347 349 @auth.CSRFRequired()
348 350 def settings_issuetracker_test(self):
349 351 if request.is_xhr:
350 352 return h.urlify_commit_message(
351 353 request.POST.get('test_text', ''),
352 354 'repo_group/test_repo1')
353 355 else:
354 356 raise HTTPBadRequest()
355 357
356 358 @HasPermissionAllDecorator('hg.admin')
357 359 @auth.CSRFRequired()
358 360 def settings_issuetracker_delete(self):
359 361 uid = request.POST.get('uid')
360 362 IssueTrackerSettingsModel().delete_entries(uid)
361 363 h.flash(_('Removed issue tracker entry'), category='success')
362 364 return redirect(url('admin_settings_issuetracker'))
363 365
364 366 @HasPermissionAllDecorator('hg.admin')
365 367 def settings_issuetracker(self):
366 368 """GET /admin/settings/issue-tracker: All items in the collection"""
367 369 # url('admin_settings_issuetracker')
368 370 c.active = 'issuetracker'
369 371 defaults = SettingsModel().get_all_settings()
370 372
371 373 entry_key = 'rhodecode_issuetracker_pat_'
372 374
373 375 c.issuetracker_entries = {}
374 376 for k, v in defaults.items():
375 377 if k.startswith(entry_key):
376 378 uid = k[len(entry_key):]
377 379 c.issuetracker_entries[uid] = None
378 380
379 381 for uid in c.issuetracker_entries:
380 382 c.issuetracker_entries[uid] = AttributeDict({
381 383 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
382 384 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
383 385 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
384 386 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
385 387 })
386 388
387 389 return render('admin/settings/settings.html')
388 390
389 391 @HasPermissionAllDecorator('hg.admin')
390 392 @auth.CSRFRequired()
391 393 def settings_issuetracker_save(self):
392 394 settings_model = IssueTrackerSettingsModel()
393 395
394 396 form = IssueTrackerPatternsForm()().to_python(request.POST)
395 397 for uid in form['delete_patterns']:
396 398 settings_model.delete_entries(uid)
397 399
398 400 for pattern in form['patterns']:
399 401 for setting, value, type_ in pattern:
400 402 sett = settings_model.create_or_update_setting(
401 403 setting, value, type_)
402 404 Session().add(sett)
403 405
404 406 Session().commit()
405 407
408 SettingsModel().invalidate_settings_cache()
406 409 h.flash(_('Updated issue tracker entries'), category='success')
407 410 return redirect(url('admin_settings_issuetracker'))
408 411
409 412 @HasPermissionAllDecorator('hg.admin')
410 413 @auth.CSRFRequired()
411 414 def settings_email_update(self):
412 415 """POST /admin/settings/email: All items in the collection"""
413 416 # url('admin_settings_email')
414 417 c.active = 'email'
415 418
416 419 test_email = request.POST.get('test_email')
417 420
418 421 if not test_email:
419 422 h.flash(_('Please enter email address'), category='error')
420 423 return redirect(url('admin_settings_email'))
421 424
422 425 email_kwargs = {
423 426 'date': datetime.datetime.now(),
424 427 'user': c.rhodecode_user,
425 428 'rhodecode_version': c.rhodecode_version
426 429 }
427 430
428 431 (subject, headers, email_body,
429 432 email_body_plaintext) = EmailNotificationModel().render_email(
430 433 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
431 434
432 435 recipients = [test_email] if test_email else None
433 436
434 437 run_task(tasks.send_email, recipients, subject,
435 438 email_body_plaintext, email_body)
436 439
437 440 h.flash(_('Send email task created'), category='success')
438 441 return redirect(url('admin_settings_email'))
439 442
440 443 @HasPermissionAllDecorator('hg.admin')
441 444 def settings_email(self):
442 445 """GET /admin/settings/email: All items in the collection"""
443 446 # url('admin_settings_email')
444 447 c.active = 'email'
445 448 c.rhodecode_ini = rhodecode.CONFIG
446 449
447 450 return htmlfill.render(
448 451 render('admin/settings/settings.html'),
449 452 defaults=self._form_defaults(),
450 453 encoding="UTF-8",
451 454 force_defaults=False)
452 455
453 456 @HasPermissionAllDecorator('hg.admin')
454 457 @auth.CSRFRequired()
455 458 def settings_hooks_update(self):
456 459 """POST or DELETE /admin/settings/hooks: All items in the collection"""
457 460 # url('admin_settings_hooks')
458 461 c.active = 'hooks'
459 462 if c.visual.allow_custom_hooks_settings:
460 463 ui_key = request.POST.get('new_hook_ui_key')
461 464 ui_value = request.POST.get('new_hook_ui_value')
462 465
463 466 hook_id = request.POST.get('hook_id')
464 467 new_hook = False
465 468
466 469 model = SettingsModel()
467 470 try:
468 471 if ui_value and ui_key:
469 472 model.create_or_update_hook(ui_key, ui_value)
470 473 h.flash(_('Added new hook'), category='success')
471 474 new_hook = True
472 475 elif hook_id:
473 476 RhodeCodeUi.delete(hook_id)
474 477 Session().commit()
475 478
476 479 # check for edits
477 480 update = False
478 481 _d = request.POST.dict_of_lists()
479 482 for k, v in zip(_d.get('hook_ui_key', []),
480 483 _d.get('hook_ui_value_new', [])):
481 484 model.create_or_update_hook(k, v)
482 485 update = True
483 486
484 487 if update and not new_hook:
485 488 h.flash(_('Updated hooks'), category='success')
486 489 Session().commit()
487 490 except Exception:
488 491 log.exception("Exception during hook creation")
489 492 h.flash(_('Error occurred during hook creation'),
490 493 category='error')
491 494
492 495 return redirect(url('admin_settings_hooks'))
493 496
494 497 @HasPermissionAllDecorator('hg.admin')
495 498 def settings_hooks(self):
496 499 """GET /admin/settings/hooks: All items in the collection"""
497 500 # url('admin_settings_hooks')
498 501 c.active = 'hooks'
499 502
500 503 model = SettingsModel()
501 504 c.hooks = model.get_builtin_hooks()
502 505 c.custom_hooks = model.get_custom_hooks()
503 506
504 507 return htmlfill.render(
505 508 render('admin/settings/settings.html'),
506 509 defaults=self._form_defaults(),
507 510 encoding="UTF-8",
508 511 force_defaults=False)
509 512
510 513 @HasPermissionAllDecorator('hg.admin')
511 514 def settings_search(self):
512 515 """GET /admin/settings/search: All items in the collection"""
513 516 # url('admin_settings_search')
514 517 c.active = 'search'
515 518
516 519 from rhodecode.lib.index import searcher_from_config
517 520 searcher = searcher_from_config(config)
518 521 c.statistics = searcher.statistics()
519 522
520 523 return render('admin/settings/settings.html')
521 524
522 525 @HasPermissionAllDecorator('hg.admin')
523 526 def settings_system(self):
524 527 """GET /admin/settings/system: All items in the collection"""
525 528 # url('admin_settings_system')
529 snapshot = str2bool(request.GET.get('snapshot'))
526 530 c.active = 'system'
527 531
528 532 defaults = self._form_defaults()
529 533 c.rhodecode_ini = rhodecode.CONFIG
530 534 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
531 535 server_info = ScmModel().get_server_info(request.environ)
532 536 for key, val in server_info.iteritems():
533 537 setattr(c, key, val)
534 538
535 539 if c.disk['percent'] > 90:
536 540 h.flash(h.literal(_(
537 541 'Critical: your disk space is very low <b>%s%%</b> used' %
538 542 c.disk['percent'])), 'error')
539 543 elif c.disk['percent'] > 70:
540 544 h.flash(h.literal(_(
541 545 'Warning: your disk space is running low <b>%s%%</b> used' %
542 546 c.disk['percent'])), 'warning')
543 547
544 548 try:
545 549 c.uptime_age = h._age(
546 550 h.time_to_datetime(c.boot_time), False, show_suffix=False)
547 551 except TypeError:
548 552 c.uptime_age = c.boot_time
549 553
550 554 try:
551 555 c.system_memory = '%s/%s, %s%% (%s%%) used%s' % (
552 556 h.format_byte_size_binary(c.memory['used']),
553 557 h.format_byte_size_binary(c.memory['total']),
554 558 c.memory['percent2'],
555 559 c.memory['percent'],
556 560 ' %s' % c.memory['error'] if 'error' in c.memory else '')
557 561 except TypeError:
558 562 c.system_memory = 'NOT AVAILABLE'
559 563
564 rhodecode_ini_safe = rhodecode.CONFIG.copy()
565 blacklist = [
566 'rhodecode_license_key',
567 'routes.map',
568 'pylons.h',
569 'pylons.app_globals',
570 'pylons.environ_config',
571 'sqlalchemy.db1.url',
572 ('app_conf', 'sqlalchemy.db1.url')
573 ]
574 for k in blacklist:
575 if isinstance(k, tuple):
576 section, key = k
577 if section in rhodecode_ini_safe:
578 rhodecode_ini_safe[section].pop(key, None)
579 else:
580 rhodecode_ini_safe.pop(k, None)
581
582 c.rhodecode_ini_safe = rhodecode_ini_safe
583
584 # TODO: marcink, figure out how to allow only selected users to do this
585 c.allowed_to_snapshot = False
586
587 if snapshot:
588 if c.allowed_to_snapshot:
589 return render('admin/settings/settings_system_snapshot.html')
590 else:
591 h.flash('You are not allowed to do this', category='warning')
592
560 593 return htmlfill.render(
561 594 render('admin/settings/settings.html'),
562 595 defaults=defaults,
563 596 encoding="UTF-8",
564 597 force_defaults=False)
565 598
566 599 @staticmethod
567 600 def get_update_data(update_url):
568 601 """Return the JSON update data."""
569 602 ver = rhodecode.__version__
570 603 log.debug('Checking for upgrade on `%s` server', update_url)
571 604 opener = urllib2.build_opener()
572 605 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
573 606 response = opener.open(update_url)
574 607 response_data = response.read()
575 608 data = json.loads(response_data)
576 609
577 610 return data
578 611
579 612 @HasPermissionAllDecorator('hg.admin')
580 613 def settings_system_update(self):
581 614 """GET /admin/settings/system/updates: All items in the collection"""
582 615 # url('admin_settings_system_update')
583 616 defaults = self._form_defaults()
584 617 update_url = defaults.get('rhodecode_update_url', '')
585 618
586 619 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
587 620 try:
588 621 data = self.get_update_data(update_url)
589 622 except urllib2.URLError as e:
590 623 log.exception("Exception contacting upgrade server")
591 624 return _err('Failed to contact upgrade server: %r' % e)
592 625 except ValueError as e:
593 626 log.exception("Bad data sent from update server")
594 627 return _err('Bad data sent from update server')
595 628
596 629 latest = data['versions'][0]
597 630
598 631 c.update_url = update_url
599 632 c.latest_data = latest
600 633 c.latest_ver = latest['version']
601 634 c.cur_ver = rhodecode.__version__
602 635 c.should_upgrade = False
603 636
604 637 if (packaging.version.Version(c.latest_ver) >
605 638 packaging.version.Version(c.cur_ver)):
606 639 c.should_upgrade = True
607 640 c.important_notices = latest['general']
608 641
609 642 return render('admin/settings/settings_system_update.html')
610 643
611 644 @HasPermissionAllDecorator('hg.admin')
612 645 def settings_supervisor(self):
613 646 c.rhodecode_ini = rhodecode.CONFIG
614 647 c.active = 'supervisor'
615 648
616 649 c.supervisor_procs = OrderedDict([
617 650 (SUPERVISOR_MASTER, {}),
618 651 ])
619 652
620 653 c.log_size = 10240
621 654 supervisor = SupervisorModel()
622 655
623 656 _connection = supervisor.get_connection(
624 657 c.rhodecode_ini.get('supervisor.uri'))
625 658 c.connection_error = None
626 659 try:
627 660 _connection.supervisor.getAllProcessInfo()
628 661 except Exception as e:
629 662 c.connection_error = str(e)
630 663 log.exception("Exception reading supervisor data")
631 664 return render('admin/settings/settings.html')
632 665
633 666 groupid = c.rhodecode_ini.get('supervisor.group_id')
634 667
635 668 # feed our group processes to the main
636 669 for proc in supervisor.get_group_processes(_connection, groupid):
637 670 c.supervisor_procs[proc['name']] = {}
638 671
639 672 for k in c.supervisor_procs.keys():
640 673 try:
641 674 # master process info
642 675 if k == SUPERVISOR_MASTER:
643 676 _data = supervisor.get_master_state(_connection)
644 677 _data['name'] = 'supervisor master'
645 678 _data['description'] = 'pid %s, id: %s, ver: %s' % (
646 679 _data['pid'], _data['id'], _data['ver'])
647 680 c.supervisor_procs[k] = _data
648 681 else:
649 682 procid = groupid + ":" + k
650 683 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
651 684 except Exception as e:
652 685 log.exception("Exception reading supervisor data")
653 686 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
654 687
655 688 return render('admin/settings/settings.html')
656 689
657 690 @HasPermissionAllDecorator('hg.admin')
658 691 def settings_supervisor_log(self, procid):
659 692 import rhodecode
660 693 c.rhodecode_ini = rhodecode.CONFIG
661 694 c.active = 'supervisor_tail'
662 695
663 696 supervisor = SupervisorModel()
664 697 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
665 698 groupid = c.rhodecode_ini.get('supervisor.group_id')
666 699 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
667 700
668 701 c.log_size = 10240
669 702 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
670 703 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
671 704
672 705 return render('admin/settings/settings.html')
673 706
674 707 @HasPermissionAllDecorator('hg.admin')
675 708 @auth.CSRFRequired()
676 709 def settings_labs_update(self):
677 710 """POST /admin/settings/labs: All items in the collection"""
678 711 # url('admin_settings/labs', method={'POST'})
679 712 c.active = 'labs'
680 713
681 714 application_form = LabsSettingsForm()()
682 715 try:
683 716 form_result = application_form.to_python(dict(request.POST))
684 717 except formencode.Invalid as errors:
685 718 h.flash(
686 719 _('Some form inputs contain invalid data.'),
687 720 category='error')
688 721 return htmlfill.render(
689 722 render('admin/settings/settings.html'),
690 723 defaults=errors.value,
691 724 errors=errors.error_dict or {},
692 725 prefix_error=False,
693 726 encoding='UTF-8',
694 727 force_defaults=False
695 728 )
696 729
697 730 try:
698 731 session = Session()
699 732 for setting in _LAB_SETTINGS:
700 733 setting_name = setting.key[len('rhodecode_'):]
701 734 sett = SettingsModel().create_or_update_setting(
702 735 setting_name, form_result[setting.key], setting.type)
703 736 session.add(sett)
704 737
705 738 except Exception:
706 739 log.exception('Exception while updating lab settings')
707 740 h.flash(_('Error occurred during updating labs settings'),
708 741 category='error')
709 742 else:
710 743 Session().commit()
744 SettingsModel().invalidate_settings_cache()
711 745 h.flash(_('Updated Labs settings'), category='success')
712 746 return redirect(url('admin_settings_labs'))
713 747
714 748 return htmlfill.render(
715 749 render('admin/settings/settings.html'),
716 750 defaults=self._form_defaults(),
717 751 encoding='UTF-8',
718 752 force_defaults=False)
719 753
720 754 @HasPermissionAllDecorator('hg.admin')
721 755 def settings_labs(self):
722 756 """GET /admin/settings/labs: All items in the collection"""
723 757 # url('admin_settings_labs')
724 758 if not c.labs_active:
725 759 redirect(url('admin_settings'))
726 760
727 761 c.active = 'labs'
728 762 c.lab_settings = _LAB_SETTINGS
729 763
730 764 return htmlfill.render(
731 765 render('admin/settings/settings.html'),
732 766 defaults=self._form_defaults(),
733 767 encoding='UTF-8',
734 768 force_defaults=False)
735 769
736 @HasPermissionAllDecorator('hg.admin')
737 def settings_open_source(self):
738 # url('admin_settings_open_source')
739
740 c.active = 'open_source'
741 c.opensource_licenses = collections.OrderedDict(
742 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
743
744 return htmlfill.render(
745 render('admin/settings/settings.html'),
746 defaults=self._form_defaults(),
747 encoding='UTF-8',
748 force_defaults=False)
749
750 770 def _form_defaults(self):
751 771 defaults = SettingsModel().get_all_settings()
752 772 defaults.update(self._get_hg_ui_settings())
753 773 defaults.update({
754 774 'new_svn_branch': '',
755 775 'new_svn_tag': '',
756 776 })
757 777 return defaults
758 778
759 779
760 780 # :param key: name of the setting including the 'rhodecode_' prefix
761 781 # :param type: the RhodeCodeSetting type to use.
762 782 # :param group: the i18ned group in which we should dispaly this setting
763 783 # :param label: the i18ned label we should display for this setting
764 784 # :param help: the i18ned help we should dispaly for this setting
765 785 LabSetting = collections.namedtuple(
766 786 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
767 787
768 788
769 789 # This list has to be kept in sync with the form
770 790 # rhodecode.model.forms.LabsSettingsForm.
771 791 _LAB_SETTINGS = [
772 792 LabSetting(
773 793 key='rhodecode_hg_use_rebase_for_merging',
774 794 type='bool',
775 795 group=lazy_ugettext('Mercurial server-side merge'),
776 796 label=lazy_ugettext('Use rebase instead of creating a merge commit when merging via web interface'),
777 797 help='' # Do not translate the empty string!
778 798 ),
779 799 LabSetting(
780 800 key='rhodecode_proxy_subversion_http_requests',
781 801 type='bool',
782 802 group=lazy_ugettext('Subversion HTTP Support'),
783 803 label=lazy_ugettext('Proxy subversion HTTP requests'),
784 804 help='' # Do not translate the empty string!
785 805 ),
786 806 LabSetting(
787 807 key='rhodecode_subversion_http_server_url',
788 808 type='str',
789 809 group=lazy_ugettext('Subversion HTTP Server URL'),
790 810 label='', # Do not translate the empty string!
791 811 help=lazy_ugettext('e.g. http://localhost:8080/')
792 812 ),
793 813 ]
794
795
796 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
797
798
799 class NavEntry(object):
800
801 def __init__(self, key, name, view_name, pyramid=False):
802 self.key = key
803 self.name = name
804 self.view_name = view_name
805 self.pyramid = pyramid
806
807 def generate_url(self, request):
808 if self.pyramid:
809 if hasattr(request, 'route_path'):
810 return request.route_path(self.view_name)
811 else:
812 # TODO: johbo: Remove this after migrating to pyramid.
813 # We need the pyramid request here to generate URLs to pyramid
814 # views from within pylons views.
815 from pyramid.threadlocal import get_current_request
816 pyramid_request = get_current_request()
817 return pyramid_request.route_path(self.view_name)
818 else:
819 return url(self.view_name)
820
821
822 class NavigationRegistry(object):
823
824 _base_entries = [
825 NavEntry('global', lazy_ugettext('Global'), 'admin_settings_global'),
826 NavEntry('vcs', lazy_ugettext('VCS'), 'admin_settings_vcs'),
827 NavEntry('visual', lazy_ugettext('Visual'), 'admin_settings_visual'),
828 NavEntry('mapping', lazy_ugettext('Remap and Rescan'),
829 'admin_settings_mapping'),
830 NavEntry('issuetracker', lazy_ugettext('Issue Tracker'),
831 'admin_settings_issuetracker'),
832 NavEntry('email', lazy_ugettext('Email'), 'admin_settings_email'),
833 NavEntry('hooks', lazy_ugettext('Hooks'), 'admin_settings_hooks'),
834 NavEntry('search', lazy_ugettext('Full Text Search'),
835 'admin_settings_search'),
836 NavEntry('system', lazy_ugettext('System Info'),
837 'admin_settings_system'),
838 NavEntry('open_source', lazy_ugettext('Open Source Licenses'),
839 'admin_settings_open_source'),
840 # TODO: marcink: we disable supervisor now until the supervisor stats
841 # page is fixed in the nix configuration
842 # NavEntry('supervisor', lazy_ugettext('Supervisor'),
843 # 'admin_settings_supervisor'),
844 ]
845
846 def __init__(self):
847 self._registered_entries = collections.OrderedDict([
848 (item.key, item) for item in self.__class__._base_entries
849 ])
850
851 # Add the labs entry when it's activated.
852 labs_active = str2bool(
853 rhodecode.CONFIG.get('labs_settings_active', 'false'))
854 if labs_active:
855 self.add_entry(
856 NavEntry('labs', lazy_ugettext('Labs'), 'admin_settings_labs'))
857
858 def add_entry(self, entry):
859 self._registered_entries[entry.key] = entry
860
861 def get_navlist(self, request):
862 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
863 for i in self._registered_entries.values()]
864 return navlist
865
866 navigation = NavigationRegistry()
@@ -1,480 +1,480 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 User Groups crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 from formencode import htmlfill
29 29 from pylons import request, tmpl_context as c, url, config
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 33 from sqlalchemy.orm import joinedload
34 34
35 35 from rhodecode.lib import auth
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
38 38 RepoGroupAssignmentError
39 39 from rhodecode.lib.utils import jsonify, action_logger
40 40 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
41 41 from rhodecode.lib.auth import (
42 42 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
43 43 HasPermissionAnyDecorator)
44 44 from rhodecode.lib.base import BaseController, render
45 45 from rhodecode.model.permission import PermissionModel
46 46 from rhodecode.model.scm import UserGroupList
47 47 from rhodecode.model.user_group import UserGroupModel
48 48 from rhodecode.model.db import (
49 49 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
50 50 from rhodecode.model.forms import (
51 51 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
52 52 UserPermissionsForm)
53 53 from rhodecode.model.meta import Session
54 54 from rhodecode.lib.utils import action_logger
55 55 from rhodecode.lib.ext_json import json
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class UserGroupsController(BaseController):
61 61 """REST Controller styled on the Atom Publishing Protocol"""
62 62
63 63 @LoginRequired()
64 64 def __before__(self):
65 65 super(UserGroupsController, self).__before__()
66 66 c.available_permissions = config['available_permissions']
67 67 PermissionModel().set_global_permission_choices(c, translator=_)
68 68
69 69 def __load_data(self, user_group_id):
70 70 c.group_members_obj = [x.user for x in c.user_group.members]
71 71 c.group_members_obj.sort(key=lambda u: u.username.lower())
72 72
73 73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74 74
75 75 c.available_members = [(x.user_id, x.username)
76 76 for x in User.query().all()]
77 77 c.available_members.sort(key=lambda u: u[1].lower())
78 78
79 79 def __load_defaults(self, user_group_id):
80 80 """
81 81 Load defaults settings for edit, and update
82 82
83 83 :param user_group_id:
84 84 """
85 85 user_group = UserGroup.get_or_404(user_group_id)
86 86 data = user_group.get_dict()
87 87 # fill owner
88 88 if user_group.user:
89 89 data.update({'user': user_group.user.username})
90 90 else:
91 replacement_user = User.get_first_admin().username
91 replacement_user = User.get_first_super_admin().username
92 92 data.update({'user': replacement_user})
93 93 return data
94 94
95 95 def _revoke_perms_on_yourself(self, form_result):
96 96 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 97 form_result['perm_updates'])
98 98 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
99 99 form_result['perm_additions'])
100 100 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
101 101 form_result['perm_deletions'])
102 102 admin_perm = 'usergroup.admin'
103 103 if _updates and _updates[0][1] != admin_perm or \
104 104 _additions and _additions[0][1] != admin_perm or \
105 105 _deletions and _deletions[0][1] != admin_perm:
106 106 return True
107 107 return False
108 108
109 109 # permission check inside
110 110 @NotAnonymous()
111 111 def index(self):
112 112 """GET /users_groups: All items in the collection"""
113 113 # url('users_groups')
114 114
115 115 from rhodecode.lib.utils import PartialRenderer
116 116 _render = PartialRenderer('data_table/_dt_elements.html')
117 117
118 118 def user_group_name(user_group_id, user_group_name):
119 119 return _render("user_group_name", user_group_id, user_group_name)
120 120
121 121 def user_group_actions(user_group_id, user_group_name):
122 122 return _render("user_group_actions", user_group_id, user_group_name)
123 123
124 124 ## json generate
125 125 group_iter = UserGroupList(UserGroup.query().all(),
126 126 perm_set=['usergroup.admin'])
127 127
128 128 user_groups_data = []
129 129 for user_gr in group_iter:
130 130 user_groups_data.append({
131 131 "group_name": user_group_name(
132 132 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
133 133 "group_name_raw": user_gr.users_group_name,
134 134 "desc": h.escape(user_gr.user_group_description),
135 135 "members": len(user_gr.members),
136 136 "active": h.bool2icon(user_gr.users_group_active),
137 137 "owner": h.escape(h.link_to_user(user_gr.user.username)),
138 138 "action": user_group_actions(
139 139 user_gr.users_group_id, user_gr.users_group_name)
140 140 })
141 141
142 142 c.data = json.dumps(user_groups_data)
143 143 return render('admin/user_groups/user_groups.html')
144 144
145 145 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
146 146 @auth.CSRFRequired()
147 147 def create(self):
148 148 """POST /users_groups: Create a new item"""
149 149 # url('users_groups')
150 150
151 151 users_group_form = UserGroupForm()()
152 152 try:
153 153 form_result = users_group_form.to_python(dict(request.POST))
154 154 user_group = UserGroupModel().create(
155 155 name=form_result['users_group_name'],
156 156 description=form_result['user_group_description'],
157 157 owner=c.rhodecode_user.user_id,
158 158 active=form_result['users_group_active'])
159 159 Session().flush()
160 160
161 161 user_group_name = form_result['users_group_name']
162 162 action_logger(c.rhodecode_user,
163 163 'admin_created_users_group:%s' % user_group_name,
164 164 None, self.ip_addr, self.sa)
165 165 user_group_link = h.link_to(h.escape(user_group_name),
166 166 url('edit_users_group',
167 167 user_group_id=user_group.users_group_id))
168 168 h.flash(h.literal(_('Created user group %(user_group_link)s')
169 169 % {'user_group_link': user_group_link}),
170 170 category='success')
171 171 Session().commit()
172 172 except formencode.Invalid as errors:
173 173 return htmlfill.render(
174 174 render('admin/user_groups/user_group_add.html'),
175 175 defaults=errors.value,
176 176 errors=errors.error_dict or {},
177 177 prefix_error=False,
178 178 encoding="UTF-8",
179 179 force_defaults=False)
180 180 except Exception:
181 181 log.exception("Exception creating user group")
182 182 h.flash(_('Error occurred during creation of user group %s') \
183 183 % request.POST.get('users_group_name'), category='error')
184 184
185 185 return redirect(
186 186 url('edit_users_group', user_group_id=user_group.users_group_id))
187 187
188 188 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
189 189 def new(self):
190 190 """GET /user_groups/new: Form to create a new item"""
191 191 # url('new_users_group')
192 192 return render('admin/user_groups/user_group_add.html')
193 193
194 194 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
195 195 @auth.CSRFRequired()
196 196 def update(self, user_group_id):
197 197 """PUT /user_groups/user_group_id: Update an existing item"""
198 198 # Forms posted to this method should contain a hidden field:
199 199 # <input type="hidden" name="_method" value="PUT" />
200 200 # Or using helpers:
201 201 # h.form(url('users_group', user_group_id=ID),
202 202 # method='put')
203 203 # url('users_group', user_group_id=ID)
204 204
205 205 user_group_id = safe_int(user_group_id)
206 206 c.user_group = UserGroup.get_or_404(user_group_id)
207 207 c.active = 'settings'
208 208 self.__load_data(user_group_id)
209 209
210 210 available_members = [safe_unicode(x[0]) for x in c.available_members]
211 211
212 users_group_form = UserGroupForm(edit=True,
213 old_data=c.user_group.get_dict(),
214 available_members=available_members)()
212 users_group_form = UserGroupForm(
213 edit=True, old_data=c.user_group.get_dict(),
214 available_members=available_members, allow_disabled=True)()
215 215
216 216 try:
217 217 form_result = users_group_form.to_python(request.POST)
218 218 UserGroupModel().update(c.user_group, form_result)
219 219 gr = form_result['users_group_name']
220 220 action_logger(c.rhodecode_user,
221 221 'admin_updated_users_group:%s' % gr,
222 222 None, self.ip_addr, self.sa)
223 223 h.flash(_('Updated user group %s') % gr, category='success')
224 224 Session().commit()
225 225 except formencode.Invalid as errors:
226 226 defaults = errors.value
227 227 e = errors.error_dict or {}
228 228
229 229 return htmlfill.render(
230 230 render('admin/user_groups/user_group_edit.html'),
231 231 defaults=defaults,
232 232 errors=e,
233 233 prefix_error=False,
234 234 encoding="UTF-8",
235 235 force_defaults=False)
236 236 except Exception:
237 237 log.exception("Exception during update of user group")
238 238 h.flash(_('Error occurred during update of user group %s')
239 239 % request.POST.get('users_group_name'), category='error')
240 240
241 241 return redirect(url('edit_users_group', user_group_id=user_group_id))
242 242
243 243 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
244 244 @auth.CSRFRequired()
245 245 def delete(self, user_group_id):
246 246 """DELETE /user_groups/user_group_id: Delete an existing item"""
247 247 # Forms posted to this method should contain a hidden field:
248 248 # <input type="hidden" name="_method" value="DELETE" />
249 249 # Or using helpers:
250 250 # h.form(url('users_group', user_group_id=ID),
251 251 # method='delete')
252 252 # url('users_group', user_group_id=ID)
253 253 user_group_id = safe_int(user_group_id)
254 254 c.user_group = UserGroup.get_or_404(user_group_id)
255 255 force = str2bool(request.POST.get('force'))
256 256
257 257 try:
258 258 UserGroupModel().delete(c.user_group, force=force)
259 259 Session().commit()
260 260 h.flash(_('Successfully deleted user group'), category='success')
261 261 except UserGroupAssignedException as e:
262 262 h.flash(str(e), category='error')
263 263 except Exception:
264 264 log.exception("Exception during deletion of user group")
265 265 h.flash(_('An error occurred during deletion of user group'),
266 266 category='error')
267 267 return redirect(url('users_groups'))
268 268
269 269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
270 270 def edit(self, user_group_id):
271 271 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
272 272 # url('edit_users_group', user_group_id=ID)
273 273
274 274 user_group_id = safe_int(user_group_id)
275 275 c.user_group = UserGroup.get_or_404(user_group_id)
276 276 c.active = 'settings'
277 277 self.__load_data(user_group_id)
278 278
279 279 defaults = self.__load_defaults(user_group_id)
280 280
281 281 return htmlfill.render(
282 282 render('admin/user_groups/user_group_edit.html'),
283 283 defaults=defaults,
284 284 encoding="UTF-8",
285 285 force_defaults=False
286 286 )
287 287
288 288 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
289 289 def edit_perms(self, user_group_id):
290 290 user_group_id = safe_int(user_group_id)
291 291 c.user_group = UserGroup.get_or_404(user_group_id)
292 292 c.active = 'perms'
293 293
294 294 defaults = {}
295 295 # fill user group users
296 296 for p in c.user_group.user_user_group_to_perm:
297 297 defaults.update({'u_perm_%s' % p.user.user_id:
298 298 p.permission.permission_name})
299 299
300 300 for p in c.user_group.user_group_user_group_to_perm:
301 301 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
302 302 p.permission.permission_name})
303 303
304 304 return htmlfill.render(
305 305 render('admin/user_groups/user_group_edit.html'),
306 306 defaults=defaults,
307 307 encoding="UTF-8",
308 308 force_defaults=False
309 309 )
310 310
311 311 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
312 312 @auth.CSRFRequired()
313 313 def update_perms(self, user_group_id):
314 314 """
315 315 grant permission for given usergroup
316 316
317 317 :param user_group_id:
318 318 """
319 319 user_group_id = safe_int(user_group_id)
320 320 c.user_group = UserGroup.get_or_404(user_group_id)
321 321 form = UserGroupPermsForm()().to_python(request.POST)
322 322
323 323 if not c.rhodecode_user.is_admin:
324 324 if self._revoke_perms_on_yourself(form):
325 325 msg = _('Cannot change permission for yourself as admin')
326 326 h.flash(msg, category='warning')
327 327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
328 328
329 329 try:
330 330 UserGroupModel().update_permissions(user_group_id,
331 331 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
332 332 except RepoGroupAssignmentError:
333 333 h.flash(_('Target group cannot be the same'), category='error')
334 334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
335 335 #TODO: implement this
336 336 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
337 337 # repo_name, self.ip_addr, self.sa)
338 338 Session().commit()
339 339 h.flash(_('User Group permissions updated'), category='success')
340 340 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
341 341
342 342 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
343 343 def edit_perms_summary(self, user_group_id):
344 344 user_group_id = safe_int(user_group_id)
345 345 c.user_group = UserGroup.get_or_404(user_group_id)
346 346 c.active = 'perms_summary'
347 347 permissions = {
348 348 'repositories': {},
349 349 'repositories_groups': {},
350 350 }
351 351 ugroup_repo_perms = UserGroupRepoToPerm.query()\
352 352 .options(joinedload(UserGroupRepoToPerm.permission))\
353 353 .options(joinedload(UserGroupRepoToPerm.repository))\
354 354 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
355 355 .all()
356 356
357 357 for gr in ugroup_repo_perms:
358 358 permissions['repositories'][gr.repository.repo_name] \
359 359 = gr.permission.permission_name
360 360
361 361 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
362 362 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
363 363 .options(joinedload(UserGroupRepoGroupToPerm.group))\
364 364 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
365 365 .all()
366 366
367 367 for gr in ugroup_group_perms:
368 368 permissions['repositories_groups'][gr.group.group_name] \
369 369 = gr.permission.permission_name
370 370 c.permissions = permissions
371 371 return render('admin/user_groups/user_group_edit.html')
372 372
373 373 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
374 374 def edit_global_perms(self, user_group_id):
375 375 user_group_id = safe_int(user_group_id)
376 376 c.user_group = UserGroup.get_or_404(user_group_id)
377 377 c.active = 'global_perms'
378 378
379 379 c.default_user = User.get_default_user()
380 380 defaults = c.user_group.get_dict()
381 381 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
382 382 defaults.update(c.user_group.get_default_perms())
383 383
384 384 return htmlfill.render(
385 385 render('admin/user_groups/user_group_edit.html'),
386 386 defaults=defaults,
387 387 encoding="UTF-8",
388 388 force_defaults=False
389 389 )
390 390
391 391 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
392 392 @auth.CSRFRequired()
393 393 def update_global_perms(self, user_group_id):
394 394 """PUT /users_perm/user_group_id: Update an existing item"""
395 395 # url('users_group_perm', user_group_id=ID, method='put')
396 396 user_group_id = safe_int(user_group_id)
397 397 user_group = UserGroup.get_or_404(user_group_id)
398 398 c.active = 'global_perms'
399 399
400 400 try:
401 401 # first stage that verifies the checkbox
402 402 _form = UserIndividualPermissionsForm()
403 403 form_result = _form.to_python(dict(request.POST))
404 404 inherit_perms = form_result['inherit_default_permissions']
405 405 user_group.inherit_default_permissions = inherit_perms
406 406 Session().add(user_group)
407 407
408 408 if not inherit_perms:
409 409 # only update the individual ones if we un check the flag
410 410 _form = UserPermissionsForm(
411 411 [x[0] for x in c.repo_create_choices],
412 412 [x[0] for x in c.repo_create_on_write_choices],
413 413 [x[0] for x in c.repo_group_create_choices],
414 414 [x[0] for x in c.user_group_create_choices],
415 415 [x[0] for x in c.fork_choices],
416 416 [x[0] for x in c.inherit_default_permission_choices])()
417 417
418 418 form_result = _form.to_python(dict(request.POST))
419 419 form_result.update({'perm_user_group_id': user_group.users_group_id})
420 420
421 421 PermissionModel().update_user_group_permissions(form_result)
422 422
423 423 Session().commit()
424 424 h.flash(_('User Group global permissions updated successfully'),
425 425 category='success')
426 426
427 427 except formencode.Invalid as errors:
428 428 defaults = errors.value
429 429 c.user_group = user_group
430 430 return htmlfill.render(
431 431 render('admin/user_groups/user_group_edit.html'),
432 432 defaults=defaults,
433 433 errors=errors.error_dict or {},
434 434 prefix_error=False,
435 435 encoding="UTF-8",
436 436 force_defaults=False)
437 437
438 438 except Exception:
439 439 log.exception("Exception during permissions saving")
440 440 h.flash(_('An error occurred during permissions saving'),
441 441 category='error')
442 442
443 443 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
444 444
445 445 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
446 446 def edit_advanced(self, user_group_id):
447 447 user_group_id = safe_int(user_group_id)
448 448 c.user_group = UserGroup.get_or_404(user_group_id)
449 449 c.active = 'advanced'
450 450 c.group_members_obj = sorted(
451 451 (x.user for x in c.user_group.members),
452 452 key=lambda u: u.username.lower())
453 453
454 454 c.group_to_repos = sorted(
455 455 (x.repository for x in c.user_group.users_group_repo_to_perm),
456 456 key=lambda u: u.repo_name.lower())
457 457
458 458 c.group_to_repo_groups = sorted(
459 459 (x.group for x in c.user_group.users_group_repo_group_to_perm),
460 460 key=lambda u: u.group_name.lower())
461 461
462 462 return render('admin/user_groups/user_group_edit.html')
463 463
464 464 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
465 465 def edit_members(self, user_group_id):
466 466 user_group_id = safe_int(user_group_id)
467 467 c.user_group = UserGroup.get_or_404(user_group_id)
468 468 c.active = 'members'
469 469 c.group_members_obj = sorted((x.user for x in c.user_group.members),
470 470 key=lambda u: u.username.lower())
471 471
472 472 group_members = [(x.user_id, x.username) for x in c.group_members_obj]
473 473
474 474 if request.is_xhr:
475 475 return jsonify(lambda *a, **k: {
476 476 'members': group_members
477 477 })
478 478
479 479 c.group_members = group_members
480 480 return render('admin/user_groups/user_group_edit.html')
@@ -1,717 +1,719 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Users crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 from formencode import htmlfill
29 29 from pylons import request, tmpl_context as c, url, config
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34 34 from rhodecode.lib.exceptions import (
35 35 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
36 36 UserOwnsUserGroupsException, UserCreationError)
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib import auth
39 39 from rhodecode.lib.auth import (
40 40 LoginRequired, HasPermissionAllDecorator, AuthUser, generate_auth_token)
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.model.auth_token import AuthTokenModel
43 43
44 44 from rhodecode.model.db import (
45 45 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
46 46 from rhodecode.model.forms import (
47 47 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.meta import Session
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.lib.utils import action_logger
52 52 from rhodecode.lib.ext_json import json
53 53 from rhodecode.lib.utils2 import datetime_to_time, safe_int
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class UsersController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60
61 61 @LoginRequired()
62 62 def __before__(self):
63 63 super(UsersController, self).__before__()
64 64 c.available_permissions = config['available_permissions']
65 65 c.allowed_languages = [
66 66 ('en', 'English (en)'),
67 67 ('de', 'German (de)'),
68 68 ('fr', 'French (fr)'),
69 69 ('it', 'Italian (it)'),
70 70 ('ja', 'Japanese (ja)'),
71 71 ('pl', 'Polish (pl)'),
72 72 ('pt', 'Portuguese (pt)'),
73 73 ('ru', 'Russian (ru)'),
74 74 ('zh', 'Chinese (zh)'),
75 75 ]
76 76 PermissionModel().set_global_permission_choices(c, translator=_)
77 77
78 78 @HasPermissionAllDecorator('hg.admin')
79 79 def index(self):
80 80 """GET /users: All items in the collection"""
81 81 # url('users')
82 82
83 83 from rhodecode.lib.utils import PartialRenderer
84 84 _render = PartialRenderer('data_table/_dt_elements.html')
85 85
86 86 def grav_tmpl(user_email, size):
87 87 return _render("user_gravatar", user_email, size)
88 88
89 89 def username(user_id, username):
90 90 return _render("user_name", user_id, username)
91 91
92 92 def user_actions(user_id, username):
93 93 return _render("user_actions", user_id, username)
94 94
95 95 # json generate
96 96 c.users_list = User.query()\
97 97 .filter(User.username != User.DEFAULT_USER) \
98 98 .all()
99 99
100 100 users_data = []
101 101 for user in c.users_list:
102 102 users_data.append({
103 103 "gravatar": grav_tmpl(user.email, 20),
104 104 "username": h.link_to(
105 105 user.username, h.url('user_profile', username=user.username)),
106 106 "username_raw": user.username,
107 107 "email": user.email,
108 108 "first_name": h.escape(user.name),
109 109 "last_name": h.escape(user.lastname),
110 110 "last_login": h.format_date(user.last_login),
111 111 "last_login_raw": datetime_to_time(user.last_login),
112 112 "last_activity": h.format_date(
113 113 h.time_to_datetime(user.user_data.get('last_activity', 0))),
114 114 "last_activity_raw": user.user_data.get('last_activity', 0),
115 115 "active": h.bool2icon(user.active),
116 116 "active_raw": user.active,
117 117 "admin": h.bool2icon(user.admin),
118 118 "admin_raw": user.admin,
119 119 "extern_type": user.extern_type,
120 120 "extern_name": user.extern_name,
121 121 "action": user_actions(user.user_id, user.username),
122 122 })
123 123
124 124
125 125 c.data = json.dumps(users_data)
126 126 return render('admin/users/users.html')
127 127
128 128 @HasPermissionAllDecorator('hg.admin')
129 129 @auth.CSRFRequired()
130 130 def create(self):
131 131 """POST /users: Create a new item"""
132 132 # url('users')
133 133 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
134 134 user_model = UserModel()
135 135 user_form = UserForm()()
136 136 try:
137 137 form_result = user_form.to_python(dict(request.POST))
138 138 user = user_model.create(form_result)
139 139 Session().flush()
140 140 username = form_result['username']
141 141 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
142 142 None, self.ip_addr, self.sa)
143 143
144 144 user_link = h.link_to(h.escape(username),
145 145 url('edit_user',
146 146 user_id=user.user_id))
147 147 h.flash(h.literal(_('Created user %(user_link)s')
148 148 % {'user_link': user_link}), category='success')
149 149 Session().commit()
150 150 except formencode.Invalid as errors:
151 151 return htmlfill.render(
152 152 render('admin/users/user_add.html'),
153 153 defaults=errors.value,
154 154 errors=errors.error_dict or {},
155 155 prefix_error=False,
156 156 encoding="UTF-8",
157 157 force_defaults=False)
158 158 except UserCreationError as e:
159 159 h.flash(e, 'error')
160 160 except Exception:
161 161 log.exception("Exception creation of user")
162 162 h.flash(_('Error occurred during creation of user %s')
163 163 % request.POST.get('username'), category='error')
164 164 return redirect(url('users'))
165 165
166 166 @HasPermissionAllDecorator('hg.admin')
167 167 def new(self):
168 168 """GET /users/new: Form to create a new item"""
169 169 # url('new_user')
170 170 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
171 171 return render('admin/users/user_add.html')
172 172
173 173 @HasPermissionAllDecorator('hg.admin')
174 174 @auth.CSRFRequired()
175 175 def update(self, user_id):
176 176 """PUT /users/user_id: Update an existing item"""
177 177 # Forms posted to this method should contain a hidden field:
178 178 # <input type="hidden" name="_method" value="PUT" />
179 179 # Or using helpers:
180 180 # h.form(url('update_user', user_id=ID),
181 181 # method='put')
182 182 # url('user', user_id=ID)
183 183 user_id = safe_int(user_id)
184 184 c.user = User.get_or_404(user_id)
185 185 c.active = 'profile'
186 186 c.extern_type = c.user.extern_type
187 187 c.extern_name = c.user.extern_name
188 188 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
189 189 available_languages = [x[0] for x in c.allowed_languages]
190 190 _form = UserForm(edit=True, available_languages=available_languages,
191 191 old_data={'user_id': user_id,
192 192 'email': c.user.email})()
193 193 form_result = {}
194 194 try:
195 195 form_result = _form.to_python(dict(request.POST))
196 196 skip_attrs = ['extern_type', 'extern_name']
197 197 # TODO: plugin should define if username can be updated
198 198 if c.extern_type != "rhodecode":
199 199 # forbid updating username for external accounts
200 200 skip_attrs.append('username')
201 201
202 202 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
203 203 usr = form_result['username']
204 204 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
205 205 None, self.ip_addr, self.sa)
206 206 h.flash(_('User updated successfully'), category='success')
207 207 Session().commit()
208 208 except formencode.Invalid as errors:
209 209 defaults = errors.value
210 210 e = errors.error_dict or {}
211 211
212 212 return htmlfill.render(
213 213 render('admin/users/user_edit.html'),
214 214 defaults=defaults,
215 215 errors=e,
216 216 prefix_error=False,
217 217 encoding="UTF-8",
218 218 force_defaults=False)
219 except UserCreationError as e:
220 h.flash(e, 'error')
219 221 except Exception:
220 222 log.exception("Exception updating user")
221 223 h.flash(_('Error occurred during update of user %s')
222 224 % form_result.get('username'), category='error')
223 225 return redirect(url('edit_user', user_id=user_id))
224 226
225 227 @HasPermissionAllDecorator('hg.admin')
226 228 @auth.CSRFRequired()
227 229 def delete(self, user_id):
228 230 """DELETE /users/user_id: Delete an existing item"""
229 231 # Forms posted to this method should contain a hidden field:
230 232 # <input type="hidden" name="_method" value="DELETE" />
231 233 # Or using helpers:
232 234 # h.form(url('delete_user', user_id=ID),
233 235 # method='delete')
234 236 # url('user', user_id=ID)
235 237 user_id = safe_int(user_id)
236 238 c.user = User.get_or_404(user_id)
237 239
238 240 _repos = c.user.repositories
239 241 _repo_groups = c.user.repository_groups
240 242 _user_groups = c.user.user_groups
241 243
242 244 handle_repos = None
243 245 handle_repo_groups = None
244 246 handle_user_groups = None
245 247 # dummy call for flash of handle
246 248 set_handle_flash_repos = lambda: None
247 249 set_handle_flash_repo_groups = lambda: None
248 250 set_handle_flash_user_groups = lambda: None
249 251
250 252 if _repos and request.POST.get('user_repos'):
251 253 do = request.POST['user_repos']
252 254 if do == 'detach':
253 255 handle_repos = 'detach'
254 256 set_handle_flash_repos = lambda: h.flash(
255 257 _('Detached %s repositories') % len(_repos),
256 258 category='success')
257 259 elif do == 'delete':
258 260 handle_repos = 'delete'
259 261 set_handle_flash_repos = lambda: h.flash(
260 262 _('Deleted %s repositories') % len(_repos),
261 263 category='success')
262 264
263 265 if _repo_groups and request.POST.get('user_repo_groups'):
264 266 do = request.POST['user_repo_groups']
265 267 if do == 'detach':
266 268 handle_repo_groups = 'detach'
267 269 set_handle_flash_repo_groups = lambda: h.flash(
268 270 _('Detached %s repository groups') % len(_repo_groups),
269 271 category='success')
270 272 elif do == 'delete':
271 273 handle_repo_groups = 'delete'
272 274 set_handle_flash_repo_groups = lambda: h.flash(
273 275 _('Deleted %s repository groups') % len(_repo_groups),
274 276 category='success')
275 277
276 278 if _user_groups and request.POST.get('user_user_groups'):
277 279 do = request.POST['user_user_groups']
278 280 if do == 'detach':
279 281 handle_user_groups = 'detach'
280 282 set_handle_flash_user_groups = lambda: h.flash(
281 283 _('Detached %s user groups') % len(_user_groups),
282 284 category='success')
283 285 elif do == 'delete':
284 286 handle_user_groups = 'delete'
285 287 set_handle_flash_user_groups = lambda: h.flash(
286 288 _('Deleted %s user groups') % len(_user_groups),
287 289 category='success')
288 290
289 291 try:
290 292 UserModel().delete(c.user, handle_repos=handle_repos,
291 293 handle_repo_groups=handle_repo_groups,
292 294 handle_user_groups=handle_user_groups)
293 295 Session().commit()
294 296 set_handle_flash_repos()
295 297 set_handle_flash_repo_groups()
296 298 set_handle_flash_user_groups()
297 299 h.flash(_('Successfully deleted user'), category='success')
298 300 except (UserOwnsReposException, UserOwnsRepoGroupsException,
299 301 UserOwnsUserGroupsException, DefaultUserException) as e:
300 302 h.flash(e, category='warning')
301 303 except Exception:
302 304 log.exception("Exception during deletion of user")
303 305 h.flash(_('An error occurred during deletion of user'),
304 306 category='error')
305 307 return redirect(url('users'))
306 308
307 309 @HasPermissionAllDecorator('hg.admin')
308 310 @auth.CSRFRequired()
309 311 def reset_password(self, user_id):
310 312 """
311 313 toggle reset password flag for this user
312 314
313 315 :param user_id:
314 316 """
315 317 user_id = safe_int(user_id)
316 318 c.user = User.get_or_404(user_id)
317 319 try:
318 320 old_value = c.user.user_data.get('force_password_change')
319 321 c.user.update_userdata(force_password_change=not old_value)
320 322 Session().commit()
321 323 if old_value:
322 324 msg = _('Force password change disabled for user')
323 325 else:
324 326 msg = _('Force password change enabled for user')
325 327 h.flash(msg, category='success')
326 328 except Exception:
327 329 log.exception("Exception during password reset for user")
328 330 h.flash(_('An error occurred during password reset for user'),
329 331 category='error')
330 332
331 333 return redirect(url('edit_user_advanced', user_id=user_id))
332 334
333 335 @HasPermissionAllDecorator('hg.admin')
334 336 @auth.CSRFRequired()
335 337 def create_personal_repo_group(self, user_id):
336 338 """
337 339 Create personal repository group for this user
338 340
339 341 :param user_id:
340 342 """
341 343 from rhodecode.model.repo_group import RepoGroupModel
342 344
343 345 user_id = safe_int(user_id)
344 346 c.user = User.get_or_404(user_id)
345 347
346 348 try:
347 349 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {
348 350 'username': c.user.username}
349 351 if not RepoGroup.get_by_group_name(c.user.username):
350 352 RepoGroupModel().create(group_name=c.user.username,
351 353 group_description=desc,
352 354 owner=c.user.username)
353 355
354 356 msg = _('Created repository group `%s`' % (c.user.username,))
355 357 h.flash(msg, category='success')
356 358 except Exception:
357 359 log.exception("Exception during repository group creation")
358 360 msg = _(
359 361 'An error occurred during repository group creation for user')
360 362 h.flash(msg, category='error')
361 363
362 364 return redirect(url('edit_user_advanced', user_id=user_id))
363 365
364 366 @HasPermissionAllDecorator('hg.admin')
365 367 def show(self, user_id):
366 368 """GET /users/user_id: Show a specific item"""
367 369 # url('user', user_id=ID)
368 370 User.get_or_404(-1)
369 371
370 372 @HasPermissionAllDecorator('hg.admin')
371 373 def edit(self, user_id):
372 374 """GET /users/user_id/edit: Form to edit an existing item"""
373 375 # url('edit_user', user_id=ID)
374 376 user_id = safe_int(user_id)
375 377 c.user = User.get_or_404(user_id)
376 378 if c.user.username == User.DEFAULT_USER:
377 379 h.flash(_("You can't edit this user"), category='warning')
378 380 return redirect(url('users'))
379 381
380 382 c.active = 'profile'
381 383 c.extern_type = c.user.extern_type
382 384 c.extern_name = c.user.extern_name
383 385 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
384 386
385 387 defaults = c.user.get_dict()
386 388 defaults.update({'language': c.user.user_data.get('language')})
387 389 return htmlfill.render(
388 390 render('admin/users/user_edit.html'),
389 391 defaults=defaults,
390 392 encoding="UTF-8",
391 393 force_defaults=False)
392 394
393 395 @HasPermissionAllDecorator('hg.admin')
394 396 def edit_advanced(self, user_id):
395 397 user_id = safe_int(user_id)
396 398 user = c.user = User.get_or_404(user_id)
397 399 if user.username == User.DEFAULT_USER:
398 400 h.flash(_("You can't edit this user"), category='warning')
399 401 return redirect(url('users'))
400 402
401 403 c.active = 'advanced'
402 404 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
403 405 c.personal_repo_group = RepoGroup.get_by_group_name(user.username)
404 c.first_admin = User.get_first_admin()
406 c.first_admin = User.get_first_super_admin()
405 407 defaults = user.get_dict()
406 408
407 409 # Interim workaround if the user participated on any pull requests as a
408 410 # reviewer.
409 411 has_review = bool(PullRequestReviewers.query().filter(
410 412 PullRequestReviewers.user_id == user_id).first())
411 413 c.can_delete_user = not has_review
412 414 c.can_delete_user_message = _(
413 415 'The user participates as reviewer in pull requests and '
414 416 'cannot be deleted. You can set the user to '
415 417 '"inactive" instead of deleting it.') if has_review else ''
416 418
417 419 return htmlfill.render(
418 420 render('admin/users/user_edit.html'),
419 421 defaults=defaults,
420 422 encoding="UTF-8",
421 423 force_defaults=False)
422 424
423 425 @HasPermissionAllDecorator('hg.admin')
424 426 def edit_auth_tokens(self, user_id):
425 427 user_id = safe_int(user_id)
426 428 c.user = User.get_or_404(user_id)
427 429 if c.user.username == User.DEFAULT_USER:
428 430 h.flash(_("You can't edit this user"), category='warning')
429 431 return redirect(url('users'))
430 432
431 433 c.active = 'auth_tokens'
432 434 show_expired = True
433 435 c.lifetime_values = [
434 436 (str(-1), _('forever')),
435 437 (str(5), _('5 minutes')),
436 438 (str(60), _('1 hour')),
437 439 (str(60 * 24), _('1 day')),
438 440 (str(60 * 24 * 30), _('1 month')),
439 441 ]
440 442 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
441 443 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
442 444 for x in AuthTokenModel.cls.ROLES]
443 445 c.role_options = [(c.role_values, _("Role"))]
444 446 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
445 447 c.user.user_id, show_expired=show_expired)
446 448 defaults = c.user.get_dict()
447 449 return htmlfill.render(
448 450 render('admin/users/user_edit.html'),
449 451 defaults=defaults,
450 452 encoding="UTF-8",
451 453 force_defaults=False)
452 454
453 455 @HasPermissionAllDecorator('hg.admin')
454 456 @auth.CSRFRequired()
455 457 def add_auth_token(self, user_id):
456 458 user_id = safe_int(user_id)
457 459 c.user = User.get_or_404(user_id)
458 460 if c.user.username == User.DEFAULT_USER:
459 461 h.flash(_("You can't edit this user"), category='warning')
460 462 return redirect(url('users'))
461 463
462 464 lifetime = safe_int(request.POST.get('lifetime'), -1)
463 465 description = request.POST.get('description')
464 466 role = request.POST.get('role')
465 467 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
466 468 Session().commit()
467 469 h.flash(_("Auth token successfully created"), category='success')
468 470 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
469 471
470 472 @HasPermissionAllDecorator('hg.admin')
471 473 @auth.CSRFRequired()
472 474 def delete_auth_token(self, user_id):
473 475 user_id = safe_int(user_id)
474 476 c.user = User.get_or_404(user_id)
475 477 if c.user.username == User.DEFAULT_USER:
476 478 h.flash(_("You can't edit this user"), category='warning')
477 479 return redirect(url('users'))
478 480
479 481 auth_token = request.POST.get('del_auth_token')
480 482 if request.POST.get('del_auth_token_builtin'):
481 483 user = User.get(c.user.user_id)
482 484 if user:
483 485 user.api_key = generate_auth_token(user.username)
484 486 Session().add(user)
485 487 Session().commit()
486 488 h.flash(_("Auth token successfully reset"), category='success')
487 489 elif auth_token:
488 490 AuthTokenModel().delete(auth_token, c.user.user_id)
489 491 Session().commit()
490 492 h.flash(_("Auth token successfully deleted"), category='success')
491 493
492 494 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
493 495
494 496 @HasPermissionAllDecorator('hg.admin')
495 497 def edit_global_perms(self, user_id):
496 498 user_id = safe_int(user_id)
497 499 c.user = User.get_or_404(user_id)
498 500 if c.user.username == User.DEFAULT_USER:
499 501 h.flash(_("You can't edit this user"), category='warning')
500 502 return redirect(url('users'))
501 503
502 504 c.active = 'global_perms'
503 505
504 506 c.default_user = User.get_default_user()
505 507 defaults = c.user.get_dict()
506 508 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
507 509 defaults.update(c.default_user.get_default_perms())
508 510 defaults.update(c.user.get_default_perms())
509 511
510 512 return htmlfill.render(
511 513 render('admin/users/user_edit.html'),
512 514 defaults=defaults,
513 515 encoding="UTF-8",
514 516 force_defaults=False)
515 517
516 518 @HasPermissionAllDecorator('hg.admin')
517 519 @auth.CSRFRequired()
518 520 def update_global_perms(self, user_id):
519 521 """PUT /users_perm/user_id: Update an existing item"""
520 522 # url('user_perm', user_id=ID, method='put')
521 523 user_id = safe_int(user_id)
522 524 user = User.get_or_404(user_id)
523 525 c.active = 'global_perms'
524 526 try:
525 527 # first stage that verifies the checkbox
526 528 _form = UserIndividualPermissionsForm()
527 529 form_result = _form.to_python(dict(request.POST))
528 530 inherit_perms = form_result['inherit_default_permissions']
529 531 user.inherit_default_permissions = inherit_perms
530 532 Session().add(user)
531 533
532 534 if not inherit_perms:
533 535 # only update the individual ones if we un check the flag
534 536 _form = UserPermissionsForm(
535 537 [x[0] for x in c.repo_create_choices],
536 538 [x[0] for x in c.repo_create_on_write_choices],
537 539 [x[0] for x in c.repo_group_create_choices],
538 540 [x[0] for x in c.user_group_create_choices],
539 541 [x[0] for x in c.fork_choices],
540 542 [x[0] for x in c.inherit_default_permission_choices])()
541 543
542 544 form_result = _form.to_python(dict(request.POST))
543 545 form_result.update({'perm_user_id': user.user_id})
544 546
545 547 PermissionModel().update_user_permissions(form_result)
546 548
547 549 Session().commit()
548 550 h.flash(_('User global permissions updated successfully'),
549 551 category='success')
550 552
551 553 Session().commit()
552 554 except formencode.Invalid as errors:
553 555 defaults = errors.value
554 556 c.user = user
555 557 return htmlfill.render(
556 558 render('admin/users/user_edit.html'),
557 559 defaults=defaults,
558 560 errors=errors.error_dict or {},
559 561 prefix_error=False,
560 562 encoding="UTF-8",
561 563 force_defaults=False)
562 564 except Exception:
563 565 log.exception("Exception during permissions saving")
564 566 h.flash(_('An error occurred during permissions saving'),
565 567 category='error')
566 568 return redirect(url('edit_user_global_perms', user_id=user_id))
567 569
568 570 @HasPermissionAllDecorator('hg.admin')
569 571 def edit_perms_summary(self, user_id):
570 572 user_id = safe_int(user_id)
571 573 c.user = User.get_or_404(user_id)
572 574 if c.user.username == User.DEFAULT_USER:
573 575 h.flash(_("You can't edit this user"), category='warning')
574 576 return redirect(url('users'))
575 577
576 578 c.active = 'perms_summary'
577 579 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
578 580
579 581 return render('admin/users/user_edit.html')
580 582
581 583 @HasPermissionAllDecorator('hg.admin')
582 584 def edit_emails(self, user_id):
583 585 user_id = safe_int(user_id)
584 586 c.user = User.get_or_404(user_id)
585 587 if c.user.username == User.DEFAULT_USER:
586 588 h.flash(_("You can't edit this user"), category='warning')
587 589 return redirect(url('users'))
588 590
589 591 c.active = 'emails'
590 592 c.user_email_map = UserEmailMap.query() \
591 593 .filter(UserEmailMap.user == c.user).all()
592 594
593 595 defaults = c.user.get_dict()
594 596 return htmlfill.render(
595 597 render('admin/users/user_edit.html'),
596 598 defaults=defaults,
597 599 encoding="UTF-8",
598 600 force_defaults=False)
599 601
600 602 @HasPermissionAllDecorator('hg.admin')
601 603 @auth.CSRFRequired()
602 604 def add_email(self, user_id):
603 605 """POST /user_emails:Add an existing item"""
604 606 # url('user_emails', user_id=ID, method='put')
605 607 user_id = safe_int(user_id)
606 608 c.user = User.get_or_404(user_id)
607 609
608 610 email = request.POST.get('new_email')
609 611 user_model = UserModel()
610 612
611 613 try:
612 614 user_model.add_extra_email(user_id, email)
613 615 Session().commit()
614 616 h.flash(_("Added new email address `%s` for user account") % email,
615 617 category='success')
616 618 except formencode.Invalid as error:
617 619 msg = error.error_dict['email']
618 620 h.flash(msg, category='error')
619 621 except Exception:
620 622 log.exception("Exception during email saving")
621 623 h.flash(_('An error occurred during email saving'),
622 624 category='error')
623 625 return redirect(url('edit_user_emails', user_id=user_id))
624 626
625 627 @HasPermissionAllDecorator('hg.admin')
626 628 @auth.CSRFRequired()
627 629 def delete_email(self, user_id):
628 630 """DELETE /user_emails_delete/user_id: Delete an existing item"""
629 631 # url('user_emails_delete', user_id=ID, method='delete')
630 632 user_id = safe_int(user_id)
631 633 c.user = User.get_or_404(user_id)
632 634 email_id = request.POST.get('del_email_id')
633 635 user_model = UserModel()
634 636 user_model.delete_extra_email(user_id, email_id)
635 637 Session().commit()
636 638 h.flash(_("Removed email address from user account"), category='success')
637 639 return redirect(url('edit_user_emails', user_id=user_id))
638 640
639 641 @HasPermissionAllDecorator('hg.admin')
640 642 def edit_ips(self, user_id):
641 643 user_id = safe_int(user_id)
642 644 c.user = User.get_or_404(user_id)
643 645 if c.user.username == User.DEFAULT_USER:
644 646 h.flash(_("You can't edit this user"), category='warning')
645 647 return redirect(url('users'))
646 648
647 649 c.active = 'ips'
648 650 c.user_ip_map = UserIpMap.query() \
649 651 .filter(UserIpMap.user == c.user).all()
650 652
651 653 c.inherit_default_ips = c.user.inherit_default_permissions
652 654 c.default_user_ip_map = UserIpMap.query() \
653 655 .filter(UserIpMap.user == User.get_default_user()).all()
654 656
655 657 defaults = c.user.get_dict()
656 658 return htmlfill.render(
657 659 render('admin/users/user_edit.html'),
658 660 defaults=defaults,
659 661 encoding="UTF-8",
660 662 force_defaults=False)
661 663
662 664 @HasPermissionAllDecorator('hg.admin')
663 665 @auth.CSRFRequired()
664 666 def add_ip(self, user_id):
665 667 """POST /user_ips:Add an existing item"""
666 668 # url('user_ips', user_id=ID, method='put')
667 669
668 670 user_id = safe_int(user_id)
669 671 c.user = User.get_or_404(user_id)
670 672 user_model = UserModel()
671 673 try:
672 674 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
673 675 except Exception as e:
674 676 ip_list = []
675 677 log.exception("Exception during ip saving")
676 678 h.flash(_('An error occurred during ip saving:%s' % (e,)),
677 679 category='error')
678 680
679 681 desc = request.POST.get('description')
680 682 added = []
681 683 for ip in ip_list:
682 684 try:
683 685 user_model.add_extra_ip(user_id, ip, desc)
684 686 Session().commit()
685 687 added.append(ip)
686 688 except formencode.Invalid as error:
687 689 msg = error.error_dict['ip']
688 690 h.flash(msg, category='error')
689 691 except Exception:
690 692 log.exception("Exception during ip saving")
691 693 h.flash(_('An error occurred during ip saving'),
692 694 category='error')
693 695 if added:
694 696 h.flash(
695 697 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
696 698 category='success')
697 699 if 'default_user' in request.POST:
698 700 return redirect(url('admin_permissions_ips'))
699 701 return redirect(url('edit_user_ips', user_id=user_id))
700 702
701 703 @HasPermissionAllDecorator('hg.admin')
702 704 @auth.CSRFRequired()
703 705 def delete_ip(self, user_id):
704 706 """DELETE /user_ips_delete/user_id: Delete an existing item"""
705 707 # url('user_ips_delete', user_id=ID, method='delete')
706 708 user_id = safe_int(user_id)
707 709 c.user = User.get_or_404(user_id)
708 710
709 711 ip_id = request.POST.get('del_ip_id')
710 712 user_model = UserModel()
711 713 user_model.delete_extra_ip(user_id, ip_id)
712 714 Session().commit()
713 715 h.flash(_("Removed ip address from user whitelist"), category='success')
714 716
715 717 if 'default_user' in request.POST:
716 718 return redirect(url('admin_permissions_ips'))
717 719 return redirect(url('edit_user_ips', user_id=user_id))
@@ -1,277 +1,289 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Home controller for RhodeCode Enterprise
23 23 """
24 24
25 25 import logging
26 26 import time
27 27 import re
28 28
29 29 from pylons import tmpl_context as c, request, url, config
30 30 from pylons.i18n.translation import _
31 31 from sqlalchemy.sql import func
32 32
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, HasPermissionAllDecorator, AuthUser,
35 35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
36 36 from rhodecode.lib.base import BaseController, render
37 37 from rhodecode.lib.index import searcher_from_config
38 38 from rhodecode.lib.ext_json import json
39 39 from rhodecode.lib.utils import jsonify
40 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.utils2 import safe_unicode, str2bool
41 41 from rhodecode.model.db import Repository, RepoGroup
42 42 from rhodecode.model.repo import RepoModel
43 43 from rhodecode.model.repo_group import RepoGroupModel
44 44 from rhodecode.model.scm import RepoList, RepoGroupList
45 45
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class HomeController(BaseController):
51 51 def __before__(self):
52 52 super(HomeController, self).__before__()
53 53
54 54 def ping(self):
55 55 """
56 56 Ping, doesn't require login, good for checking out the platform
57 57 """
58 58 instance_id = getattr(c, 'rhodecode_instanceid', '')
59 59 return 'pong[%s] => %s' % (instance_id, self.ip_addr,)
60 60
61 61 @LoginRequired()
62 62 @HasPermissionAllDecorator('hg.admin')
63 63 def error_test(self):
64 64 """
65 65 Test exception handling and emails on errors
66 66 """
67 67 class TestException(Exception):
68 68 pass
69 69
70 70 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
71 71 % (c.rhodecode_name, time.time()))
72 72 raise TestException(msg)
73 73
74 74 def _get_groups_and_repos(self, repo_group_id=None):
75 75 # repo groups groups
76 76 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
77 77 _perms = ['group.read', 'group.write', 'group.admin']
78 78 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
79 79 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
80 80 repo_group_list=repo_group_list_acl, admin=False)
81 81
82 82 # repositories
83 83 repo_list = Repository.get_all_repos(group_id=repo_group_id)
84 84 _perms = ['repository.read', 'repository.write', 'repository.admin']
85 85 repo_list_acl = RepoList(repo_list, perm_set=_perms)
86 86 repo_data = RepoModel().get_repos_as_dict(
87 87 repo_list=repo_list_acl, admin=False)
88 88
89 89 return repo_data, repo_group_data
90 90
91 91 @LoginRequired()
92 92 def index(self):
93 93 c.repo_group = None
94 94
95 95 repo_data, repo_group_data = self._get_groups_and_repos()
96 96 # json used to render the grids
97 97 c.repos_data = json.dumps(repo_data)
98 98 c.repo_groups_data = json.dumps(repo_group_data)
99 99
100 100 return render('/index.html')
101 101
102 102 @LoginRequired()
103 103 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
104 104 'group.admin')
105 105 def index_repo_group(self, group_name):
106 106 """GET /repo_group_name: Show a specific item"""
107 107 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
108 108 repo_data, repo_group_data = self._get_groups_and_repos(
109 109 c.repo_group.group_id)
110 110
111 111 # json used to render the grids
112 112 c.repos_data = json.dumps(repo_data)
113 113 c.repo_groups_data = json.dumps(repo_group_data)
114 114
115 115 return render('index_repo_group.html')
116 116
117 117 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
118 118 query = Repository.query()\
119 119 .order_by(func.length(Repository.repo_name))\
120 120 .order_by(Repository.repo_name)
121 121
122 122 if repo_type:
123 123 query = query.filter(Repository.repo_type == repo_type)
124 124
125 125 if name_contains:
126 126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
127 127 query = query.filter(
128 128 Repository.repo_name.ilike(ilike_expression))
129 129 query = query.limit(limit)
130 130
131 131 all_repos = query.all()
132 132 repo_iter = self.scm_model.get_repos(all_repos)
133 133 return [
134 134 {
135 135 'id': obj['name'],
136 136 'text': obj['name'],
137 137 'type': 'repo',
138 138 'obj': obj['dbrepo'],
139 139 'url': url('summary_home', repo_name=obj['name'])
140 140 }
141 141 for obj in repo_iter]
142 142
143 143 def _get_repo_group_list(self, name_contains=None, limit=20):
144 144 query = RepoGroup.query()\
145 145 .order_by(func.length(RepoGroup.group_name))\
146 146 .order_by(RepoGroup.group_name)
147 147
148 148 if name_contains:
149 149 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
150 150 query = query.filter(
151 151 RepoGroup.group_name.ilike(ilike_expression))
152 152 query = query.limit(limit)
153 153
154 154 all_groups = query.all()
155 155 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
156 156 return [
157 157 {
158 158 'id': obj.group_name,
159 159 'text': obj.group_name,
160 160 'type': 'group',
161 161 'obj': {},
162 162 'url': url('repo_group_home', group_name=obj.group_name)
163 163 }
164 164 for obj in repo_groups_iter]
165 165
166 166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
167 167 if not hash_starts_with or len(hash_starts_with) < 3:
168 168 return []
169 169
170 170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
171 171
172 172 if len(commit_hashes) != 1:
173 173 return []
174 174
175 175 commit_hash_prefix = commit_hashes[0]
176 176
177 177 auth_user = AuthUser(
178 178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
179 179 searcher = searcher_from_config(config)
180 180 result = searcher.search(
181 181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user)
182 182
183 183 return [
184 184 {
185 185 'id': entry['commit_id'],
186 186 'text': entry['commit_id'],
187 187 'type': 'commit',
188 188 'obj': {'repo': entry['repository']},
189 189 'url': url('changeset_home',
190 190 repo_name=entry['repository'], revision=entry['commit_id'])
191 191 }
192 192 for entry in result['results']]
193 193
194 194 @LoginRequired()
195 195 @XHRRequired()
196 196 @jsonify
197 197 def goto_switcher_data(self):
198 198 query = request.GET.get('query')
199 199 log.debug('generating goto switcher list, query %s', query)
200 200
201 201 res = []
202 202 repo_groups = self._get_repo_group_list(query)
203 203 if repo_groups:
204 204 res.append({
205 205 'text': _('Groups'),
206 206 'children': repo_groups
207 207 })
208 208
209 209 repos = self._get_repo_list(query)
210 210 if repos:
211 211 res.append({
212 212 'text': _('Repositories'),
213 213 'children': repos
214 214 })
215 215
216 216 commits = self._get_hash_commit_list(query)
217 217 if commits:
218 218 unique_repos = {}
219 219 for commit in commits:
220 220 unique_repos.setdefault(commit['obj']['repo'], []
221 221 ).append(commit)
222 222
223 223 for repo in unique_repos:
224 224 res.append({
225 225 'text': _('Commits in %(repo)s') % {'repo': repo},
226 226 'children': unique_repos[repo]
227 227 })
228 228
229 229 data = {
230 230 'more': False,
231 231 'results': res
232 232 }
233 233 return data
234 234
235 235 @LoginRequired()
236 236 @XHRRequired()
237 237 @jsonify
238 238 def repo_list_data(self):
239 239 query = request.GET.get('query')
240 240 repo_type = request.GET.get('repo_type')
241 241 log.debug('generating repo list, query:%s', query)
242 242
243 243 res = []
244 244 repos = self._get_repo_list(query, repo_type=repo_type)
245 245 if repos:
246 246 res.append({
247 247 'text': _('Repositories'),
248 248 'children': repos
249 249 })
250 250
251 251 data = {
252 252 'more': False,
253 253 'results': res
254 254 }
255 255 return data
256 256
257 257 @LoginRequired()
258 258 @XHRRequired()
259 259 @jsonify
260 260 def user_autocomplete_data(self):
261 261 query = request.GET.get('query')
262 active = str2bool(request.GET.get('active') or True)
262 263
263 264 repo_model = RepoModel()
264 _users = repo_model.get_users(name_contains=query)
265 _users = repo_model.get_users(
266 name_contains=query, only_active=active)
265 267
266 268 if request.GET.get('user_groups'):
267 269 # extend with user groups
268 _user_groups = repo_model.get_user_groups(name_contains=query)
270 _user_groups = repo_model.get_user_groups(
271 name_contains=query, only_active=active)
269 272 _users = _users + _user_groups
270 273
271 274 return {'suggestions': _users}
272 275
273 276 @LoginRequired()
274 277 @XHRRequired()
275 278 @jsonify
276 279 def user_group_autocomplete_data(self):
277 return {'suggestions': []}
280 query = request.GET.get('query')
281 active = str2bool(request.GET.get('active') or True)
282
283 repo_model = RepoModel()
284 _user_groups = repo_model.get_user_groups(
285 name_contains=query, only_active=active)
286 _user_groups = _user_groups
287
288 return {'suggestions': _user_groups}
289
@@ -1,31 +1,53 b''
1 1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
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 Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 from zope.interface import implementer
20 from rhodecode.interfaces import IUserRegistered
20 from rhodecode.interfaces import (
21 IUserRegistered, IUserPreCreate, IUserPreUpdate)
21 22
22 23
23 24 @implementer(IUserRegistered)
24 25 class UserRegistered(object):
25 26 """
26 27 An instance of this class is emitted as an :term:`event` whenever a user
27 28 account is registered.
28 29 """
29 30 def __init__(self, user, session):
30 31 self.user = user
31 32 self.session = session
33
34
35 @implementer(IUserPreCreate)
36 class UserPreCreate(object):
37 """
38 An instance of this class is emitted as an :term:`event` before a new user
39 object is created.
40 """
41 def __init__(self, user_data):
42 self.user_data = user_data
43
44
45 @implementer(IUserPreUpdate)
46 class UserPreUpdate(object):
47 """
48 An instance of this class is emitted as an :term:`event` before a user
49 object is updated.
50 """
51 def __init__(self, user, user_data):
52 self.user = user
53 self.user_data = user_data
This diff has been collapsed as it changes many lines, (2626 lines changed) Show them Hide them
@@ -1,7772 +1,7786 b''
1 1 # Translations template for rhodecode-enterprise-ce.
2 2 # Copyright (C) 2016 RhodeCode GmbH
3 3 # This file is distributed under the same license as the rhodecode-enterprise-ce project.
4 4 # FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
5 5 #
6 6 #, fuzzy
7 7 msgid ""
8 8 msgstr ""
9 "Project-Id-Version: rhodecode-enterprise-ce 4.0.0\n"
9 "Project-Id-Version: rhodecode-enterprise-ce 4.2.0\n"
10 10 "Report-Msgid-Bugs-To: marcin@rhodecode.com\n"
11 "POT-Creation-Date: 2016-05-22 18:01+0000\n"
11 "POT-Creation-Date: 2016-06-28 10:55+0000\n"
12 12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 14 "Language-Team: LANGUAGE <LL@li.org>\n"
15 15 "MIME-Version: 1.0\n"
16 16 "Content-Type: text/plain; charset=utf-8\n"
17 17 "Content-Transfer-Encoding: 8bit\n"
18 18 "Generated-By: Babel 1.3\n"
19 19
20 #: rhodecode/authentication/routes.py:61
21 #: rhodecode/controllers/admin/settings.py:825
20 #: rhodecode/authentication/routes.py:60
21 #: rhodecode/controllers/admin/settings.py:844
22 22 #: rhodecode/templates/admin/permissions/permissions.html:36
23 23 msgid "Global"
24 24 msgstr ""
25 25
26 #: rhodecode/authentication/schema.py:35
27 msgid "Enable or disable this authentication plugin."
28 msgstr ""
29
26 30 #: rhodecode/authentication/schema.py:37
27 msgid "Enable or disable this authentication plugin."
28 msgstr ""
29
30 #: rhodecode/authentication/schema.py:39
31 31 msgid "Enabled"
32 32 msgstr ""
33 33
34 #: rhodecode/authentication/schema.py:45
34 #: rhodecode/authentication/schema.py:43
35 35 msgid "Amount of seconds to cache the authentication call for this plugin. Useful for long calls like LDAP to improve the responsiveness of the authentication system (0 means disabled)."
36 36 msgstr ""
37 37
38 #: rhodecode/authentication/schema.py:50
38 #: rhodecode/authentication/schema.py:48
39 39 msgid "Auth Cache TTL"
40 40 msgstr ""
41 41
42 #: rhodecode/authentication/views.py:108
42 #: rhodecode/authentication/views.py:88
43 43 msgid "Errors exist when saving plugin settings. Please check the form inputs."
44 44 msgstr ""
45 45
46 #: rhodecode/authentication/views.py:131 rhodecode/authentication/views.py:200
46 #: rhodecode/authentication/views.py:101 rhodecode/authentication/views.py:170
47 47 msgid "Auth settings updated successfully."
48 48 msgstr ""
49 49
50 #: rhodecode/authentication/views.py:205
50 #: rhodecode/authentication/views.py:175
51 51 msgid "Errors exist when saving plugin setting. Please check the form inputs."
52 52 msgstr ""
53 53
54 #: rhodecode/authentication/views.py:215
54 #: rhodecode/authentication/views.py:185
55 55 msgid "Error occurred during update of auth settings."
56 56 msgstr ""
57 57
58 #: rhodecode/authentication/plugins/auth_crowd.py:61
59 msgid "The FQDN or IP of the Atlassian CROWD Server"
60 msgstr ""
61
62 #: rhodecode/authentication/plugins/auth_crowd.py:63
63 msgid "Host"
64 msgstr ""
65
66 #: rhodecode/authentication/plugins/auth_crowd.py:68
67 msgid "The Port in use by the Atlassian CROWD Server"
68 msgstr ""
69
70 #: rhodecode/authentication/plugins/auth_crowd.py:70
71 #: rhodecode/authentication/plugins/auth_ldap.py:84
72 msgid "Port"
73 msgstr ""
74
75 #: rhodecode/authentication/plugins/auth_crowd.py:76
76 msgid "The Application Name to authenticate to CROWD"
77 msgstr ""
78
79 #: rhodecode/authentication/plugins/auth_crowd.py:78
80 msgid "Application Name"
81 msgstr ""
82
83 #: rhodecode/authentication/plugins/auth_crowd.py:83
84 msgid "The password to authenticate to CROWD"
85 msgstr ""
86
87 #: rhodecode/authentication/plugins/auth_crowd.py:85
88 msgid "Application Password"
89 msgstr ""
90
91 #: rhodecode/authentication/plugins/auth_crowd.py:90
92 msgid "A comma separated list of group names that identify users as RhodeCode Administrators"
93 msgstr ""
94
95 #: rhodecode/authentication/plugins/auth_crowd.py:94
96 msgid "Admin Groups"
97 msgstr ""
98
99 #: rhodecode/authentication/plugins/auth_crowd.py:216
100 msgid "CROWD"
101 msgstr ""
102
103 #: rhodecode/authentication/plugins/auth_headers.py:55
104 msgid "Header to extract the user from"
105 msgstr ""
106
107 #: rhodecode/authentication/plugins/auth_headers.py:57
108 msgid "Header"
109 msgstr ""
110
111 #: rhodecode/authentication/plugins/auth_headers.py:62
112 msgid "Header to extract the user from when main one fails"
113 msgstr ""
114
115 #: rhodecode/authentication/plugins/auth_headers.py:64
116 msgid "Fallback header"
117 msgstr ""
118
119 #: rhodecode/authentication/plugins/auth_headers.py:69
120 msgid "Perform cleaning of user, if passed user has @ in username then first part before @ is taken. If there's \\ in the username only the part after \\ is taken"
121 msgstr ""
122
123 #: rhodecode/authentication/plugins/auth_headers.py:74
124 msgid "Clean username"
125 msgstr ""
126
127 #: rhodecode/authentication/plugins/auth_headers.py:99
128 msgid "Headers"
129 msgstr ""
130
131 #: rhodecode/authentication/plugins/auth_jasig_cas.py:63
132 msgid "The url of the Jasig CAS REST service"
133 msgstr ""
134
135 #: rhodecode/authentication/plugins/auth_jasig_cas.py:65
136 #: rhodecode/templates/admin/gists/show.html:21
137 msgid "URL"
138 msgstr ""
139
140 #: rhodecode/authentication/plugins/auth_jasig_cas.py:93
141 msgid "Jasig-CAS"
142 msgstr ""
143
144 #: rhodecode/authentication/plugins/auth_ldap.py:75
145 msgid "Host of the LDAP Server"
146 msgstr ""
147
148 #: rhodecode/authentication/plugins/auth_ldap.py:77
149 msgid "LDAP Host"
150 msgstr ""
151
152 #: rhodecode/authentication/plugins/auth_ldap.py:82
153 msgid "Port that the LDAP server is listening on"
154 msgstr ""
155
156 #: rhodecode/authentication/plugins/auth_ldap.py:90
157 msgid "User to connect to LDAP"
158 msgstr ""
159
160 #: rhodecode/authentication/plugins/auth_ldap.py:93
161 msgid "Account"
162 msgstr ""
163
164 #: rhodecode/authentication/plugins/auth_ldap.py:98
165 msgid "Password to connect to LDAP"
166 msgstr ""
167
168 #: rhodecode/authentication/plugins/auth_ldap.py:101
169 #: rhodecode/templates/login.html:50 rhodecode/templates/register.html:48
170 #: rhodecode/templates/admin/my_account/my_account.html:30
171 #: rhodecode/templates/admin/users/user_add.html:44
172 #: rhodecode/templates/base/base.html:314
173 #: rhodecode/templates/debug_style/login.html:45
174 msgid "Password"
175 msgstr ""
176
177 #: rhodecode/authentication/plugins/auth_ldap.py:106
178 msgid "TLS Type"
179 msgstr ""
180
181 #: rhodecode/authentication/plugins/auth_ldap.py:107
182 msgid "Connection Security"
183 msgstr ""
184
185 #: rhodecode/authentication/plugins/auth_ldap.py:113
186 msgid "Require Cert over TLS?"
187 msgstr ""
188
189 #: rhodecode/authentication/plugins/auth_ldap.py:114
190 msgid "Certificate Checks"
191 msgstr ""
192
193 #: rhodecode/authentication/plugins/auth_ldap.py:120
194 msgid "Base DN to search (e.g., dc=mydomain,dc=com)"
195 msgstr ""
196
197 #: rhodecode/authentication/plugins/auth_ldap.py:123
198 msgid "Base DN"
199 msgstr ""
200
201 #: rhodecode/authentication/plugins/auth_ldap.py:128
202 msgid "Filter to narrow results (e.g., ou=Users, etc)"
203 msgstr ""
204
205 #: rhodecode/authentication/plugins/auth_ldap.py:131
206 msgid "LDAP Search Filter"
207 msgstr ""
208
209 #: rhodecode/authentication/plugins/auth_ldap.py:136
210 msgid "How deep to search LDAP"
211 msgstr ""
212
213 #: rhodecode/authentication/plugins/auth_ldap.py:137
214 msgid "LDAP Search Scope"
215 msgstr ""
216
217 #: rhodecode/authentication/plugins/auth_ldap.py:143
218 msgid "LDAP Attribute to map to user name"
219 msgstr ""
220
221 #: rhodecode/authentication/plugins/auth_ldap.py:144
222 msgid "The LDAP Login attribute of the CN must be specified"
223 msgstr ""
224
225 #: rhodecode/authentication/plugins/auth_ldap.py:146
226 msgid "Login Attribute"
227 msgstr ""
228
229 #: rhodecode/authentication/plugins/auth_ldap.py:151
230 msgid "LDAP Attribute to map to first name"
231 msgstr ""
232
233 #: rhodecode/authentication/plugins/auth_ldap.py:154
234 msgid "First Name Attribute"
235 msgstr ""
236
237 #: rhodecode/authentication/plugins/auth_ldap.py:159
238 msgid "LDAP Attribute to map to last name"
239 msgstr ""
240
241 #: rhodecode/authentication/plugins/auth_ldap.py:162
242 msgid "Last Name Attribute"
243 msgstr ""
244
245 #: rhodecode/authentication/plugins/auth_ldap.py:167
246 msgid "LDAP Attribute to map to email address"
247 msgstr ""
248
249 #: rhodecode/authentication/plugins/auth_ldap.py:170
250 msgid "Email Attribute"
251 msgstr ""
252
253 #: rhodecode/authentication/plugins/auth_ldap.py:348
254 msgid "LDAP"
255 msgstr ""
256
257 #: rhodecode/authentication/plugins/auth_pam.py:60
258 msgid "PAM service name to use for authentication."
259 msgstr ""
260
261 #: rhodecode/authentication/plugins/auth_pam.py:62
262 msgid "PAM service name"
263 msgstr ""
264
265 #: rhodecode/authentication/plugins/auth_pam.py:67
266 msgid "Regular expression for extracting user name/email etc. from Unix userinfo."
267 msgstr ""
268
269 #: rhodecode/authentication/plugins/auth_pam.py:70
270 msgid "Gecos Regex"
271 msgstr ""
272
273 #: rhodecode/authentication/plugins/auth_pam.py:98
274 msgid "PAM"
275 msgstr ""
276
277 #: rhodecode/authentication/plugins/auth_rhodecode.py:68
278 msgid "Rhodecode"
279 msgstr ""
280
281 #: rhodecode/authentication/plugins/auth_token.py:71
282 msgid "Rhodecode Token Auth"
283 msgstr ""
284
58 285 #: rhodecode/controllers/changelog.py:90 rhodecode/controllers/compare.py:63
59 286 #: rhodecode/controllers/pullrequests.py:279
60 287 msgid "There are no commits yet"
61 288 msgstr ""
62 289
63 290 #: rhodecode/controllers/changeset.py:77
64 291 #: rhodecode/templates/files/diff_2way.html:75
65 292 msgid "Show whitespace"
66 293 msgstr ""
67 294
68 295 #: rhodecode/controllers/changeset.py:78
69 296 msgid "Show whitespace for all diffs"
70 297 msgstr ""
71 298
72 299 #: rhodecode/controllers/changeset.py:84
73 300 #: rhodecode/templates/files/diff_2way.html:74
74 301 msgid "Ignore whitespace"
75 302 msgstr ""
76 303
77 304 #: rhodecode/controllers/changeset.py:85
78 305 msgid "Ignore whitespace for all diffs"
79 306 msgstr ""
80 307
81 308 #: rhodecode/controllers/changeset.py:141
82 309 msgid "Increase context"
83 310 msgstr ""
84 311
85 312 #: rhodecode/controllers/changeset.py:142
86 313 msgid "Increase context for all diffs"
87 314 msgstr ""
88 315
89 316 #: rhodecode/controllers/changeset.py:181 rhodecode/controllers/files.py:104
90 317 #: rhodecode/controllers/files.py:125
91 318 msgid "No such commit exists for this repository"
92 319 msgstr ""
93 320
94 321 #: rhodecode/controllers/changeset.py:335
95 #: rhodecode/controllers/pullrequests.py:744
96 #: rhodecode/model/pull_request.py:828
322 #: rhodecode/controllers/pullrequests.py:746
323 #: rhodecode/model/pull_request.py:836
97 324 #, python-format
98 325 msgid "Status change %(transition_icon)s %(status)s"
99 326 msgstr ""
100 327
101 328 #: rhodecode/controllers/changeset.py:372
102 329 msgid "Changing the status of a commit associated with a closed pull request is not allowed"
103 330 msgstr ""
104 331
105 332 #: rhodecode/controllers/compare.py:87
106 333 msgid "Select commit"
107 334 msgstr ""
108 335
109 #: rhodecode/controllers/compare.py:142
336 #: rhodecode/controllers/compare.py:139
110 337 #, python-format
111 338 msgid "Could not find the original repo: %(repo)s"
112 339 msgstr ""
113 340
114 #: rhodecode/controllers/compare.py:150
341 #: rhodecode/controllers/compare.py:147
115 342 #, python-format
116 343 msgid "Could not find the other repo: %(repo)s"
117 344 msgstr ""
118 345
119 #: rhodecode/controllers/compare.py:159
346 #: rhodecode/controllers/compare.py:156
120 347 msgid "The comparison of two different kinds of remote repos is not available"
121 348 msgstr ""
122 349
123 #: rhodecode/controllers/compare.py:186
350 #: rhodecode/controllers/compare.py:190
124 351 msgid "Could not compare repos with different large file settings"
125 352 msgstr ""
126 353
127 #: rhodecode/controllers/compare.py:223
354 #: rhodecode/controllers/compare.py:226
128 355 #, python-format
129 356 msgid "Repositories unrelated. Cannot compare commit %(commit1)s from repository %(repo1)s with commit %(commit2)s from repository %(repo2)s."
130 357 msgstr ""
131 358
132 #: rhodecode/controllers/error.py:85 rhodecode/controllers/error.py:136
133 msgid "Home page"
134 msgstr ""
135
136 #: rhodecode/controllers/error.py:114
137 msgid "The request could not be understood by the server due to malformed syntax."
138 msgstr ""
139
140 #: rhodecode/controllers/error.py:117
141 msgid "Unauthorized access to resource"
142 msgstr ""
143
144 #: rhodecode/controllers/error.py:119
145 msgid "You don't have permission to view this page"
146 msgstr ""
147
148 #: rhodecode/controllers/error.py:121
149 msgid "The resource could not be found"
150 msgstr ""
151
152 #: rhodecode/controllers/error.py:123
153 msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
154 msgstr ""
155
156 #: rhodecode/controllers/error.py:128
157 msgid "VCS Server Required"
158 msgstr ""
159
160 #: rhodecode/controllers/error.py:129
161 msgid "A VCS Server is required for this action. There is currently no VCS Server configured."
162 msgstr ""
163
164 359 #: rhodecode/controllers/feed.py:70
165 360 #, python-format
166 361 msgid "Changes on %s repository"
167 362 msgstr ""
168 363
169 364 #: rhodecode/controllers/feed.py:71
170 365 #, python-format
171 366 msgid "%s %s feed"
172 367 msgstr ""
173 368
174 369 #: rhodecode/controllers/files.py:96
175 370 msgid "Click here to add a new file."
176 371 msgstr ""
177 372
178 373 #: rhodecode/controllers/files.py:101
179 374 #, python-format
180 375 msgid "There are no files yet. %s"
181 376 msgstr ""
182 377
183 378 #: rhodecode/controllers/files.py:390 rhodecode/controllers/files.py:443
184 379 #: rhodecode/controllers/files.py:474 rhodecode/controllers/files.py:549
185 380 #: rhodecode/controllers/files.py:594 rhodecode/controllers/files.py:685
186 381 #, python-format
187 382 msgid "This repository has been locked by %s on %s"
188 383 msgstr ""
189 384
190 385 #: rhodecode/controllers/files.py:398 rhodecode/controllers/files.py:451
191 386 msgid "You can only delete files with revision being a valid branch "
192 387 msgstr ""
193 388
194 389 #: rhodecode/controllers/files.py:407 rhodecode/controllers/files.py:460
195 390 #, python-format
196 391 msgid "Deleted file %s via RhodeCode Enterprise"
197 392 msgstr ""
198 393
199 394 #: rhodecode/controllers/files.py:427
200 395 #, python-format
201 396 msgid "Successfully deleted file %s"
202 397 msgstr ""
203 398
204 399 #: rhodecode/controllers/files.py:430 rhodecode/controllers/files.py:536
205 400 #: rhodecode/controllers/files.py:673
206 401 msgid "Error occurred during commit"
207 402 msgstr ""
208 403
209 404 #: rhodecode/controllers/files.py:482 rhodecode/controllers/files.py:557
210 405 msgid "You can only edit files with revision being a valid branch "
211 406 msgstr ""
212 407
213 408 #: rhodecode/controllers/files.py:494 rhodecode/controllers/files.py:569
214 409 #, python-format
215 410 msgid "Edited file %s via RhodeCode Enterprise"
216 411 msgstr ""
217 412
218 413 #: rhodecode/controllers/files.py:511
219 414 msgid "No changes"
220 415 msgstr ""
221 416
222 417 #: rhodecode/controllers/files.py:533 rhodecode/controllers/files.py:662
223 418 #, python-format
224 419 msgid "Successfully committed to %s"
225 420 msgstr ""
226 421
227 422 #: rhodecode/controllers/files.py:607 rhodecode/controllers/files.py:696
228 423 msgid "Added file via RhodeCode Enterprise"
229 424 msgstr ""
230 425
231 426 #: rhodecode/controllers/files.py:632
232 427 msgid "No filename"
233 428 msgstr ""
234 429
235 430 #: rhodecode/controllers/files.py:665
236 431 msgid "The location specified must be a relative path and must not contain .. in the path"
237 432 msgstr ""
238 433
239 434 #: rhodecode/controllers/files.py:719
240 435 msgid "Downloads disabled"
241 436 msgstr ""
242 437
243 438 #: rhodecode/controllers/files.py:725
244 439 #, python-format
245 440 msgid "Unknown revision %s"
246 441 msgstr ""
247 442
248 443 #: rhodecode/controllers/files.py:727
249 444 msgid "Empty repository"
250 445 msgstr ""
251 446
252 447 #: rhodecode/controllers/files.py:729 rhodecode/controllers/files.py:763
253 448 msgid "Unknown archive type"
254 449 msgstr ""
255 450
256 451 #: rhodecode/controllers/files.py:930
257 452 #, python-format
258 453 msgid "Commit %(commit)s does not exist."
259 454 msgstr ""
260 455
261 456 #: rhodecode/controllers/files.py:947
262 457 #, python-format
263 458 msgid "%(file_path)s has not changed between %(commit_1)s and %(commit_2)s."
264 459 msgstr ""
265 460
266 461 #: rhodecode/controllers/files.py:1014
267 462 msgid "Changesets"
268 463 msgstr ""
269 464
270 465 #: rhodecode/controllers/files.py:1035 rhodecode/controllers/summary.py:256
271 #: rhodecode/model/pull_request.py:1037 rhodecode/model/scm.py:783
466 #: rhodecode/model/pull_request.py:1051 rhodecode/model/scm.py:783
272 467 #: rhodecode/templates/base/vcs_settings.html:138
273 468 msgid "Branches"
274 469 msgstr ""
275 470
276 471 #: rhodecode/controllers/files.py:1039 rhodecode/model/scm.py:798
277 472 #: rhodecode/templates/base/vcs_settings.html:163
278 473 msgid "Tags"
279 474 msgstr ""
280 475
281 476 #: rhodecode/controllers/forks.py:191
282 477 #, python-format
283 478 msgid "An error occurred during repository forking %s"
284 479 msgstr ""
285 480
286 #: rhodecode/controllers/home.py:174
481 #: rhodecode/controllers/home.py:205
287 482 msgid "Groups"
288 483 msgstr ""
289 484
290 #: rhodecode/controllers/home.py:181 rhodecode/controllers/home.py:203
485 #: rhodecode/controllers/home.py:212 rhodecode/controllers/home.py:247
291 486 #: rhodecode/controllers/pullrequests.py:382
292 487 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:128
293 488 #: rhodecode/templates/admin/repos/repo_add.html:15
294 489 #: rhodecode/templates/admin/repos/repo_add.html:19
295 490 #: rhodecode/templates/admin/users/user_edit_advanced.html:11
296 #: rhodecode/templates/base/base.html:79 rhodecode/templates/base/base.html:147
297 #: rhodecode/templates/base/base.html:621
491 #: rhodecode/templates/base/base.html:79 rhodecode/templates/base/base.html:149
492 #: rhodecode/templates/base/base.html:626
298 493 msgid "Repositories"
299 494 msgstr ""
300 495
496 #: rhodecode/controllers/home.py:225
497 #, python-format
498 msgid "Commits in %(repo)s"
499 msgstr ""
500
301 501 #: rhodecode/controllers/journal.py:107 rhodecode/controllers/journal.py:150
302 502 msgid "public journal"
303 503 msgstr ""
304 504
305 505 #: rhodecode/controllers/journal.py:111 rhodecode/controllers/journal.py:154
306 506 msgid "journal"
307 507 msgstr ""
308 508
309 #: rhodecode/controllers/login.py:227 rhodecode/controllers/login.py:281
310 msgid "bad captcha"
311 msgstr ""
312
313 #: rhodecode/controllers/login.py:241
314 msgid "You have successfully registered with RhodeCode"
315 msgstr ""
316
317 #: rhodecode/controllers/login.py:286
318 msgid "Your password reset link was sent"
319 msgstr ""
320
321 #: rhodecode/controllers/login.py:307
322 msgid "Your password reset was successful, a new password has been sent to your email"
323 msgstr ""
324
325 #: rhodecode/controllers/login.py:340
326 msgid "There was an error during OAuth processing."
327 msgstr ""
328
329 #: rhodecode/controllers/login.py:406
330 msgid "You need to finish registration process to bind your external identity to your account or sign in to existing account"
331 msgstr ""
332
333 509 #: rhodecode/controllers/pullrequests.py:293
334 510 msgid "Commit does not exist"
335 511 msgstr ""
336 512
337 513 #: rhodecode/controllers/pullrequests.py:405
338 514 msgid "Pull request requires a title with min. 3 chars"
339 515 msgstr ""
340 516
341 517 #: rhodecode/controllers/pullrequests.py:407
342 518 msgid "Error creating pull request: {}"
343 519 msgstr ""
344 520
345 521 #: rhodecode/controllers/pullrequests.py:454
346 522 msgid "Successfully opened new pull request"
347 523 msgstr ""
348 524
349 525 #: rhodecode/controllers/pullrequests.py:457
350 526 msgid "Error occurred during sending pull request"
351 527 msgstr ""
352 528
353 529 #: rhodecode/controllers/pullrequests.py:497
354 530 msgid "Cannot update closed pull requests."
355 531 msgstr ""
356 532
357 533 #: rhodecode/controllers/pullrequests.py:503
358 534 msgid "Pull request title & description updated."
359 535 msgstr ""
360 536
361 537 #: rhodecode/controllers/pullrequests.py:513
362 538 msgid "Pull request updated to \"{source_commit_id}\" with {count_added} added, {count_removed} removed commits."
363 539 msgstr ""
364 540
365 541 #: rhodecode/controllers/pullrequests.py:523
366 542 msgid "Nothing changed in pull request."
367 543 msgstr ""
368 544
369 545 #: rhodecode/controllers/pullrequests.py:526
370 546 msgid "Skipping update of pull request due to reference type: {reference_type}"
371 547 msgstr ""
372 548
373 549 #: rhodecode/controllers/pullrequests.py:533
374 550 msgid "Update failed due to missing commits."
375 551 msgstr ""
376 552
377 553 #: rhodecode/controllers/pullrequests.py:579
378 554 msgid "Pull request reviewer approval is pending."
379 555 msgstr ""
380 556
381 #: rhodecode/controllers/pullrequests.py:629
557 #: rhodecode/controllers/pullrequests.py:593
558 msgid "Pull request was successfully merged and closed."
559 msgstr ""
560
561 #: rhodecode/controllers/pullrequests.py:631
382 562 msgid "Successfully deleted pull request"
383 563 msgstr ""
384 564
385 #: rhodecode/controllers/pullrequests.py:662
565 #: rhodecode/controllers/pullrequests.py:664
386 566 msgid "Reviewer approval is pending."
387 567 msgstr ""
388 568
389 #: rhodecode/controllers/pullrequests.py:704
569 #: rhodecode/controllers/pullrequests.py:706
390 570 msgid "Close Pull Request"
391 571 msgstr ""
392 572
393 #: rhodecode/controllers/pullrequests.py:748
394 #: rhodecode/model/pull_request.py:832
573 #: rhodecode/controllers/pullrequests.py:750
574 #: rhodecode/model/pull_request.py:840
395 575 msgid "Closing with"
396 576 msgstr ""
397 577
398 #: rhodecode/controllers/pullrequests.py:793
578 #: rhodecode/controllers/pullrequests.py:795
399 579 #, python-format
400 580 msgid "Closing pull request on other statuses than rejected or approved is forbidden. Calculated status from all reviewers is currently: %s"
401 581 msgstr ""
402 582
403 583 #: rhodecode/controllers/summary.py:240
404 584 msgid "Branch"
405 585 msgstr ""
406 586
407 587 #: rhodecode/controllers/summary.py:241
408 588 msgid "Tag"
409 589 msgstr ""
410 590
411 591 #: rhodecode/controllers/summary.py:242
412 592 msgid "Bookmark"
413 593 msgstr ""
414 594
415 595 #: rhodecode/controllers/summary.py:257
416 596 msgid "Closed branches"
417 597 msgstr ""
418 598
419 599 #: rhodecode/controllers/admin/defaults.py:84
420 600 msgid "Default settings updated successfully"
421 601 msgstr ""
422 602
423 603 #: rhodecode/controllers/admin/defaults.py:99
424 604 msgid "Error occurred during update of default values"
425 605 msgstr ""
426 606
427 607 #: rhodecode/controllers/admin/gists.py:59
428 #: rhodecode/controllers/admin/my_account.py:308
429 #: rhodecode/controllers/admin/users.py:434
608 #: rhodecode/controllers/admin/my_account.py:307
609 #: rhodecode/controllers/admin/users.py:436
430 610 msgid "forever"
431 611 msgstr ""
432 612
433 613 #: rhodecode/controllers/admin/gists.py:60
434 #: rhodecode/controllers/admin/my_account.py:309
435 #: rhodecode/controllers/admin/users.py:435
614 #: rhodecode/controllers/admin/my_account.py:308
615 #: rhodecode/controllers/admin/users.py:437
436 616 msgid "5 minutes"
437 617 msgstr ""
438 618
439 619 #: rhodecode/controllers/admin/gists.py:61
440 #: rhodecode/controllers/admin/my_account.py:310
441 #: rhodecode/controllers/admin/users.py:436
620 #: rhodecode/controllers/admin/my_account.py:309
621 #: rhodecode/controllers/admin/users.py:438
442 622 msgid "1 hour"
443 623 msgstr ""
444 624
445 625 #: rhodecode/controllers/admin/gists.py:62
446 #: rhodecode/controllers/admin/my_account.py:311
447 #: rhodecode/controllers/admin/users.py:437
626 #: rhodecode/controllers/admin/my_account.py:310
627 #: rhodecode/controllers/admin/users.py:439
448 628 msgid "1 day"
449 629 msgstr ""
450 630
451 631 #: rhodecode/controllers/admin/gists.py:63
452 #: rhodecode/controllers/admin/my_account.py:312
453 #: rhodecode/controllers/admin/users.py:438
632 #: rhodecode/controllers/admin/my_account.py:311
633 #: rhodecode/controllers/admin/users.py:440
454 634 msgid "1 month"
455 635 msgstr ""
456 636
457 637 #: rhodecode/controllers/admin/gists.py:67
458 #: rhodecode/controllers/admin/my_account.py:314
459 #: rhodecode/controllers/admin/users.py:440
638 #: rhodecode/controllers/admin/my_account.py:313
639 #: rhodecode/controllers/admin/users.py:442
460 640 msgid "Lifetime"
461 641 msgstr ""
462 642
463 643 #: rhodecode/controllers/admin/gists.py:69
464 644 msgid "Requires registered account"
465 645 msgstr ""
466 646
467 647 #: rhodecode/controllers/admin/gists.py:70
468 648 msgid "Can be accessed by anonymous users"
469 649 msgstr ""
470 650
471 651 #: rhodecode/controllers/admin/gists.py:180
472 652 msgid "Error occurred during gist creation"
473 653 msgstr ""
474 654
475 655 #: rhodecode/controllers/admin/gists.py:211
476 656 #, python-format
477 657 msgid "Deleted gist %s"
478 658 msgstr ""
479 659
480 660 #: rhodecode/controllers/admin/gists.py:284
481 661 msgid "Successfully updated gist content"
482 662 msgstr ""
483 663
484 664 #: rhodecode/controllers/admin/gists.py:289
485 665 msgid "Successfully updated gist data"
486 666 msgstr ""
487 667
488 668 #: rhodecode/controllers/admin/gists.py:292
489 669 #, python-format
490 670 msgid "Error occurred during update of gist %s"
491 671 msgstr ""
492 672
493 673 #: rhodecode/controllers/admin/gists.py:315
494 674 #: rhodecode/templates/admin/gists/show.html:67
495 675 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:19
496 676 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:42
497 677 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:16
498 678 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:38
499 679 #: rhodecode/templates/data_table/_dt_elements.html:253
500 680 msgid "never"
501 681 msgstr ""
502 682
503 683 #: rhodecode/controllers/admin/gists.py:320
504 684 #, python-format
505 685 msgid "%(expiry)s - current value"
506 686 msgstr ""
507 687
508 #: rhodecode/controllers/admin/my_account.py:71
688 #: rhodecode/controllers/admin/my_account.py:70
509 689 msgid "You can't edit this user since it's crucial for entire application"
510 690 msgstr ""
511 691
512 #: rhodecode/controllers/admin/my_account.py:129
692 #: rhodecode/controllers/admin/my_account.py:128
513 693 msgid "Your account was updated successfully"
514 694 msgstr ""
515 695
516 #: rhodecode/controllers/admin/my_account.py:144
517 #: rhodecode/controllers/admin/users.py:221
696 #: rhodecode/controllers/admin/my_account.py:143
697 #: rhodecode/controllers/admin/users.py:223
518 698 #, python-format
519 699 msgid "Error occurred during update of user %s"
520 700 msgstr ""
521 701
522 #: rhodecode/controllers/admin/my_account.py:203
702 #: rhodecode/controllers/admin/my_account.py:202
523 703 msgid "Successfully updated password"
524 704 msgstr ""
525 705
526 #: rhodecode/controllers/admin/my_account.py:214
706 #: rhodecode/controllers/admin/my_account.py:213
527 707 msgid "Error occurred during update of user password"
528 708 msgstr ""
529 709
530 #: rhodecode/controllers/admin/my_account.py:262
531 #: rhodecode/controllers/admin/users.py:614
710 #: rhodecode/controllers/admin/my_account.py:261
711 #: rhodecode/controllers/admin/users.py:616
532 712 #, python-format
533 713 msgid "Added new email address `%s` for user account"
534 714 msgstr ""
535 715
536 #: rhodecode/controllers/admin/my_account.py:269
537 #: rhodecode/controllers/admin/users.py:621
716 #: rhodecode/controllers/admin/my_account.py:268
717 #: rhodecode/controllers/admin/users.py:623
538 718 msgid "An error occurred during email saving"
539 719 msgstr ""
540 720
541 #: rhodecode/controllers/admin/my_account.py:279
542 #: rhodecode/controllers/admin/users.py:636
721 #: rhodecode/controllers/admin/my_account.py:278
722 #: rhodecode/controllers/admin/users.py:638
543 723 msgid "Removed email address from user account"
544 724 msgstr ""
545 725
546 #: rhodecode/controllers/admin/my_account.py:317
547 #: rhodecode/controllers/admin/users.py:443
726 #: rhodecode/controllers/admin/my_account.py:316
727 #: rhodecode/controllers/admin/users.py:445
548 728 msgid "Role"
549 729 msgstr ""
550 730
551 #: rhodecode/controllers/admin/my_account.py:330
552 #: rhodecode/controllers/admin/users.py:467
731 #: rhodecode/controllers/admin/my_account.py:329
732 #: rhodecode/controllers/admin/users.py:469
553 733 msgid "Auth token successfully created"
554 734 msgstr ""
555 735
556 #: rhodecode/controllers/admin/my_account.py:343
557 #: rhodecode/controllers/admin/users.py:486
736 #: rhodecode/controllers/admin/my_account.py:342
737 #: rhodecode/controllers/admin/users.py:488
558 738 msgid "Auth token successfully reset"
559 739 msgstr ""
560 740
561 #: rhodecode/controllers/admin/my_account.py:347
562 #: rhodecode/controllers/admin/users.py:490
741 #: rhodecode/controllers/admin/my_account.py:346
742 #: rhodecode/controllers/admin/users.py:492
563 743 msgid "Auth token successfully deleted"
564 744 msgstr ""
565 745
566 #: rhodecode/controllers/admin/my_account.py:371
567 msgid "OAuth token successfully deleted"
568 msgstr ""
569
570 746 #: rhodecode/controllers/admin/permissions.py:111
571 747 msgid "Application permissions updated successfully"
572 748 msgstr ""
573 749
574 750 #: rhodecode/controllers/admin/permissions.py:126
575 751 #: rhodecode/controllers/admin/permissions.py:175
576 752 #: rhodecode/controllers/admin/permissions.py:229
577 753 msgid "Error occurred during update of permissions"
578 754 msgstr ""
579 755
580 756 #: rhodecode/controllers/admin/permissions.py:160
581 757 msgid "Object permissions updated successfully"
582 758 msgstr ""
583 759
584 760 #: rhodecode/controllers/admin/permissions.py:214
585 761 msgid "Global permissions updated successfully"
586 762 msgstr ""
587 763
588 764 #: rhodecode/controllers/admin/repo_groups.py:197
589 765 #, python-format
590 766 msgid "Created repository group %s"
591 767 msgstr ""
592 768
593 769 #: rhodecode/controllers/admin/repo_groups.py:210
594 770 #, python-format
595 771 msgid "Error occurred during creation of repository group %s"
596 772 msgstr ""
597 773
598 #: rhodecode/controllers/admin/repo_groups.py:259
774 #: rhodecode/controllers/admin/repo_groups.py:258
599 775 #, python-format
600 776 msgid "Updated repository group %s"
601 777 msgstr ""
602 778
603 #: rhodecode/controllers/admin/repo_groups.py:275
779 #: rhodecode/controllers/admin/repo_groups.py:274
604 780 #, python-format
605 781 msgid "Error occurred during update of repository group %s"
606 782 msgstr ""
607 783
608 #: rhodecode/controllers/admin/repo_groups.py:297
784 #: rhodecode/controllers/admin/repo_groups.py:296
609 785 #, python-format
610 786 msgid "This group contains %(num)d repository and cannot be deleted"
611 787 msgid_plural "This group contains %(num)d repositories and cannot be deleted"
612 788 msgstr[0] ""
613 789 msgstr[1] ""
614 790
615 #: rhodecode/controllers/admin/repo_groups.py:306
791 #: rhodecode/controllers/admin/repo_groups.py:305
616 792 #, python-format
617 793 msgid "This group contains %(num)d subgroup and cannot be deleted"
618 794 msgid_plural "This group contains %(num)d subgroups and cannot be deleted"
619 795 msgstr[0] ""
620 796 msgstr[1] ""
621 797
622 #: rhodecode/controllers/admin/repo_groups.py:313
798 #: rhodecode/controllers/admin/repo_groups.py:312
623 799 #, python-format
624 800 msgid "Removed repository group %s"
625 801 msgstr ""
626 802
627 #: rhodecode/controllers/admin/repo_groups.py:318
803 #: rhodecode/controllers/admin/repo_groups.py:317
628 804 #, python-format
629 805 msgid "Error occurred during deletion of repository group %s"
630 806 msgstr ""
631 807
632 #: rhodecode/controllers/admin/repo_groups.py:389
633 #: rhodecode/controllers/admin/user_groups.py:323
808 #: rhodecode/controllers/admin/repo_groups.py:388
809 #: rhodecode/controllers/admin/user_groups.py:325
634 810 msgid "Cannot change permission for yourself as admin"
635 811 msgstr ""
636 812
637 #: rhodecode/controllers/admin/repo_groups.py:406
813 #: rhodecode/controllers/admin/repo_groups.py:405
638 814 msgid "Repository Group permissions updated"
639 815 msgstr ""
640 816
641 817 #: rhodecode/controllers/admin/repos.py:128
642 818 #, python-format
643 819 msgid "Error creating repository %s: invalid certificate"
644 820 msgstr ""
645 821
646 822 #: rhodecode/controllers/admin/repos.py:132
647 823 #, python-format
648 824 msgid "Error creating repository %s"
649 825 msgstr ""
650 826
651 827 #: rhodecode/controllers/admin/repos.py:264
652 828 #, python-format
653 829 msgid "Created repository %s from %s"
654 830 msgstr ""
655 831
656 832 #: rhodecode/controllers/admin/repos.py:273
657 833 #, python-format
658 834 msgid "Forked repository %s as %s"
659 835 msgstr ""
660 836
661 837 #: rhodecode/controllers/admin/repos.py:276
662 838 #, python-format
663 839 msgid "Created repository %s"
664 840 msgstr ""
665 841
666 842 #: rhodecode/controllers/admin/repos.py:318
667 843 #, python-format
668 844 msgid "Repository %s updated successfully"
669 845 msgstr ""
670 846
671 847 #: rhodecode/controllers/admin/repos.py:337
672 848 #, python-format
673 849 msgid "Error occurred during update of repository %s"
674 850 msgstr ""
675 851
676 852 #: rhodecode/controllers/admin/repos.py:365
677 853 #, python-format
678 854 msgid "Detached %s forks"
679 855 msgstr ""
680 856
681 857 #: rhodecode/controllers/admin/repos.py:368
682 858 #, python-format
683 859 msgid "Deleted %s forks"
684 860 msgstr ""
685 861
686 862 #: rhodecode/controllers/admin/repos.py:373
687 863 #, python-format
688 864 msgid "Deleted repository %s"
689 865 msgstr ""
690 866
691 867 #: rhodecode/controllers/admin/repos.py:376
692 868 #, python-format
693 869 msgid "Cannot delete %s it still contains attached forks"
694 870 msgstr ""
695 871
696 872 #: rhodecode/controllers/admin/repos.py:381
697 873 #, python-format
698 874 msgid "An error occurred during deletion of %s"
699 875 msgstr ""
700 876
701 877 #: rhodecode/controllers/admin/repos.py:435
702 878 msgid "Repository permissions updated"
703 879 msgstr ""
704 880
705 881 #: rhodecode/controllers/admin/repos.py:466
706 882 msgid "An error occurred during creation of field"
707 883 msgstr ""
708 884
709 885 #: rhodecode/controllers/admin/repos.py:481
710 886 msgid "An error occurred during removal of field"
711 887 msgstr ""
712 888
713 889 #: rhodecode/controllers/admin/repos.py:520
714 890 msgid "Updated repository visibility in public journal"
715 891 msgstr ""
716 892
717 893 #: rhodecode/controllers/admin/repos.py:524
718 894 msgid "An error occurred during setting this repository in public journal"
719 895 msgstr ""
720 896
721 897 #: rhodecode/controllers/admin/repos.py:548
722 898 msgid "Nothing"
723 899 msgstr ""
724 900
725 901 #: rhodecode/controllers/admin/repos.py:550
726 902 #, python-format
727 903 msgid "Marked repo %s as fork of %s"
728 904 msgstr ""
729 905
730 906 #: rhodecode/controllers/admin/repos.py:557
731 907 msgid "An error occurred during this operation"
732 908 msgstr ""
733 909
734 910 #: rhodecode/controllers/admin/repos.py:575
735 911 msgid "Locked repository"
736 912 msgstr ""
737 913
738 914 #: rhodecode/controllers/admin/repos.py:578
739 915 msgid "Unlocked repository"
740 916 msgstr ""
741 917
742 918 #: rhodecode/controllers/admin/repos.py:581
743 919 #: rhodecode/controllers/admin/repos.py:610
744 920 msgid "An error occurred during unlocking"
745 921 msgstr ""
746 922
747 923 #: rhodecode/controllers/admin/repos.py:600
748 924 msgid "Unlocked"
749 925 msgstr ""
750 926
751 927 #: rhodecode/controllers/admin/repos.py:604
752 928 msgid "Locked"
753 929 msgstr ""
754 930
755 931 #: rhodecode/controllers/admin/repos.py:606
756 932 #, python-format
757 933 msgid "Repository has been %s"
758 934 msgstr ""
759 935
760 936 #: rhodecode/controllers/admin/repos.py:621
761 937 msgid "Cache invalidation successful"
762 938 msgstr ""
763 939
764 940 #: rhodecode/controllers/admin/repos.py:625
765 941 msgid "An error occurred during cache invalidation"
766 942 msgstr ""
767 943
768 944 #: rhodecode/controllers/admin/repos.py:645
769 945 msgid "Pulled from remote location"
770 946 msgstr ""
771 947
772 948 #: rhodecode/controllers/admin/repos.py:648
773 949 msgid "An error occurred during pull from remote location"
774 950 msgstr ""
775 951
776 952 #: rhodecode/controllers/admin/repos.py:670
777 953 msgid "An error occurred during deletion of repository stats"
778 954 msgstr ""
779 955
780 956 #: rhodecode/controllers/admin/repos.py:717
781 957 msgid "Error occurred during deleting issue tracker entry"
782 958 msgstr ""
783 959
784 960 #: rhodecode/controllers/admin/repos.py:720
785 #: rhodecode/controllers/admin/settings.py:361
961 #: rhodecode/controllers/admin/settings.py:362
786 962 msgid "Removed issue tracker entry"
787 963 msgstr ""
788 964
789 965 #: rhodecode/controllers/admin/repos.py:750
790 #: rhodecode/controllers/admin/settings.py:406
966 #: rhodecode/controllers/admin/settings.py:408
791 967 msgid "Updated issue tracker entries"
792 968 msgstr ""
793 969
794 970 #: rhodecode/controllers/admin/repos.py:809
795 #: rhodecode/controllers/admin/settings.py:140
796 #: rhodecode/controllers/admin/settings.py:686
971 #: rhodecode/controllers/admin/settings.py:141
972 #: rhodecode/controllers/admin/settings.py:718
797 973 msgid "Some form inputs contain invalid data."
798 974 msgstr ""
799 975
800 976 #: rhodecode/controllers/admin/repos.py:827
801 977 msgid "Error occurred during updating repository VCS settings"
802 978 msgstr ""
803 979
804 980 #: rhodecode/controllers/admin/repos.py:831
805 #: rhodecode/controllers/admin/settings.py:166
981 #: rhodecode/controllers/admin/settings.py:167
806 982 msgid "Updated VCS settings"
807 983 msgstr ""
808 984
809 #: rhodecode/controllers/admin/settings.py:162
810 #: rhodecode/controllers/admin/settings.py:265
985 #: rhodecode/controllers/admin/settings.py:163
986 #: rhodecode/controllers/admin/settings.py:266
811 987 msgid "Error occurred during updating application settings"
812 988 msgstr ""
813 989
814 #: rhodecode/controllers/admin/settings.py:209
990 #: rhodecode/controllers/admin/settings.py:210
815 991 #, python-format
816 992 msgid "Repositories successfully rescanned added: %s ; removed: %s"
817 993 msgstr ""
818 994
819 #: rhodecode/controllers/admin/settings.py:260
995 #: rhodecode/controllers/admin/settings.py:262
820 996 msgid "Updated application settings"
821 997 msgstr ""
822 998
823 #: rhodecode/controllers/admin/settings.py:325
999 #: rhodecode/controllers/admin/settings.py:326
824 1000 msgid "Updated visualisation settings"
825 1001 msgstr ""
826 1002
827 #: rhodecode/controllers/admin/settings.py:328
1003 #: rhodecode/controllers/admin/settings.py:329
828 1004 msgid "Error occurred during updating visualisation settings"
829 1005 msgstr ""
830 1006
831 #: rhodecode/controllers/admin/settings.py:419
1007 #: rhodecode/controllers/admin/settings.py:421
832 1008 msgid "Please enter email address"
833 1009 msgstr ""
834 1010
835 #: rhodecode/controllers/admin/settings.py:437
1011 #: rhodecode/controllers/admin/settings.py:439
836 1012 msgid "Send email task created"
837 1013 msgstr ""
838 1014
839 #: rhodecode/controllers/admin/settings.py:470
1015 #: rhodecode/controllers/admin/settings.py:472
840 1016 msgid "Added new hook"
841 1017 msgstr ""
842 1018
843 #: rhodecode/controllers/admin/settings.py:485
1019 #: rhodecode/controllers/admin/settings.py:487
844 1020 msgid "Updated hooks"
845 1021 msgstr ""
846 1022
847 #: rhodecode/controllers/admin/settings.py:489
1023 #: rhodecode/controllers/admin/settings.py:491
848 1024 msgid "Error occurred during hook creation"
849 1025 msgstr ""
850 1026
851 #: rhodecode/controllers/admin/settings.py:536
1027 #: rhodecode/controllers/admin/settings.py:539
852 1028 #, python-format
853 1029 msgid "Critical: your disk space is very low <b>%s%%</b> usedpercent"
854 1030 msgstr ""
855 1031
856 #: rhodecode/controllers/admin/settings.py:540
1032 #: rhodecode/controllers/admin/settings.py:543
857 1033 #, python-format
858 1034 msgid "Warning: your disk space is running low <b>%s%%</b> usedpercent"
859 1035 msgstr ""
860 1036
861 #: rhodecode/controllers/admin/settings.py:707
1037 #: rhodecode/controllers/admin/settings.py:739
862 1038 msgid "Error occurred during updating labs settings"
863 1039 msgstr ""
864 1040
865 #: rhodecode/controllers/admin/settings.py:711
1041 #: rhodecode/controllers/admin/settings.py:744
866 1042 msgid "Updated Labs settings"
867 1043 msgstr ""
868 1044
869 #: rhodecode/controllers/admin/settings.py:775
1045 #: rhodecode/controllers/admin/settings.py:794
870 1046 msgid "Mercurial server-side merge"
871 1047 msgstr ""
872 1048
873 #: rhodecode/controllers/admin/settings.py:776
1049 #: rhodecode/controllers/admin/settings.py:795
874 1050 msgid "Use rebase instead of creating a merge commit when merging via web interface"
875 1051 msgstr ""
876 1052
877 #: rhodecode/controllers/admin/settings.py:782
1053 #: rhodecode/controllers/admin/settings.py:801
878 1054 msgid "Subversion HTTP Support"
879 1055 msgstr ""
880 1056
881 #: rhodecode/controllers/admin/settings.py:783
1057 #: rhodecode/controllers/admin/settings.py:802
882 1058 msgid "Proxy subversion HTTP requests"
883 1059 msgstr ""
884 1060
885 #: rhodecode/controllers/admin/settings.py:789
1061 #: rhodecode/controllers/admin/settings.py:808
886 1062 msgid "Subversion HTTP Server URL"
887 1063 msgstr ""
888 1064
889 #: rhodecode/controllers/admin/settings.py:791
1065 #: rhodecode/controllers/admin/settings.py:810
890 1066 msgid "e.g. http://localhost:8080/"
891 1067 msgstr ""
892 1068
893 #: rhodecode/controllers/admin/settings.py:826
1069 #: rhodecode/controllers/admin/settings.py:845
894 1070 #: rhodecode/templates/admin/repos/repo_edit.html:48
895 1071 msgid "VCS"
896 1072 msgstr ""
897 1073
898 #: rhodecode/controllers/admin/settings.py:827
1074 #: rhodecode/controllers/admin/settings.py:846
899 1075 msgid "Visual"
900 1076 msgstr ""
901 1077
902 #: rhodecode/controllers/admin/settings.py:828
1078 #: rhodecode/controllers/admin/settings.py:847
903 1079 msgid "Remap and Rescan"
904 1080 msgstr ""
905 1081
906 #: rhodecode/controllers/admin/settings.py:830
1082 #: rhodecode/controllers/admin/settings.py:849
907 1083 #: rhodecode/templates/admin/repos/repo_edit.html:54
908 1084 msgid "Issue Tracker"
909 1085 msgstr ""
910 1086
911 #: rhodecode/controllers/admin/settings.py:832
912 #: rhodecode/templates/register.html:51
1087 #: rhodecode/controllers/admin/settings.py:851
1088 #: rhodecode/templates/register.html:76
913 1089 #: rhodecode/templates/admin/my_account/my_account_profile.html:48
914 1090 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:94
915 1091 #: rhodecode/templates/admin/users/user_add.html:86
916 1092 #: rhodecode/templates/admin/users/user_edit_profile.html:65
917 1093 #: rhodecode/templates/admin/users/users.html:91
918 1094 #: rhodecode/templates/users/user_profile.html:51
919 1095 msgid "Email"
920 1096 msgstr ""
921 1097
922 #: rhodecode/controllers/admin/settings.py:833
1098 #: rhodecode/controllers/admin/settings.py:852
923 1099 msgid "Hooks"
924 1100 msgstr ""
925 1101
926 #: rhodecode/controllers/admin/settings.py:834
1102 #: rhodecode/controllers/admin/settings.py:853
927 1103 msgid "Full Text Search"
928 1104 msgstr ""
929 1105
930 #: rhodecode/controllers/admin/settings.py:836
1106 #: rhodecode/controllers/admin/settings.py:855
931 1107 #: rhodecode/templates/admin/settings/settings_system.html:47
932 1108 msgid "System Info"
933 1109 msgstr ""
934 1110
935 #: rhodecode/controllers/admin/settings.py:838
1111 #: rhodecode/controllers/admin/settings.py:857
936 1112 msgid "Open Source Licenses"
937 1113 msgstr ""
938 1114
939 #: rhodecode/controllers/admin/settings.py:856
1115 #: rhodecode/controllers/admin/settings.py:875
940 1116 msgid "Labs"
941 1117 msgstr ""
942 1118
943 #: rhodecode/controllers/admin/user_groups.py:167
1119 #: rhodecode/controllers/admin/user_groups.py:168
944 1120 #, python-format
945 1121 msgid "Created user group %(user_group_link)s"
946 1122 msgstr ""
947 1123
948 #: rhodecode/controllers/admin/user_groups.py:181
1124 #: rhodecode/controllers/admin/user_groups.py:182
949 1125 #, python-format
950 1126 msgid "Error occurred during creation of user group %s"
951 1127 msgstr ""
952 1128
953 #: rhodecode/controllers/admin/user_groups.py:221
1129 #: rhodecode/controllers/admin/user_groups.py:223
954 1130 #, python-format
955 1131 msgid "Updated user group %s"
956 1132 msgstr ""
957 1133
958 #: rhodecode/controllers/admin/user_groups.py:236
1134 #: rhodecode/controllers/admin/user_groups.py:238
959 1135 #, python-format
960 1136 msgid "Error occurred during update of user group %s"
961 1137 msgstr ""
962 1138
963 #: rhodecode/controllers/admin/user_groups.py:258
1139 #: rhodecode/controllers/admin/user_groups.py:260
964 1140 msgid "Successfully deleted user group"
965 1141 msgstr ""
966 1142
967 #: rhodecode/controllers/admin/user_groups.py:263
1143 #: rhodecode/controllers/admin/user_groups.py:265
968 1144 msgid "An error occurred during deletion of user group"
969 1145 msgstr ""
970 1146
971 #: rhodecode/controllers/admin/user_groups.py:331
1147 #: rhodecode/controllers/admin/user_groups.py:333
972 1148 msgid "Target group cannot be the same"
973 1149 msgstr ""
974 1150
975 #: rhodecode/controllers/admin/user_groups.py:337
1151 #: rhodecode/controllers/admin/user_groups.py:339
976 1152 msgid "User Group permissions updated"
977 1153 msgstr ""
978 1154
979 #: rhodecode/controllers/admin/user_groups.py:422
1155 #: rhodecode/controllers/admin/user_groups.py:424
980 1156 msgid "User Group global permissions updated successfully"
981 1157 msgstr ""
982 1158
983 #: rhodecode/controllers/admin/user_groups.py:438
984 #: rhodecode/controllers/admin/users.py:564
1159 #: rhodecode/controllers/admin/user_groups.py:440
1160 #: rhodecode/controllers/admin/users.py:566
985 1161 msgid "An error occurred during permissions saving"
986 1162 msgstr ""
987 1163
988 1164 #: rhodecode/controllers/admin/users.py:147
989 1165 #, python-format
990 1166 msgid "Created user %(user_link)s"
991 1167 msgstr ""
992 1168
993 1169 #: rhodecode/controllers/admin/users.py:162
994 1170 #, python-format
995 1171 msgid "Error occurred during creation of user %s"
996 1172 msgstr ""
997 1173
998 1174 #: rhodecode/controllers/admin/users.py:206
999 1175 msgid "User updated successfully"
1000 1176 msgstr ""
1001 1177
1002 #: rhodecode/controllers/admin/users.py:255
1178 #: rhodecode/controllers/admin/users.py:257
1003 1179 #, python-format
1004 1180 msgid "Detached %s repositories"
1005 1181 msgstr ""
1006 1182
1007 #: rhodecode/controllers/admin/users.py:260
1183 #: rhodecode/controllers/admin/users.py:262
1008 1184 #, python-format
1009 1185 msgid "Deleted %s repositories"
1010 1186 msgstr ""
1011 1187
1012 #: rhodecode/controllers/admin/users.py:268
1188 #: rhodecode/controllers/admin/users.py:270
1013 1189 #, python-format
1014 1190 msgid "Detached %s repository groups"
1015 1191 msgstr ""
1016 1192
1017 #: rhodecode/controllers/admin/users.py:273
1193 #: rhodecode/controllers/admin/users.py:275
1018 1194 #, python-format
1019 1195 msgid "Deleted %s repository groups"
1020 1196 msgstr ""
1021 1197
1022 #: rhodecode/controllers/admin/users.py:281
1198 #: rhodecode/controllers/admin/users.py:283
1023 1199 #, python-format
1024 1200 msgid "Detached %s user groups"
1025 1201 msgstr ""
1026 1202
1027 #: rhodecode/controllers/admin/users.py:286
1203 #: rhodecode/controllers/admin/users.py:288
1028 1204 #, python-format
1029 1205 msgid "Deleted %s user groups"
1030 1206 msgstr ""
1031 1207
1032 #: rhodecode/controllers/admin/users.py:297
1208 #: rhodecode/controllers/admin/users.py:299
1033 1209 msgid "Successfully deleted user"
1034 1210 msgstr ""
1035 1211
1036 #: rhodecode/controllers/admin/users.py:303
1212 #: rhodecode/controllers/admin/users.py:305
1037 1213 msgid "An error occurred during deletion of user"
1038 1214 msgstr ""
1039 1215
1040 #: rhodecode/controllers/admin/users.py:322
1041 msgid "Force password change disabled for user"
1042 msgstr ""
1043
1044 1216 #: rhodecode/controllers/admin/users.py:324
1217 msgid "Force password change disabled for user"
1218 msgstr ""
1219
1220 #: rhodecode/controllers/admin/users.py:326
1045 1221 msgid "Force password change enabled for user"
1046 1222 msgstr ""
1047 1223
1048 #: rhodecode/controllers/admin/users.py:328
1224 #: rhodecode/controllers/admin/users.py:330
1049 1225 msgid "An error occurred during password reset for user"
1050 1226 msgstr ""
1051 1227
1052 #: rhodecode/controllers/admin/users.py:354
1228 #: rhodecode/controllers/admin/users.py:356
1053 1229 #, python-format
1054 1230 msgid "Created repository group `%s`"
1055 1231 msgstr ""
1056 1232
1057 #: rhodecode/controllers/admin/users.py:358
1233 #: rhodecode/controllers/admin/users.py:360
1058 1234 msgid "An error occurred during repository group creation for user"
1059 1235 msgstr ""
1060 1236
1061 #: rhodecode/controllers/admin/users.py:377
1062 #: rhodecode/controllers/admin/users.py:398
1063 #: rhodecode/controllers/admin/users.py:428
1064 #: rhodecode/controllers/admin/users.py:459
1065 #: rhodecode/controllers/admin/users.py:476
1066 #: rhodecode/controllers/admin/users.py:499
1067 #: rhodecode/controllers/admin/users.py:573
1068 #: rhodecode/controllers/admin/users.py:586
1069 #: rhodecode/controllers/admin/users.py:644
1237 #: rhodecode/controllers/admin/users.py:379
1238 #: rhodecode/controllers/admin/users.py:400
1239 #: rhodecode/controllers/admin/users.py:430
1240 #: rhodecode/controllers/admin/users.py:461
1241 #: rhodecode/controllers/admin/users.py:478
1242 #: rhodecode/controllers/admin/users.py:501
1243 #: rhodecode/controllers/admin/users.py:575
1244 #: rhodecode/controllers/admin/users.py:588
1245 #: rhodecode/controllers/admin/users.py:646
1070 1246 msgid "You can't edit this user"
1071 1247 msgstr ""
1072 1248
1073 #: rhodecode/controllers/admin/users.py:412
1249 #: rhodecode/controllers/admin/users.py:414
1074 1250 msgid "The user participates as reviewer in pull requests and cannot be deleted. You can set the user to \"inactive\" instead of deleting it."
1075 1251 msgstr ""
1076 1252
1077 #: rhodecode/controllers/admin/users.py:548
1253 #: rhodecode/controllers/admin/users.py:550
1078 1254 msgid "User global permissions updated successfully"
1079 1255 msgstr ""
1080 1256
1081 #: rhodecode/controllers/admin/users.py:676
1257 #: rhodecode/controllers/admin/users.py:678
1082 1258 #, python-format
1083 1259 msgid "An error occurred during ip saving:%s"
1084 1260 msgstr ""
1085 1261
1086 #: rhodecode/controllers/admin/users.py:691
1262 #: rhodecode/controllers/admin/users.py:693
1087 1263 msgid "An error occurred during ip saving"
1088 1264 msgstr ""
1089 1265
1090 #: rhodecode/controllers/admin/users.py:695
1266 #: rhodecode/controllers/admin/users.py:697
1091 1267 #, python-format
1092 1268 msgid "Added ips %s to user whitelist"
1093 1269 msgstr ""
1094 1270
1095 #: rhodecode/controllers/admin/users.py:713
1271 #: rhodecode/controllers/admin/users.py:715
1096 1272 msgid "Removed ip address from user whitelist"
1097 1273 msgstr ""
1098 1274
1099 1275 #: rhodecode/lib/action_parser.py:89
1100 1276 msgid "[deleted] repository"
1101 1277 msgstr ""
1102 1278
1103 1279 #: rhodecode/lib/action_parser.py:92 rhodecode/lib/action_parser.py:110
1104 1280 msgid "[created] repository"
1105 1281 msgstr ""
1106 1282
1107 1283 #: rhodecode/lib/action_parser.py:95
1108 1284 msgid "[created] repository as fork"
1109 1285 msgstr ""
1110 1286
1111 1287 #: rhodecode/lib/action_parser.py:98 rhodecode/lib/action_parser.py:113
1112 1288 msgid "[forked] repository"
1113 1289 msgstr ""
1114 1290
1115 1291 #: rhodecode/lib/action_parser.py:101 rhodecode/lib/action_parser.py:116
1116 1292 msgid "[updated] repository"
1117 1293 msgstr ""
1118 1294
1119 1295 #: rhodecode/lib/action_parser.py:104
1120 1296 msgid "[downloaded] archive from repository"
1121 1297 msgstr ""
1122 1298
1123 1299 #: rhodecode/lib/action_parser.py:107
1124 1300 msgid "[delete] repository"
1125 1301 msgstr ""
1126 1302
1127 1303 #: rhodecode/lib/action_parser.py:119
1128 1304 msgid "[created] user"
1129 1305 msgstr ""
1130 1306
1131 1307 #: rhodecode/lib/action_parser.py:122
1132 1308 msgid "[updated] user"
1133 1309 msgstr ""
1134 1310
1135 1311 #: rhodecode/lib/action_parser.py:125
1136 1312 msgid "[created] user group"
1137 1313 msgstr ""
1138 1314
1139 1315 #: rhodecode/lib/action_parser.py:128
1140 1316 msgid "[updated] user group"
1141 1317 msgstr ""
1142 1318
1143 1319 #: rhodecode/lib/action_parser.py:131
1144 1320 msgid "[commented] on commit in repository"
1145 1321 msgstr ""
1146 1322
1147 1323 #: rhodecode/lib/action_parser.py:134
1148 1324 msgid "[commented] on pull request for"
1149 1325 msgstr ""
1150 1326
1151 1327 #: rhodecode/lib/action_parser.py:137
1152 1328 msgid "[closed] pull request for"
1153 1329 msgstr ""
1154 1330
1155 1331 #: rhodecode/lib/action_parser.py:140
1156 1332 msgid "[merged] pull request for"
1157 1333 msgstr ""
1158 1334
1159 1335 #: rhodecode/lib/action_parser.py:143
1160 1336 msgid "[pushed] into"
1161 1337 msgstr ""
1162 1338
1163 1339 #: rhodecode/lib/action_parser.py:146
1164 1340 msgid "[committed via RhodeCode] into repository"
1165 1341 msgstr ""
1166 1342
1167 1343 #: rhodecode/lib/action_parser.py:149
1168 1344 msgid "[pulled from remote] into repository"
1169 1345 msgstr ""
1170 1346
1171 1347 #: rhodecode/lib/action_parser.py:152
1172 1348 msgid "[pulled] from"
1173 1349 msgstr ""
1174 1350
1175 1351 #: rhodecode/lib/action_parser.py:155
1176 1352 msgid "[started following] repository"
1177 1353 msgstr ""
1178 1354
1179 1355 #: rhodecode/lib/action_parser.py:158
1180 1356 msgid "[stopped following] repository"
1181 1357 msgstr ""
1182 1358
1183 1359 #: rhodecode/lib/action_parser.py:166
1184 1360 #, python-format
1185 1361 msgid "fork name %s"
1186 1362 msgstr ""
1187 1363
1188 1364 #: rhodecode/lib/action_parser.py:183
1189 1365 #: rhodecode/templates/pullrequests/pullrequest_show.html:48
1190 1366 #, python-format
1191 1367 msgid "Pull request #%s"
1192 1368 msgstr ""
1193 1369
1194 1370 #: rhodecode/lib/action_parser.py:216
1195 1371 #, python-format
1196 1372 msgid "Show all combined commits %s->%s"
1197 1373 msgstr ""
1198 1374
1199 1375 #: rhodecode/lib/action_parser.py:220
1200 1376 msgid "compare view"
1201 1377 msgstr ""
1202 1378
1203 1379 #: rhodecode/lib/action_parser.py:227
1204 1380 #, python-format
1205 1381 msgid " and %(num)s more commits"
1206 1382 msgstr ""
1207 1383
1208 1384 #: rhodecode/lib/action_parser.py:279
1209 1385 #, python-format
1210 1386 msgid "Deleted branch: %s"
1211 1387 msgstr ""
1212 1388
1213 1389 #: rhodecode/lib/action_parser.py:282
1214 1390 #, python-format
1215 1391 msgid "Created tag: %s"
1216 1392 msgstr ""
1217 1393
1218 1394 #: rhodecode/lib/action_parser.py:295
1219 1395 msgid "Commit not found"
1220 1396 msgstr ""
1221 1397
1222 #: rhodecode/lib/auth.py:1106
1398 #: rhodecode/lib/auth.py:1180
1223 1399 #, python-format
1224 1400 msgid "IP %s not allowed"
1225 1401 msgstr ""
1226 1402
1227 #: rhodecode/lib/auth.py:1180
1403 #: rhodecode/lib/auth.py:1254
1228 1404 msgid "You need to be a registered user to perform this action"
1229 1405 msgstr ""
1230 1406
1231 #: rhodecode/lib/auth.py:1227
1407 #: rhodecode/lib/auth.py:1302
1232 1408 #, python-format
1233 1409 msgid "Action not supported for %s."
1234 1410 msgstr ""
1235 1411
1236 #: rhodecode/lib/auth.py:1264
1412 #: rhodecode/lib/auth.py:1339
1237 1413 msgid "You need to be signed in to view this page"
1238 1414 msgstr ""
1239 1415
1240 #: rhodecode/lib/base.py:513
1416 #: rhodecode/lib/base.py:511
1241 1417 #, python-format
1242 1418 msgid "The repository at %(repo_name)s cannot be located."
1243 1419 msgstr ""
1244 1420
1245 1421 #: rhodecode/lib/diffs.py:71
1246 1422 msgid "Binary file"
1247 1423 msgstr ""
1248 1424
1249 1425 #: rhodecode/lib/diffs.py:91
1250 1426 msgid "Changeset was too big and was cut off, use diff menu to display this diff"
1251 1427 msgstr ""
1252 1428
1253 1429 #: rhodecode/lib/diffs.py:102
1254 1430 msgid "No changes detected"
1255 1431 msgstr ""
1256 1432
1257 #: rhodecode/lib/helpers.py:1246
1433 #: rhodecode/lib/helpers.py:1434
1258 1434 #, python-format
1259 1435 msgid " and %s more"
1260 1436 msgstr ""
1261 1437
1262 #: rhodecode/lib/helpers.py:1250
1438 #: rhodecode/lib/helpers.py:1438
1263 1439 msgid "No Files"
1264 1440 msgstr ""
1265 1441
1266 #: rhodecode/lib/helpers.py:1323
1442 #: rhodecode/lib/helpers.py:1511
1267 1443 msgid "new file"
1268 1444 msgstr ""
1269 1445
1270 #: rhodecode/lib/helpers.py:1326
1446 #: rhodecode/lib/helpers.py:1514
1271 1447 msgid "mod"
1272 1448 msgstr ""
1273 1449
1274 #: rhodecode/lib/helpers.py:1329
1450 #: rhodecode/lib/helpers.py:1517
1275 1451 msgid "del"
1276 1452 msgstr ""
1277 1453
1278 #: rhodecode/lib/helpers.py:1332
1454 #: rhodecode/lib/helpers.py:1520
1279 1455 msgid "rename"
1280 1456 msgstr ""
1281 1457
1282 #: rhodecode/lib/helpers.py:1337
1458 #: rhodecode/lib/helpers.py:1525
1283 1459 msgid "chmod"
1284 1460 msgstr ""
1285 1461
1286 #: rhodecode/lib/helpers.py:1579
1462 #: rhodecode/lib/helpers.py:1767
1287 1463 msgid ""
1288 1464 "Example filter terms:\n"
1289 1465 " repository:vcs\n"
1290 1466 " username:marcin\n"
1291 1467 " action:*push*\n"
1292 1468 " ip:127.0.0.1\n"
1293 1469 " date:20120101\n"
1294 1470 " date:[20120101100000 TO 20120102]\n"
1295 1471 "\n"
1296 1472 "Generate wildcards using '*' character:\n"
1297 1473 " \"repository:vcs*\" - search everything starting with 'vcs'\n"
1298 1474 " \"repository:*vcs*\" - search for repository containing 'vcs'\n"
1299 1475 "\n"
1300 1476 "Optional AND / OR operators in queries\n"
1301 1477 " \"repository:vcs OR repository:test\"\n"
1302 1478 " \"username:test AND repository:test*\"\n"
1303 1479 msgstr ""
1304 1480
1305 #: rhodecode/lib/helpers.py:1599
1481 #: rhodecode/lib/helpers.py:1787
1306 1482 #, python-format
1307 1483 msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
1308 1484 msgstr ""
1309 1485
1310 #: rhodecode/lib/utils2.py:446
1486 #: rhodecode/lib/utils2.py:453
1311 1487 #, python-format
1312 1488 msgid "%d year"
1313 1489 msgid_plural "%d years"
1314 1490 msgstr[0] ""
1315 1491 msgstr[1] ""
1316 1492
1317 #: rhodecode/lib/utils2.py:447
1493 #: rhodecode/lib/utils2.py:454
1318 1494 #, python-format
1319 1495 msgid "%d month"
1320 1496 msgid_plural "%d months"
1321 1497 msgstr[0] ""
1322 1498 msgstr[1] ""
1323 1499
1324 #: rhodecode/lib/utils2.py:448
1500 #: rhodecode/lib/utils2.py:455
1325 1501 #, python-format
1326 1502 msgid "%d day"
1327 1503 msgid_plural "%d days"
1328 1504 msgstr[0] ""
1329 1505 msgstr[1] ""
1330 1506
1331 #: rhodecode/lib/utils2.py:449
1507 #: rhodecode/lib/utils2.py:456
1332 1508 #, python-format
1333 1509 msgid "%d hour"
1334 1510 msgid_plural "%d hours"
1335 1511 msgstr[0] ""
1336 1512 msgstr[1] ""
1337 1513
1338 #: rhodecode/lib/utils2.py:450
1514 #: rhodecode/lib/utils2.py:457
1339 1515 #, python-format
1340 1516 msgid "%d minute"
1341 1517 msgid_plural "%d minutes"
1342 1518 msgstr[0] ""
1343 1519 msgstr[1] ""
1344 1520
1345 #: rhodecode/lib/utils2.py:451
1521 #: rhodecode/lib/utils2.py:458
1346 1522 #, python-format
1347 1523 msgid "%d second"
1348 1524 msgid_plural "%d seconds"
1349 1525 msgstr[0] ""
1350 1526 msgstr[1] ""
1351 1527
1352 #: rhodecode/lib/utils2.py:469
1528 #: rhodecode/lib/utils2.py:476
1353 1529 #, python-format
1354 1530 msgid "in %s"
1355 1531 msgstr ""
1356 1532
1357 #: rhodecode/lib/utils2.py:475
1533 #: rhodecode/lib/utils2.py:482
1358 1534 #, python-format
1359 1535 msgid "%s ago"
1360 1536 msgstr ""
1361 1537
1362 #: rhodecode/lib/utils2.py:485
1538 #: rhodecode/lib/utils2.py:492
1363 1539 #, python-format
1364 1540 msgid "%s, %s ago"
1365 1541 msgstr ""
1366 1542
1367 #: rhodecode/lib/utils2.py:487
1543 #: rhodecode/lib/utils2.py:494
1368 1544 #, python-format
1369 1545 msgid "in %s, %s"
1370 1546 msgstr ""
1371 1547
1372 #: rhodecode/lib/utils2.py:489
1548 #: rhodecode/lib/utils2.py:496
1373 1549 #, python-format
1374 1550 msgid "%s and %s"
1375 1551 msgstr ""
1376 1552
1377 #: rhodecode/lib/utils2.py:491
1553 #: rhodecode/lib/utils2.py:498
1378 1554 #, python-format
1379 1555 msgid "%s and %s ago"
1380 1556 msgstr ""
1381 1557
1382 #: rhodecode/lib/utils2.py:493
1558 #: rhodecode/lib/utils2.py:500
1383 1559 #, python-format
1384 1560 msgid "in %s and %s"
1385 1561 msgstr ""
1386 1562
1387 #: rhodecode/lib/utils2.py:497
1563 #: rhodecode/lib/utils2.py:504
1388 1564 msgid "just now"
1389 1565 msgstr ""
1390 1566
1391 #: rhodecode/lib/auth_modules/__init__.py:690
1392 msgid "This provider is currently disabled"
1393 msgstr ""
1394
1395 #: rhodecode/lib/auth_modules/__init__.py:708
1396 msgid "Your external identity is now connected with your account"
1397 msgstr ""
1398
1399 #: rhodecode/lib/auth_modules/__init__.py:712
1400 msgid "No external user id found? Perhaps permissionsfor authentication are set incorrectly"
1401 msgstr ""
1402
1403 #: rhodecode/lib/auth_modules/auth_crowd.py:60
1404 msgid "The FQDN or IP of the Atlassian CROWD Server"
1405 msgstr ""
1406
1407 #: rhodecode/lib/auth_modules/auth_crowd.py:61
1408 msgid "Host"
1409 msgstr ""
1410
1411 #: rhodecode/lib/auth_modules/auth_crowd.py:66
1412 msgid "The Port in use by the Atlassian CROWD Server"
1413 msgstr ""
1414
1415 #: rhodecode/lib/auth_modules/auth_crowd.py:67
1416 #: rhodecode/lib/auth_modules/auth_ldap.py:81
1417 msgid "Port"
1418 msgstr ""
1419
1420 #: rhodecode/lib/auth_modules/auth_crowd.py:73
1421 msgid "The Application Name to authenticate to CROWD"
1422 msgstr ""
1423
1424 #: rhodecode/lib/auth_modules/auth_crowd.py:74
1425 msgid "Application Name"
1426 msgstr ""
1427
1428 #: rhodecode/lib/auth_modules/auth_crowd.py:79
1429 msgid "The password to authenticate to CROWD"
1430 msgstr ""
1431
1432 #: rhodecode/lib/auth_modules/auth_crowd.py:80
1433 msgid "Application Password"
1434 msgstr ""
1435
1436 #: rhodecode/lib/auth_modules/auth_crowd.py:85
1437 msgid "A comma separated list of group names that identify users as RhodeCode Administrators"
1438 msgstr ""
1439
1440 #: rhodecode/lib/auth_modules/auth_crowd.py:88
1441 msgid "Admin Groups"
1442 msgstr ""
1443
1444 #: rhodecode/lib/auth_modules/auth_crowd.py:208
1445 msgid "CROWD"
1446 msgstr ""
1447
1448 #: rhodecode/lib/auth_modules/auth_jasig_cas.py:63
1449 msgid "The url of the Jasig CAS REST service"
1450 msgstr ""
1451
1452 #: rhodecode/lib/auth_modules/auth_jasig_cas.py:64
1453 #: rhodecode/templates/admin/gists/show.html:21
1454 msgid "URL"
1455 msgstr ""
1456
1457 #: rhodecode/lib/auth_modules/auth_jasig_cas.py:90
1458 msgid "Jasig-CAS"
1459 msgstr ""
1460
1461 #: rhodecode/lib/auth_modules/auth_ldap.py:74
1462 msgid "Host of the LDAP Server"
1463 msgstr ""
1464
1465 #: rhodecode/lib/auth_modules/auth_ldap.py:75
1466 msgid "LDAP Host"
1467 msgstr ""
1468
1469 #: rhodecode/lib/auth_modules/auth_ldap.py:80
1470 msgid "Port that the LDAP server is listening on"
1471 msgstr ""
1472
1473 #: rhodecode/lib/auth_modules/auth_ldap.py:87
1474 msgid "User to connect to LDAP"
1475 msgstr ""
1476
1477 #: rhodecode/lib/auth_modules/auth_ldap.py:89
1478 msgid "Account"
1479 msgstr ""
1480
1481 #: rhodecode/lib/auth_modules/auth_ldap.py:94
1482 msgid "Password to connect to LDAP"
1483 msgstr ""
1484
1485 #: rhodecode/lib/auth_modules/auth_ldap.py:96 rhodecode/templates/login.html:45
1486 #: rhodecode/templates/register.html:43
1487 #: rhodecode/templates/admin/my_account/my_account.html:30
1488 #: rhodecode/templates/admin/users/user_add.html:44
1489 #: rhodecode/templates/base/base.html:312
1490 #: rhodecode/templates/debug_style/login.html:45
1491 msgid "Password"
1492 msgstr ""
1493
1494 #: rhodecode/lib/auth_modules/auth_ldap.py:101
1495 msgid "TLS Type"
1496 msgstr ""
1497
1498 #: rhodecode/lib/auth_modules/auth_ldap.py:102
1499 msgid "Connection Security"
1500 msgstr ""
1501
1502 #: rhodecode/lib/auth_modules/auth_ldap.py:108
1503 msgid "Require Cert over TLS?"
1504 msgstr ""
1505
1506 #: rhodecode/lib/auth_modules/auth_ldap.py:109
1507 msgid "Certificate Checks"
1508 msgstr ""
1509
1510 #: rhodecode/lib/auth_modules/auth_ldap.py:115
1511 msgid "Base DN to search (e.g., dc=mydomain,dc=com)"
1512 msgstr ""
1513
1514 #: rhodecode/lib/auth_modules/auth_ldap.py:117
1515 msgid "Base DN"
1516 msgstr ""
1517
1518 #: rhodecode/lib/auth_modules/auth_ldap.py:122
1519 msgid "Filter to narrow results (e.g., ou=Users, etc)"
1520 msgstr ""
1521
1522 #: rhodecode/lib/auth_modules/auth_ldap.py:124
1523 msgid "LDAP Search Filter"
1524 msgstr ""
1525
1526 #: rhodecode/lib/auth_modules/auth_ldap.py:129
1527 msgid "How deep to search LDAP"
1528 msgstr ""
1529
1530 #: rhodecode/lib/auth_modules/auth_ldap.py:130
1531 msgid "LDAP Search Scope"
1532 msgstr ""
1533
1534 #: rhodecode/lib/auth_modules/auth_ldap.py:136
1535 msgid "LDAP Attribute to map to user name"
1536 msgstr ""
1537
1538 #: rhodecode/lib/auth_modules/auth_ldap.py:137
1539 msgid "Login Attribute"
1540 msgstr ""
1541
1542 #: rhodecode/lib/auth_modules/auth_ldap.py:138
1543 msgid "The LDAP Login attribute of the CN must be specified"
1544 msgstr ""
1545
1546 #: rhodecode/lib/auth_modules/auth_ldap.py:143
1547 msgid "LDAP Attribute to map to first name"
1548 msgstr ""
1549
1550 #: rhodecode/lib/auth_modules/auth_ldap.py:145
1551 msgid "First Name Attribute"
1552 msgstr ""
1553
1554 #: rhodecode/lib/auth_modules/auth_ldap.py:150
1555 msgid "LDAP Attribute to map to last name"
1556 msgstr ""
1557
1558 #: rhodecode/lib/auth_modules/auth_ldap.py:152
1559 msgid "Last Name Attribute"
1560 msgstr ""
1561
1562 #: rhodecode/lib/auth_modules/auth_ldap.py:157
1563 msgid "LDAP Attribute to map to email address"
1564 msgstr ""
1565
1566 #: rhodecode/lib/auth_modules/auth_ldap.py:159
1567 msgid "Email Attribute"
1568 msgstr ""
1569
1570 #: rhodecode/lib/auth_modules/auth_ldap.py:335
1571 msgid "LDAP"
1572 msgstr ""
1573
1574 #: rhodecode/lib/auth_modules/auth_pam.py:60
1575 msgid "PAM service name to use for authentication."
1576 msgstr ""
1577
1578 #: rhodecode/lib/auth_modules/auth_pam.py:61
1579 msgid "PAM service name"
1580 msgstr ""
1581
1582 #: rhodecode/lib/auth_modules/auth_pam.py:66
1583 msgid "Regular expression for extracting user name/email etc. from Unix userinfo."
1584 msgstr ""
1585
1586 #: rhodecode/lib/auth_modules/auth_pam.py:68
1587 msgid "Gecos Regex"
1588 msgstr ""
1589
1590 #: rhodecode/lib/auth_modules/auth_pam.py:94
1591 msgid "PAM"
1592 msgstr ""
1593
1594 #: rhodecode/lib/auth_modules/auth_rhodecode.py:66
1595 msgid "Rhodecode"
1596 msgstr ""
1597
1598 1567 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:570
1599 1568 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:582
1600 1569 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:599
1601 1570 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:684
1602 1571 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:657
1603 1572 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:659
1604 1573 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:682
1605 1574 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:683
1606 1575 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:700
1607 1576 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:717
1608 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:735
1609 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:738
1610 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:740
1611 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:740
1612 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:767
1613 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:777
1614 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:820
1615 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:819
1616 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:820
1617 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:820
1618 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:826
1619 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:948
1620 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:973 rhodecode/model/db.py:2243
1577 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:733
1578 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:736
1579 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:738
1580 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:738
1581 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:765
1582 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:775
1583 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:818
1584 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:817
1585 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:818
1586 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:818
1587 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:824
1588 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:946
1589 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:971 rhodecode/model/db.py:2291
1621 1590 msgid "Repository no access"
1622 1591 msgstr ""
1623 1592
1624 1593 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:571
1625 1594 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:583
1626 1595 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:600
1627 1596 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:685
1628 1597 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:658
1629 1598 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:660
1630 1599 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:683
1631 1600 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:684
1632 1601 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:701
1633 1602 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:718
1634 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:736
1635 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:739
1636 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:741
1637 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:741
1638 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:768
1639 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:778
1640 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:821
1641 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:820
1642 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:821
1643 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:821
1644 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:827
1645 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:949
1646 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:974 rhodecode/model/db.py:2244
1603 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:734
1604 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:737
1605 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:739
1606 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:739
1607 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:766
1608 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:776
1609 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:819
1610 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:818
1611 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:819
1612 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:819
1613 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:825
1614 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:947
1615 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:972 rhodecode/model/db.py:2292
1647 1616 msgid "Repository read access"
1648 1617 msgstr ""
1649 1618
1650 1619 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:572
1651 1620 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:584
1652 1621 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:601
1653 1622 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:686
1654 1623 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:659
1655 1624 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:661
1656 1625 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:684
1657 1626 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:685
1658 1627 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:702
1659 1628 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:719
1660 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:737
1661 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:740
1662 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:742
1663 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:742
1664 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:769
1665 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:779
1666 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:822
1667 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:821
1668 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:822
1669 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:822
1670 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:828
1671 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:950
1672 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:975 rhodecode/model/db.py:2245
1629 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:735
1630 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:738
1631 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:740
1632 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:740
1633 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:767
1634 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:777
1635 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:820
1636 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:819
1637 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:820
1638 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:820
1639 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:826
1640 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:948
1641 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:973 rhodecode/model/db.py:2293
1673 1642 msgid "Repository write access"
1674 1643 msgstr ""
1675 1644
1676 1645 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:573
1677 1646 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:585
1678 1647 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:602
1679 1648 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:687
1680 1649 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:660
1681 1650 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:662
1682 1651 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:685
1683 1652 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:686
1684 1653 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:703
1685 1654 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:720
1686 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:738
1687 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:741
1688 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:743
1689 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:743
1690 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:770
1691 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:780
1692 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:823
1693 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:822
1694 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:823
1695 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:823
1696 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:829
1697 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:951
1698 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:976 rhodecode/model/db.py:2246
1655 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:736
1656 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:739
1657 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:741
1658 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:741
1659 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:768
1660 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:778
1661 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:821
1662 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:820
1663 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:821
1664 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:821
1665 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:827
1666 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:949
1667 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:974 rhodecode/model/db.py:2294
1699 1668 msgid "Repository admin access"
1700 1669 msgstr ""
1701 1670
1702 1671 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:575
1703 1672 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:587
1704 1673 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:604
1705 1674 msgid "Repositories Group no access"
1706 1675 msgstr ""
1707 1676
1708 1677 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:576
1709 1678 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:588
1710 1679 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:605
1711 1680 msgid "Repositories Group read access"
1712 1681 msgstr ""
1713 1682
1714 1683 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:577
1715 1684 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:589
1716 1685 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:606
1717 1686 msgid "Repositories Group write access"
1718 1687 msgstr ""
1719 1688
1720 1689 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:578
1721 1690 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:590
1722 1691 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:607
1723 1692 msgid "Repositories Group admin access"
1724 1693 msgstr ""
1725 1694
1726 1695 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:580
1727 1696 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:592
1728 1697 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:609
1729 1698 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:694
1730 1699 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:655
1731 1700 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:657
1732 1701 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:680
1733 1702 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:681
1734 1703 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:698
1735 1704 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:715
1736 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:733
1737 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:736
1738 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:738
1705 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:731
1706 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:734
1707 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:736
1739 1708 msgid "RhodeCode Administrator"
1740 1709 msgstr ""
1741 1710
1742 1711 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:581
1743 1712 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:593
1744 1713 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:610
1745 1714 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:695
1746 1715 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:678
1747 1716 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:680
1748 1717 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:703
1749 1718 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:704
1750 1719 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:721
1751 1720 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:738
1752 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:756
1753 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:759
1754 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:761
1755 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:761
1756 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:788
1757 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:798
1758 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:841
1759 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:840
1760 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:841
1761 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:841
1762 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:847
1763 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:969
1764 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:994 rhodecode/model/db.py:2264
1721 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:754
1722 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:757
1723 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:759
1724 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:759
1725 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:786
1726 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:796
1727 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:839
1728 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:838
1729 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:839
1730 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:839
1731 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:845
1732 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:967
1733 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:992 rhodecode/model/db.py:2312
1765 1734 msgid "Repository creation disabled"
1766 1735 msgstr ""
1767 1736
1768 1737 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:582
1769 1738 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:594
1770 1739 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:611
1771 1740 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:696
1772 1741 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:679
1773 1742 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:681
1774 1743 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:704
1775 1744 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:705
1776 1745 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:722
1777 1746 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:739
1778 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:757
1779 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:760
1780 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:762
1781 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:762
1782 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:789
1783 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:799
1784 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:842
1785 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:841
1786 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:842
1787 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:842
1788 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:848
1789 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:970
1790 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:995 rhodecode/model/db.py:2265
1747 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:755
1748 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:758
1749 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:760
1750 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:760
1751 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:787
1752 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:797
1753 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:840
1754 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:839
1755 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:840
1756 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:840
1757 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:846
1758 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:968
1759 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:993 rhodecode/model/db.py:2313
1791 1760 msgid "Repository creation enabled"
1792 1761 msgstr ""
1793 1762
1794 1763 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:583
1795 1764 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:595
1796 1765 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:612
1797 1766 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:697
1798 1767 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:681
1799 1768 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:683
1800 1769 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:706
1801 1770 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:707
1802 1771 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:724
1803 1772 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:741
1804 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:761
1805 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:764
1806 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:766
1807 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:766
1808 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:793
1809 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:803
1810 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:846
1811 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:845
1812 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:846
1813 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:846
1814 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:852
1815 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:974
1816 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:999 rhodecode/model/db.py:2269
1773 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:759
1774 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:762
1775 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:764
1776 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:764
1777 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:791
1778 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:801
1779 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:844
1780 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:843
1781 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:844
1782 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:844
1783 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:850
1784 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:972
1785 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:997 rhodecode/model/db.py:2317
1817 1786 msgid "Repository forking disabled"
1818 1787 msgstr ""
1819 1788
1820 1789 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:584
1821 1790 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:596
1822 1791 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:613
1823 1792 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:698
1824 1793 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:682
1825 1794 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:684
1826 1795 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:707
1827 1796 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:708
1828 1797 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:725
1829 1798 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:742
1830 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:762
1831 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:765
1832 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:767
1833 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:767
1834 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:794
1835 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:804
1836 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:847
1837 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:846
1838 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:847
1839 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:847
1840 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:853
1841 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:975
1842 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1000 rhodecode/model/db.py:2270
1799 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:760
1800 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:763
1801 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:765
1802 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:765
1803 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:792
1804 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:802
1805 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:845
1806 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:844
1807 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:845
1808 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:845
1809 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:851
1810 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:973
1811 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:998 rhodecode/model/db.py:2318
1843 1812 msgid "Repository forking enabled"
1844 1813 msgstr ""
1845 1814
1846 1815 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:585
1847 1816 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:597
1848 1817 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:614
1849 1818 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:699
1850 1819 msgid "Register disabled"
1851 1820 msgstr ""
1852 1821
1853 1822 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:586
1854 1823 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:598
1855 1824 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:615
1856 1825 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:700
1857 1826 msgid "Register new user with RhodeCode with manual activation"
1858 1827 msgstr ""
1859 1828
1860 1829 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:589
1861 1830 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:601
1862 1831 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:618
1863 1832 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:703
1864 1833 msgid "Register new user with RhodeCode with auto activation"
1865 1834 msgstr ""
1866 1835
1867 1836 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:845
1868 1837 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:865
1869 1838 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:874
1870 1839 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:959
1871 1840 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:995
1872 1841 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:997
1873 1842 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:1020
1874 1843 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:1021
1875 1844 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:1038
1876 1845 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:1055
1877 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1076
1878 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1079
1879 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1090
1880 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1093
1881 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1123
1882 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1137
1883 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1180
1884 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1179
1885 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1188
1886 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1188
1887 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1198
1888 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1320
1889 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1345 rhodecode/model/db.py:2900
1846 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1074
1847 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1077
1848 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1088
1849 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1091
1850 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1121
1851 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1135
1852 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1178
1853 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1177
1854 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1186
1855 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1186
1856 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1196
1857 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1318
1858 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1343 rhodecode/model/db.py:2950
1890 1859 msgid "Not Reviewed"
1891 1860 msgstr ""
1892 1861
1893 1862 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:846
1894 1863 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:866
1895 1864 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:875
1896 1865 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:960
1897 1866 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:996
1898 1867 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:998
1899 1868 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:1021
1900 1869 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:1022
1901 1870 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:1039
1902 1871 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:1056
1903 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1077
1904 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1080
1905 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1091
1906 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1094
1907 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1124
1908 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1138
1909 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1181
1910 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1180
1911 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1189
1912 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1189
1913 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1199
1914 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1321
1915 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1346 rhodecode/model/db.py:2901
1872 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1075
1873 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1078
1874 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1089
1875 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1092
1876 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1122
1877 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1136
1878 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1179
1879 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1178
1880 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1187
1881 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1187
1882 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1197
1883 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1319
1884 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1344 rhodecode/model/db.py:2951
1916 1885 msgid "Approved"
1917 1886 msgstr ""
1918 1887
1919 1888 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:847
1920 1889 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:867
1921 1890 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:876
1922 1891 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:961
1923 1892 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:997
1924 1893 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:999
1925 1894 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:1022
1926 1895 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:1023
1927 1896 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:1040
1928 1897 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:1057
1929 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1078
1930 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1081
1931 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1092
1932 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1095
1933 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1125
1934 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1139
1935 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1182
1936 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1181
1937 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1190
1938 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1190
1939 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1200
1940 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1322
1941 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1347 rhodecode/model/db.py:2902
1898 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1076
1899 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1079
1900 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1090
1901 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1093
1902 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1123
1903 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1137
1904 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1180
1905 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1179
1906 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1188
1907 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1188
1908 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1198
1909 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1320
1910 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1345 rhodecode/model/db.py:2952
1942 1911 msgid "Rejected"
1943 1912 msgstr ""
1944 1913
1945 1914 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:848
1946 1915 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:868
1947 1916 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:877
1948 1917 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:962
1949 1918 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:998
1950 1919 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:1000
1951 1920 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:1023
1952 1921 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:1024
1953 1922 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:1041
1954 1923 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:1058
1955 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1079
1956 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1082
1957 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1093
1958 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1096
1959 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1126
1960 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1140
1961 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1183
1962 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1182
1963 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1191
1964 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1191
1965 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1201
1966 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1323
1967 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1348 rhodecode/model/db.py:2903
1924 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1077
1925 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1080
1926 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1091
1927 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1094
1928 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1124
1929 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1138
1930 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1181
1931 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1180
1932 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1189
1933 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1189
1934 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1199
1935 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1321
1936 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1346 rhodecode/model/db.py:2953
1968 1937 msgid "Under Review"
1969 1938 msgstr ""
1970 1939
1971 1940 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:689
1972 1941 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:662
1973 1942 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:664
1974 1943 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:687
1975 1944 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:688
1976 1945 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:705
1977 1946 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:722
1947 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:738
1948 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:741
1949 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:743
1950 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:743
1951 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:770
1952 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:780
1953 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:823
1954 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:822
1955 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:823
1956 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:823
1957 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:829
1958 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:951
1959 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:976 rhodecode/model/db.py:2296
1960 msgid "Repository group no access"
1961 msgstr ""
1962
1963 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:690
1964 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:663
1965 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:665
1966 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:688
1967 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:689
1968 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:706
1969 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:723
1970 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:739
1971 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:742
1972 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:744
1973 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:744
1974 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:771
1975 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:781
1976 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:824
1977 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:823
1978 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:824
1979 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:824
1980 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:830
1981 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:952
1982 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:977 rhodecode/model/db.py:2297
1983 msgid "Repository group read access"
1984 msgstr ""
1985
1986 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:691
1987 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:664
1988 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:666
1989 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:689
1990 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:690
1991 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:707
1992 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:724
1978 1993 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:740
1979 1994 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:743
1980 1995 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:745
1981 1996 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:745
1982 1997 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:772
1983 1998 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:782
1984 1999 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:825
1985 2000 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:824
1986 2001 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:825
1987 2002 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:825
1988 2003 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:831
1989 2004 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:953
1990 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:978 rhodecode/model/db.py:2248
1991 msgid "Repository group no access"
1992 msgstr ""
1993
1994 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:690
1995 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:663
1996 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:665
1997 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:688
1998 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:689
1999 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:706
2000 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:723
2005 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:978 rhodecode/model/db.py:2298
2006 msgid "Repository group write access"
2007 msgstr ""
2008
2009 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:692
2010 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:665
2011 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:667
2012 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:690
2013 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:691
2014 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:708
2015 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:725
2001 2016 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:741
2002 2017 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:744
2003 2018 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:746
2004 2019 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:746
2005 2020 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:773
2006 2021 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:783
2007 2022 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:826
2008 2023 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:825
2009 2024 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:826
2010 2025 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:826
2011 2026 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:832
2012 2027 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:954
2013 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:979 rhodecode/model/db.py:2249
2014 msgid "Repository group read access"
2015 msgstr ""
2016
2017 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:691
2018 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:664
2019 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:666
2020 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:689
2021 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:690
2022 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:707
2023 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:724
2024 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:742
2025 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:745
2026 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:747
2027 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:747
2028 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:774
2029 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:784
2030 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:827
2031 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:826
2032 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:827
2033 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:827
2034 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:833
2035 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:955
2036 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:980 rhodecode/model/db.py:2250
2037 msgid "Repository group write access"
2038 msgstr ""
2039
2040 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:692
2041 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:665
2042 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:667
2043 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:690
2044 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:691
2045 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:708
2046 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:725
2028 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:979 rhodecode/model/db.py:2299
2029 msgid "Repository group admin access"
2030 msgstr ""
2031
2032 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:667
2033 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:669
2034 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:692
2035 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:693
2036 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:710
2037 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:727
2047 2038 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:743
2048 2039 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:746
2049 2040 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:748
2050 2041 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:748
2051 2042 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:775
2052 2043 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:785
2053 2044 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:828
2054 2045 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:827
2055 2046 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:828
2056 2047 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:828
2057 2048 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:834
2058 2049 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:956
2059 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:981 rhodecode/model/db.py:2251
2060 msgid "Repository group admin access"
2061 msgstr ""
2062
2063 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:667
2064 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:669
2065 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:692
2066 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:693
2067 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:710
2068 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:727
2050 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:981 rhodecode/model/db.py:2301
2051 msgid "User group no access"
2052 msgstr ""
2053
2054 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:668
2055 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:670
2056 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:693
2057 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:694
2058 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:711
2059 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:728
2060 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:744
2061 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:747
2062 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:749
2063 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:749
2064 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:776
2065 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:786
2066 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:829
2067 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:828
2068 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:829
2069 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:829
2070 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:835
2071 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:957
2072 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:982 rhodecode/model/db.py:2302
2073 msgid "User group read access"
2074 msgstr ""
2075
2076 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:669
2077 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:671
2078 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:694
2079 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:695
2080 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:712
2081 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:729
2069 2082 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:745
2070 2083 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:748
2071 2084 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:750
2072 2085 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:750
2073 2086 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:777
2074 2087 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:787
2075 2088 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:830
2076 2089 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:829
2077 2090 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:830
2078 2091 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:830
2079 2092 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:836
2080 2093 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:958
2081 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:983 rhodecode/model/db.py:2253
2082 msgid "User group no access"
2083 msgstr ""
2084
2085 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:668
2086 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:670
2087 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:693
2088 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:694
2089 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:711
2090 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:728
2094 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:983 rhodecode/model/db.py:2303
2095 msgid "User group write access"
2096 msgstr ""
2097
2098 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:670
2099 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:672
2100 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:695
2101 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:696
2102 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:713
2103 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:730
2091 2104 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:746
2092 2105 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:749
2093 2106 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:751
2094 2107 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:751
2095 2108 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:778
2096 2109 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:788
2097 2110 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:831
2098 2111 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:830
2099 2112 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:831
2100 2113 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:831
2101 2114 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:837
2102 2115 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:959
2103 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:984 rhodecode/model/db.py:2254
2104 msgid "User group read access"
2105 msgstr ""
2106
2107 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:669
2108 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:671
2109 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:694
2110 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:695
2111 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:712
2112 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:729
2113 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:747
2114 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:750
2115 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:752
2116 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:752
2117 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:779
2118 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:789
2119 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:832
2120 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:831
2121 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:832
2122 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:832
2123 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:838
2124 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:960
2125 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:985 rhodecode/model/db.py:2255
2126 msgid "User group write access"
2127 msgstr ""
2128
2129 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:670
2130 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:672
2131 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:695
2132 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:696
2133 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:713
2134 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:730
2116 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:984 rhodecode/model/db.py:2304
2117 msgid "User group admin access"
2118 msgstr ""
2119
2120 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:672
2121 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:674
2122 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:697
2123 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:698
2124 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:715
2125 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:732
2135 2126 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:748
2136 2127 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:751
2137 2128 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:753
2138 2129 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:753
2139 2130 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:780
2140 2131 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:790
2141 2132 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:833
2142 2133 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:832
2143 2134 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:833
2144 2135 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:833
2145 2136 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:839
2146 2137 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:961
2147 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:986 rhodecode/model/db.py:2256
2148 msgid "User group admin access"
2149 msgstr ""
2150
2151 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:672
2152 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:674
2153 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:697
2154 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:698
2155 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:715
2156 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:732
2157 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:750
2158 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:753
2159 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:755
2160 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:755
2161 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:782
2162 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:792
2163 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:835
2164 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:834
2165 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:835
2166 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:835
2167 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:841
2168 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:963
2169 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:988 rhodecode/model/db.py:2258
2138 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:986 rhodecode/model/db.py:2306
2170 2139 msgid "Repository Group creation disabled"
2171 2140 msgstr ""
2172 2141
2173 2142 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:673
2174 2143 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:675
2175 2144 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:698
2176 2145 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:699
2177 2146 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:716
2178 2147 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:733
2148 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:749
2149 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:752
2150 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:754
2151 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:754
2152 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:781
2153 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:791
2154 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:834
2155 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:833
2156 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:834
2157 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:834
2158 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:840
2159 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:962
2160 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:987 rhodecode/model/db.py:2307
2161 msgid "Repository Group creation enabled"
2162 msgstr ""
2163
2164 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:675
2165 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:677
2166 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:700
2167 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:701
2168 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:718
2169 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:735
2179 2170 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:751
2180 2171 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:754
2181 2172 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:756
2182 2173 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:756
2183 2174 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:783
2184 2175 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:793
2185 2176 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:836
2186 2177 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:835
2187 2178 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:836
2188 2179 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:836
2189 2180 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:842
2190 2181 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:964
2191 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:989 rhodecode/model/db.py:2259
2192 msgid "Repository Group creation enabled"
2193 msgstr ""
2194
2195 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:675
2196 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:677
2197 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:700
2198 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:701
2199 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:718
2200 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:735
2201 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:753
2202 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:756
2203 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:758
2204 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:758
2205 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:785
2206 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:795
2207 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:838
2208 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:837
2209 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:838
2210 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:838
2211 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:844
2212 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:966
2213 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:991 rhodecode/model/db.py:2261
2182 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:989 rhodecode/model/db.py:2309
2214 2183 msgid "User Group creation disabled"
2215 2184 msgstr ""
2216 2185
2217 2186 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:676
2218 2187 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:678
2219 2188 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:701
2220 2189 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:702
2221 2190 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:719
2222 2191 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:736
2223 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:754
2224 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:757
2225 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:759
2226 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:759
2227 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:786
2228 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:796
2229 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:839
2230 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:838
2231 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:839
2232 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:839
2233 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:845
2234 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:967
2235 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:992 rhodecode/model/db.py:2262
2192 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:752
2193 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:755
2194 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:757
2195 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:757
2196 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:784
2197 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:794
2198 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:837
2199 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:836
2200 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:837
2201 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:837
2202 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:843
2203 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:965
2204 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:990 rhodecode/model/db.py:2310
2236 2205 msgid "User Group creation enabled"
2237 2206 msgstr ""
2238 2207
2239 2208 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:684
2240 2209 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:686
2241 2210 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:709
2242 2211 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:710
2243 2212 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:727
2244 2213 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:744
2214 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:762
2215 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:765
2216 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:767
2217 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:767
2218 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:794
2219 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:804
2220 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:847
2221 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:846
2222 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:847
2223 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:847
2224 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:853
2225 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:975
2226 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1000 rhodecode/model/db.py:2320
2227 msgid "Registration disabled"
2228 msgstr ""
2229
2230 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:685
2231 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:687
2232 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:710
2233 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:711
2234 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:728
2235 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:745
2236 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:763
2237 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:766
2238 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:768
2239 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:768
2240 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:795
2241 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:805
2242 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:848
2243 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:847
2244 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:848
2245 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:848
2246 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:854
2247 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:976
2248 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1001 rhodecode/model/db.py:2321
2249 msgid "User Registration with manual account activation"
2250 msgstr ""
2251
2252 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:686
2253 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:688
2254 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:711
2255 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:712
2256 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:729
2257 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:746
2245 2258 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:764
2246 2259 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:767
2247 2260 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:769
2248 2261 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:769
2249 2262 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:796
2250 2263 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:806
2251 2264 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:849
2252 2265 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:848
2253 2266 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:849
2254 2267 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:849
2255 2268 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:855
2256 2269 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:977
2257 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1002 rhodecode/model/db.py:2272
2258 msgid "Registration disabled"
2259 msgstr ""
2260
2261 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:685
2262 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:687
2263 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:710
2264 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:711
2265 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:728
2266 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:745
2267 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:765
2268 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:768
2269 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:770
2270 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:770
2271 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:797
2272 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:807
2273 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:850
2274 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:849
2275 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:850
2276 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:850
2277 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:856
2278 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:978
2279 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1003 rhodecode/model/db.py:2273
2280 msgid "User Registration with manual account activation"
2281 msgstr ""
2282
2283 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:686
2284 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:688
2285 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:711
2286 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:712
2287 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:729
2288 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:746
2270 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1002 rhodecode/model/db.py:2322
2271 msgid "User Registration with automatic account activation"
2272 msgstr ""
2273
2274 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:688
2275 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:690
2276 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:713
2277 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:714
2278 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:731
2279 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:748
2289 2280 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:766
2290 2281 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:769
2291 2282 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:771
2292 2283 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:771
2293 2284 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:798
2294 2285 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:808
2295 2286 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:851
2296 2287 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:850
2297 2288 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:851
2298 2289 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:851
2299 2290 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:857
2300 2291 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:979
2301 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1004 rhodecode/model/db.py:2274
2302 msgid "User Registration with automatic account activation"
2303 msgstr ""
2304
2305 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:688
2306 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:690
2307 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:713
2308 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:714
2309 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:731
2310 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:748
2311 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:768
2312 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:771
2313 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:773
2314 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:773
2315 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:800
2316 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:810
2317 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:853
2318 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:852
2319 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:853
2320 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:853
2321 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:859
2322 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:981
2323 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1006 rhodecode/model/db.py:2276
2292 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1004 rhodecode/model/db.py:2324
2324 2293 msgid "Manual activation of external account"
2325 2294 msgstr ""
2326 2295
2327 2296 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:689
2328 2297 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:691
2329 2298 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:714
2330 2299 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:715
2331 2300 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:732
2332 2301 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:749
2333 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:769
2334 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:772
2335 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:774
2336 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:774
2337 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:801
2302 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:767
2303 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:770
2304 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:772
2305 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:772
2306 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:799
2307 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:809
2308 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:852
2309 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:851
2310 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:852
2311 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:852
2312 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:858
2313 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:980
2314 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1005 rhodecode/model/db.py:2325
2315 msgid "Automatic activation of external account"
2316 msgstr ""
2317
2318 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:756
2319 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:759
2320 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:761
2321 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:761
2322 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:788
2323 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:798
2324 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:841
2325 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:840
2326 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:841
2327 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:841
2328 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:847
2329 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:969
2330 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:994 rhodecode/model/db.py:2314
2331 msgid "Repository creation enabled with write permission to a repository group"
2332 msgstr ""
2333
2334 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:757
2335 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:760
2336 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:762
2337 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:762
2338 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:789
2339 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:799
2340 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:842
2341 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:841
2342 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:842
2343 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:842
2344 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:848
2345 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:970
2346 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:995 rhodecode/model/db.py:2315
2347 msgid "Repository creation disabled with write permission to a repository group"
2348 msgstr ""
2349
2350 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:736
2351 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:763
2352 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:773
2353 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:816
2354 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:815
2355 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:816
2356 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:816
2357 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:822
2358 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:944
2359 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:969 rhodecode/model/db.py:2289
2360 msgid "RhodeCode Super Administrator"
2361 msgstr ""
2362
2338 2363 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:811
2339 2364 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:854
2340 2365 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:853
2341 2366 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:854
2342 2367 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:854
2343 2368 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:860
2344 2369 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:982
2345 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1007 rhodecode/model/db.py:2277
2346 msgid "Automatic activation of external account"
2347 msgstr ""
2348
2349 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:758
2350 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:761
2351 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:763
2352 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:763
2353 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:790
2354 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:800
2355 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:843
2356 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:842
2357 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:843
2358 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:843
2359 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:849
2360 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:971
2361 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:996 rhodecode/model/db.py:2266
2362 msgid "Repository creation enabled with write permission to a repository group"
2363 msgstr ""
2364
2365 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:759
2366 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:762
2367 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:764
2368 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:764
2369 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:791
2370 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:801
2371 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:844
2372 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:843
2373 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:844
2374 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:844
2375 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:850
2376 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:972
2377 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:997 rhodecode/model/db.py:2267
2378 msgid "Repository creation disabled with write permission to a repository group"
2379 msgstr ""
2380
2381 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:738
2382 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:765
2383 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:775
2384 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:818
2385 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:817
2386 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:818
2387 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:818
2388 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:824
2389 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:946
2390 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:971 rhodecode/model/db.py:2241
2391 msgid "RhodeCode Super Administrator"
2392 msgstr ""
2393
2394 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:813
2395 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:856
2396 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:855
2397 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:856
2398 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:856
2399 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:862
2400 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:984
2401 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1009 rhodecode/model/db.py:2279
2370 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1007 rhodecode/model/db.py:2327
2402 2371 msgid "Inherit object permissions from default user disabled"
2403 2372 msgstr ""
2404 2373
2405 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:814
2406 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:857
2407 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:856
2408 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:857
2409 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:857
2410 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:863
2411 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:985
2412 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1010 rhodecode/model/db.py:2280
2374 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:812
2375 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:855
2376 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:854
2377 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:855
2378 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:855
2379 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:861
2380 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:983
2381 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1008 rhodecode/model/db.py:2328
2413 2382 msgid "Inherit object permissions from default user enabled"
2414 2383 msgstr ""
2415 2384
2416 #: rhodecode/lib/index/whoosh.py:129
2385 #: rhodecode/lib/index/whoosh.py:148
2417 2386 msgid "Invalid search query. Try quoting it."
2418 2387 msgstr ""
2419 2388
2420 #: rhodecode/lib/index/whoosh.py:131
2421 msgid "There is no index to search in. Please run whoosh indexer"
2422 msgstr ""
2423
2424 #: rhodecode/lib/index/whoosh.py:136
2425 msgid "An error occurred during this search operation"
2426 msgstr ""
2427
2428 #: rhodecode/lib/index/whoosh.py:144
2429 msgid "Index Type"
2430 msgstr ""
2431
2432 #: rhodecode/lib/index/whoosh.py:145
2433 msgid "File Index"
2434 msgstr ""
2435
2436 #: rhodecode/lib/index/whoosh.py:146 rhodecode/lib/index/whoosh.py:151
2437 msgid "Indexed documents"
2438 msgstr ""
2439
2440 #: rhodecode/lib/index/whoosh.py:148 rhodecode/lib/index/whoosh.py:153
2441 msgid "Last update"
2442 msgstr ""
2443
2444 2389 #: rhodecode/lib/index/whoosh.py:150
2390 msgid "There is no index to search in. Please run whoosh indexer"
2391 msgstr ""
2392
2393 #: rhodecode/lib/index/whoosh.py:155
2394 msgid "An error occurred during this search operation"
2395 msgstr ""
2396
2397 #: rhodecode/lib/index/whoosh.py:163
2398 msgid "Index Type"
2399 msgstr ""
2400
2401 #: rhodecode/lib/index/whoosh.py:164
2402 msgid "File Index"
2403 msgstr ""
2404
2405 #: rhodecode/lib/index/whoosh.py:165 rhodecode/lib/index/whoosh.py:170
2406 msgid "Indexed documents"
2407 msgstr ""
2408
2409 #: rhodecode/lib/index/whoosh.py:167 rhodecode/lib/index/whoosh.py:172
2410 msgid "Last update"
2411 msgstr ""
2412
2413 #: rhodecode/lib/index/whoosh.py:169
2445 2414 msgid "Commit index"
2446 2415 msgstr ""
2447 2416
2448 #: rhodecode/model/db.py:871
2417 #: rhodecode/login/views.py:237 rhodecode/login/views.py:296
2418 msgid "bad captcha"
2419 msgstr ""
2420
2421 #: rhodecode/login/views.py:246
2422 msgid "You have successfully registered with RhodeCode"
2423 msgstr ""
2424
2425 #: rhodecode/login/views.py:312
2426 msgid "Your password reset link was sent"
2427 msgstr ""
2428
2429 #: rhodecode/login/views.py:333
2430 msgid "Your password reset was successful, a new password has been sent to your email"
2431 msgstr ""
2432
2433 #: rhodecode/model/db.py:909
2449 2434 msgid "all"
2450 2435 msgstr ""
2451 2436
2452 #: rhodecode/model/db.py:872
2437 #: rhodecode/model/db.py:910
2453 2438 msgid "http/web interface"
2454 2439 msgstr ""
2455 2440
2456 #: rhodecode/model/db.py:873
2441 #: rhodecode/model/db.py:911
2457 2442 msgid "vcs (git/hg protocol)"
2458 2443 msgstr ""
2459 2444
2460 #: rhodecode/model/db.py:874
2445 #: rhodecode/model/db.py:912
2461 2446 msgid "api calls"
2462 2447 msgstr ""
2463 2448
2464 #: rhodecode/model/db.py:875
2449 #: rhodecode/model/db.py:913
2465 2450 msgid "feed access"
2466 2451 msgstr ""
2467 2452
2468 #: rhodecode/model/db.py:2020
2453 #: rhodecode/model/db.py:2067
2469 2454 msgid "No parent"
2470 2455 msgstr ""
2471 2456
2472 2457 #: rhodecode/model/forms.py:66
2473 2458 msgid "Please enter a login"
2474 2459 msgstr ""
2475 2460
2476 2461 #: rhodecode/model/forms.py:67
2477 2462 #, python-format
2478 2463 msgid "Enter a value %(min)i characters long or more"
2479 2464 msgstr ""
2480 2465
2481 2466 #: rhodecode/model/forms.py:76
2482 2467 msgid "Please enter a password"
2483 2468 msgstr ""
2484 2469
2485 2470 #: rhodecode/model/forms.py:77
2486 2471 #, python-format
2487 2472 msgid "Enter %(min)i characters or more"
2488 2473 msgstr ""
2489 2474
2490 2475 #: rhodecode/model/notification.py:247
2491 2476 #, python-format
2492 2477 msgid "%(user)s commented on commit %(date_or_age)s"
2493 2478 msgstr ""
2494 2479
2495 2480 #: rhodecode/model/notification.py:248
2496 2481 #, python-format
2497 2482 msgid "%(user)s commented on commit at %(date_or_age)s"
2498 2483 msgstr ""
2499 2484
2500 2485 #: rhodecode/model/notification.py:251
2501 2486 #, python-format
2502 2487 msgid "%(user)s sent message %(date_or_age)s"
2503 2488 msgstr ""
2504 2489
2505 2490 #: rhodecode/model/notification.py:252
2506 2491 #, python-format
2507 2492 msgid "%(user)s sent message at %(date_or_age)s"
2508 2493 msgstr ""
2509 2494
2510 2495 #: rhodecode/model/notification.py:255
2511 2496 #, python-format
2512 2497 msgid "%(user)s mentioned you %(date_or_age)s"
2513 2498 msgstr ""
2514 2499
2515 2500 #: rhodecode/model/notification.py:256
2516 2501 #, python-format
2517 2502 msgid "%(user)s mentioned you at %(date_or_age)s"
2518 2503 msgstr ""
2519 2504
2520 2505 #: rhodecode/model/notification.py:259
2521 2506 #, python-format
2522 2507 msgid "%(user)s registered in RhodeCode %(date_or_age)s"
2523 2508 msgstr ""
2524 2509
2525 2510 #: rhodecode/model/notification.py:260
2526 2511 #, python-format
2527 2512 msgid "%(user)s registered in RhodeCode at %(date_or_age)s"
2528 2513 msgstr ""
2529 2514
2530 2515 #: rhodecode/model/notification.py:263
2531 2516 #, python-format
2532 2517 msgid "%(user)s opened new pull request %(date_or_age)s"
2533 2518 msgstr ""
2534 2519
2535 2520 #: rhodecode/model/notification.py:264
2536 2521 #, python-format
2537 2522 msgid "%(user)s opened new pull request at %(date_or_age)s"
2538 2523 msgstr ""
2539 2524
2540 2525 #: rhodecode/model/notification.py:267
2541 2526 #, python-format
2542 2527 msgid "%(user)s commented on pull request %(date_or_age)s"
2543 2528 msgstr ""
2544 2529
2545 2530 #: rhodecode/model/notification.py:268
2546 2531 #, python-format
2547 2532 msgid "%(user)s commented on pull request at %(date_or_age)s"
2548 2533 msgstr ""
2549 2534
2550 2535 #: rhodecode/model/pull_request.py:69
2551 2536 msgid "This pull request can be automatically merged."
2552 2537 msgstr ""
2553 2538
2554 2539 #: rhodecode/model/pull_request.py:71
2555 2540 msgid "This pull request cannot be merged because of an unhandled exception."
2556 2541 msgstr ""
2557 2542
2558 2543 #: rhodecode/model/pull_request.py:74
2559 2544 msgid "This pull request cannot be merged because of conflicts."
2560 2545 msgstr ""
2561 2546
2562 2547 #: rhodecode/model/pull_request.py:76
2563 2548 msgid "This pull request could not be merged because push to target failed."
2564 2549 msgstr ""
2565 2550
2566 2551 #: rhodecode/model/pull_request.py:79
2567 2552 msgid "This pull request cannot be merged because the target is not a head."
2568 2553 msgstr ""
2569 2554
2570 2555 #: rhodecode/model/pull_request.py:82
2571 2556 msgid "This pull request cannot be merged because the source contains more branches than the target."
2572 2557 msgstr ""
2573 2558
2574 2559 #: rhodecode/model/pull_request.py:85
2575 2560 msgid "This pull request cannot be merged because the target has multiple heads."
2576 2561 msgstr ""
2577 2562
2578 2563 #: rhodecode/model/pull_request.py:88
2579 2564 msgid "This pull request cannot be merged because the target repository is locked."
2580 2565 msgstr ""
2581 2566
2582 2567 #: rhodecode/model/pull_request.py:91
2583 2568 msgid "This pull request cannot be merged because the target or the source reference is missing."
2584 2569 msgstr ""
2585 2570
2586 #: rhodecode/model/pull_request.py:411
2571 #: rhodecode/model/pull_request.py:416
2587 2572 #, python-format
2588 2573 msgid ""
2589 2574 "Merge pull request #%(pr_id)s from %(source_repo)s %(source_ref_name)s\n"
2590 2575 "\n"
2591 2576 " %(pr_title)s"
2592 2577 msgstr ""
2593 2578
2594 #: rhodecode/model/pull_request.py:443
2579 #: rhodecode/model/pull_request.py:448
2595 2580 msgid "Pull request merged and closed"
2596 2581 msgstr ""
2597 2582
2598 #: rhodecode/model/pull_request.py:859
2583 #: rhodecode/model/pull_request.py:867
2599 2584 msgid "Server-side pull request merging is disabled."
2600 2585 msgstr ""
2601 2586
2602 #: rhodecode/model/pull_request.py:861
2587 #: rhodecode/model/pull_request.py:869
2603 2588 msgid "This pull request is closed."
2604 2589 msgstr ""
2605 2590
2606 #: rhodecode/model/pull_request.py:872
2591 #: rhodecode/model/pull_request.py:880
2607 2592 msgid "Pull request merging is not supported."
2608 2593 msgstr ""
2609 2594
2610 #: rhodecode/model/pull_request.py:890
2595 #: rhodecode/model/pull_request.py:898
2611 2596 msgid "Target repository large files support is disabled."
2612 2597 msgstr ""
2613 2598
2614 #: rhodecode/model/pull_request.py:893
2599 #: rhodecode/model/pull_request.py:901
2615 2600 msgid "Source repository large files support is disabled."
2616 2601 msgstr ""
2617 2602
2618 #: rhodecode/model/pull_request.py:1036 rhodecode/model/scm.py:791
2603 #: rhodecode/model/pull_request.py:1050 rhodecode/model/scm.py:791
2619 2604 msgid "Bookmarks"
2620 2605 msgstr ""
2621 2606
2622 #: rhodecode/model/pull_request.py:1041
2607 #: rhodecode/model/pull_request.py:1055
2623 2608 msgid "Commit IDs"
2624 2609 msgstr ""
2625 2610
2626 #: rhodecode/model/pull_request.py:1044
2611 #: rhodecode/model/pull_request.py:1058
2627 2612 msgid "Closed Branches"
2628 2613 msgstr ""
2629 2614
2630 2615 #: rhodecode/model/scm.py:773
2631 2616 msgid "latest tip"
2632 2617 msgstr ""
2633 2618
2634 2619 #: rhodecode/model/user.py:124
2635 2620 msgid "You can't Edit this user since it's crucial for entire application"
2636 2621 msgstr ""
2637 2622
2638 #: rhodecode/model/user.py:283
2623 #: rhodecode/model/user.py:285
2639 2624 #, python-format
2640 2625 msgid "You can't edit this user (`%(username)s`) since it's crucial for entire application"
2641 2626 msgstr ""
2642 2627
2643 #: rhodecode/model/user.py:456
2628 #: rhodecode/model/user.py:458
2644 2629 msgid "You can't remove this user since it's crucial for entire application"
2645 2630 msgstr ""
2646 2631
2647 #: rhodecode/model/user.py:464
2632 #: rhodecode/model/user.py:466
2648 2633 #, python-format
2649 2634 msgid "user \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories:%s"
2650 2635 msgstr ""
2651 2636
2652 #: rhodecode/model/user.py:473
2637 #: rhodecode/model/user.py:475
2653 2638 #, python-format
2654 2639 msgid "user \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups:%s"
2655 2640 msgstr ""
2656 2641
2657 #: rhodecode/model/user.py:482
2642 #: rhodecode/model/user.py:484
2658 2643 #, python-format
2659 2644 msgid "user \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups:%s"
2660 2645 msgstr ""
2661 2646
2662 #: rhodecode/model/validators.py:96 rhodecode/model/validators.py:97
2647 #: rhodecode/model/validators.py:98 rhodecode/model/validators.py:99
2663 2648 msgid "Value cannot be an empty list"
2664 2649 msgstr ""
2665 2650
2666 #: rhodecode/model/validators.py:140
2651 #: rhodecode/model/validators.py:142
2667 2652 msgid "Pattern already exists"
2668 2653 msgstr ""
2669 2654
2670 #: rhodecode/model/validators.py:158
2671 #, python-format
2672 msgid "Username \"%(username)s\" already exists"
2673 msgstr ""
2674
2675 2655 #: rhodecode/model/validators.py:160
2676 2656 #, python-format
2677 msgid "Username \"%(username)s\" is forbidden"
2657 msgid "Username \"%(username)s\" already exists"
2678 2658 msgstr ""
2679 2659
2680 2660 #: rhodecode/model/validators.py:162
2661 #, python-format
2662 msgid "Username \"%(username)s\" is forbidden"
2663 msgstr ""
2664
2665 #: rhodecode/model/validators.py:164
2681 2666 msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character or underscore"
2682 2667 msgstr ""
2683 2668
2684 #: rhodecode/model/validators.py:190
2669 #: rhodecode/model/validators.py:192
2685 2670 msgid "The input is not valid"
2686 2671 msgstr ""
2687 2672
2688 #: rhodecode/model/validators.py:197
2673 #: rhodecode/model/validators.py:199
2689 2674 #, python-format
2690 2675 msgid "Username %(username)s is not valid"
2691 2676 msgstr ""
2692 2677
2693 #: rhodecode/model/validators.py:216
2678 #: rhodecode/model/validators.py:200
2679 #, python-format
2680 msgid "Username %(username)s is disabled"
2681 msgstr ""
2682
2683 #: rhodecode/model/validators.py:223
2694 2684 msgid "Invalid user group name"
2695 2685 msgstr ""
2696 2686
2697 #: rhodecode/model/validators.py:217
2687 #: rhodecode/model/validators.py:224
2698 2688 #, python-format
2699 2689 msgid "User group \"%(usergroup)s\" already exists"
2700 2690 msgstr ""
2701 2691
2702 #: rhodecode/model/validators.py:219
2692 #: rhodecode/model/validators.py:226
2703 2693 msgid "user group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
2704 2694 msgstr ""
2705 2695
2706 #: rhodecode/model/validators.py:257
2696 #: rhodecode/model/validators.py:264
2707 2697 msgid "Cannot assign this group as parent"
2708 2698 msgstr ""
2709 2699
2710 #: rhodecode/model/validators.py:258
2700 #: rhodecode/model/validators.py:265
2711 2701 #, python-format
2712 2702 msgid "Group \"%(group_name)s\" already exists"
2713 2703 msgstr ""
2714 2704
2715 #: rhodecode/model/validators.py:259
2705 #: rhodecode/model/validators.py:266
2716 2706 #, python-format
2717 2707 msgid "Repository with name \"%(group_name)s\" already exists"
2718 2708 msgstr ""
2719 2709
2720 #: rhodecode/model/validators.py:261
2710 #: rhodecode/model/validators.py:268
2721 2711 msgid "no permission to store repository groupin this location"
2722 2712 msgstr ""
2723 2713
2724 #: rhodecode/model/validators.py:263
2714 #: rhodecode/model/validators.py:270
2725 2715 msgid "no permission to store repository group in root location"
2726 2716 msgstr ""
2727 2717
2728 #: rhodecode/model/validators.py:379
2718 #: rhodecode/model/validators.py:386
2729 2719 msgid "Invalid characters (non-ascii) in password"
2730 2720 msgstr ""
2731 2721
2732 #: rhodecode/model/validators.py:394
2722 #: rhodecode/model/validators.py:401
2733 2723 msgid "Invalid old password"
2734 2724 msgstr ""
2735 2725
2736 #: rhodecode/model/validators.py:412
2726 #: rhodecode/model/validators.py:418
2737 2727 msgid "Passwords do not match"
2738 2728 msgstr ""
2739 2729
2740 #: rhodecode/model/validators.py:430
2730 #: rhodecode/model/validators.py:436
2741 2731 msgid "invalid password"
2742 2732 msgstr ""
2743 2733
2744 #: rhodecode/model/validators.py:431
2734 #: rhodecode/model/validators.py:437
2745 2735 msgid "invalid user name"
2746 2736 msgstr ""
2747 2737
2748 #: rhodecode/model/validators.py:432
2738 #: rhodecode/model/validators.py:438
2749 2739 msgid "Your account is disabled"
2750 2740 msgstr ""
2751 2741
2752 #: rhodecode/model/validators.py:465
2742 #: rhodecode/model/validators.py:470
2753 2743 msgid "Token mismatch"
2754 2744 msgstr ""
2755 2745
2756 #: rhodecode/model/validators.py:479
2746 #: rhodecode/model/validators.py:484
2757 2747 #, python-format
2758 2748 msgid "Repository name %(repo)s is disallowed"
2759 2749 msgstr ""
2760 2750
2761 #: rhodecode/model/validators.py:481
2762 #, python-format
2763 msgid "Repository with name %(repo)s already exists"
2764 msgstr ""
2765
2766 #: rhodecode/model/validators.py:483
2767 #, python-format
2768 msgid "Repository group with name \"%(repo)s\" already exists"
2769 msgstr ""
2770
2771 2751 #: rhodecode/model/validators.py:486
2772 2752 #, python-format
2773 msgid "Repository with name %(repo)s exists in group \"%(group)s\""
2753 msgid "Repository with name %(repo)s already exists"
2774 2754 msgstr ""
2775 2755
2776 2756 #: rhodecode/model/validators.py:488
2777 2757 #, python-format
2758 msgid "Repository group with name \"%(repo)s\" already exists"
2759 msgstr ""
2760
2761 #: rhodecode/model/validators.py:491
2762 #, python-format
2763 msgid "Repository with name %(repo)s exists in group \"%(group)s\""
2764 msgstr ""
2765
2766 #: rhodecode/model/validators.py:493
2767 #, python-format
2778 2768 msgid "Repository group with name \"%(repo)s\" exists in group \"%(group)s\""
2779 2769 msgstr ""
2780 2770
2781 #: rhodecode/model/validators.py:615
2771 #: rhodecode/model/validators.py:620
2782 2772 #, python-format
2783 2773 msgid "invalid clone url for %(rtype)s repository"
2784 2774 msgstr ""
2785 2775
2786 #: rhodecode/model/validators.py:616
2776 #: rhodecode/model/validators.py:621
2787 2777 #, python-format
2788 2778 msgid "Invalid clone url, provide a valid clone url starting with one of %(allowed_prefixes)s"
2789 2779 msgstr ""
2790 2780
2791 #: rhodecode/model/validators.py:645
2781 #: rhodecode/model/validators.py:650
2792 2782 msgid "Fork have to be the same type as parent"
2793 2783 msgstr ""
2794 2784
2795 #: rhodecode/model/validators.py:660
2785 #: rhodecode/model/validators.py:665
2796 2786 msgid "You do not have the permission to create repositories in this group."
2797 2787 msgstr ""
2798 2788
2799 #: rhodecode/model/validators.py:663
2789 #: rhodecode/model/validators.py:668
2800 2790 msgid "You do not have the permission to store repositories in the root location."
2801 2791 msgstr ""
2802 2792
2803 #: rhodecode/model/validators.py:723
2793 #: rhodecode/model/validators.py:728
2804 2794 msgid "This username or user group name is not valid"
2805 2795 msgstr ""
2806 2796
2807 #: rhodecode/model/validators.py:841
2797 #: rhodecode/model/validators.py:846
2808 2798 msgid "This is not a valid path"
2809 2799 msgstr ""
2810 2800
2811 #: rhodecode/model/validators.py:856
2801 #: rhodecode/model/validators.py:861
2812 2802 msgid "This e-mail address is already taken"
2813 2803 msgstr ""
2814 2804
2815 #: rhodecode/model/validators.py:876
2805 #: rhodecode/model/validators.py:881
2816 2806 #, python-format
2817 2807 msgid "e-mail \"%(email)s\" does not exist."
2818 2808 msgstr ""
2819 2809
2820 #: rhodecode/model/validators.py:912
2821 msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\""
2822 msgstr ""
2823
2824 #: rhodecode/model/validators.py:926
2810 #: rhodecode/model/validators.py:902
2825 2811 #, python-format
2826 2812 msgid "Revisions %(revs)s are already part of pull request or have set status"
2827 2813 msgstr ""
2828 2814
2829 #: rhodecode/model/validators.py:957
2815 #: rhodecode/model/validators.py:933
2830 2816 msgid "Please enter a valid IPv4 or IpV6 address"
2831 2817 msgstr ""
2832 2818
2833 #: rhodecode/model/validators.py:958
2819 #: rhodecode/model/validators.py:934
2834 2820 #, python-format
2835 2821 msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
2836 2822 msgstr ""
2837 2823
2838 #: rhodecode/model/validators.py:985
2824 #: rhodecode/model/validators.py:961
2839 2825 msgid "Key name can only consist of letters, underscore, dash or numbers"
2840 2826 msgstr ""
2841 2827
2828 #: rhodecode/model/validators.py:976
2829 msgid "Filename cannot be inside a directory"
2830 msgstr ""
2831
2832 #: rhodecode/model/validators.py:992
2833 #, python-format
2834 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
2835 msgstr ""
2836
2837 #: rhodecode/model/validators.py:995
2838 #, python-format
2839 msgid "The plugin \"%(plugin_id)s\" is missing an includeme function."
2840 msgstr ""
2841
2842 #: rhodecode/model/validators.py:998
2843 #, python-format
2844 msgid "Can not load plugin \"%(plugin_id)s\""
2845 msgstr ""
2846
2842 2847 #: rhodecode/model/validators.py:1000
2843 msgid "Filename cannot be inside a directory"
2844 msgstr ""
2845
2846 #: rhodecode/model/validators.py:1016
2847 #, python-format
2848 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
2849 msgstr ""
2850
2851 #: rhodecode/model/validators.py:1048
2848 #, python-format
2849 msgid "No plugin available with ID \"%(plugin_id)s\""
2850 msgstr ""
2851
2852 #: rhodecode/model/validators.py:1067
2852 2853 msgid "This gistid is already in use"
2853 2854 msgstr ""
2854 2855
2855 2856 #: rhodecode/templates/index.html:5
2856 2857 msgid "Dashboard"
2857 2858 msgstr ""
2858 2859
2859 2860 #: rhodecode/templates/index_base.html:8
2860 2861 #: rhodecode/templates/admin/gists/index.html:18
2861 2862 #: rhodecode/templates/admin/my_account/my_account_repos.html:7
2862 2863 #: rhodecode/templates/admin/my_account/my_account_watched.html:7
2863 2864 #: rhodecode/templates/admin/repo_groups/repo_groups.html:12
2864 2865 #: rhodecode/templates/admin/repos/repos.html:12
2865 2866 #: rhodecode/templates/admin/user_groups/user_groups.html:12
2866 2867 #: rhodecode/templates/admin/users/users.html:12
2867 2868 #: rhodecode/templates/bookmarks/bookmarks.html:12
2868 2869 #: rhodecode/templates/branches/branches.html:12
2869 2870 #: rhodecode/templates/journal/journal.html:12
2870 2871 #: rhodecode/templates/tags/tags.html:12
2871 2872 msgid "quick filter..."
2872 2873 msgstr ""
2873 2874
2874 2875 #: rhodecode/templates/index_base.html:10
2875 2876 msgid "matches"
2876 2877 msgstr ""
2877 2878
2878 2879 #: rhodecode/templates/index_base.html:30
2879 2880 #: rhodecode/templates/index_base.html:39
2880 2881 #: rhodecode/templates/admin/repos/repo_add.html:22
2881 2882 #: rhodecode/templates/admin/repos/repos.html:27
2882 2883 msgid "Add Repository"
2883 2884 msgstr ""
2884 2885
2885 2886 #: rhodecode/templates/index_base.html:34
2886 2887 #: rhodecode/templates/index_base.html:42
2887 2888 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:16
2888 2889 #: rhodecode/templates/admin/repo_groups/repo_groups.html:27
2889 2890 msgid "Add Repository Group"
2890 2891 msgstr ""
2891 2892
2892 2893 #: rhodecode/templates/index_base.html:45
2893 2894 msgid "You have admin right to this group, and can edit it"
2894 2895 msgstr ""
2895 2896
2896 2897 #: rhodecode/templates/index_base.html:45
2897 2898 msgid "Edit Repository Group"
2898 2899 msgstr ""
2899 2900
2900 2901 #: rhodecode/templates/index_base.html:97
2901 2902 #: rhodecode/templates/index_base.html:122
2902 2903 #: rhodecode/templates/admin/gists/index.html:112
2903 2904 #: rhodecode/templates/admin/my_account/my_account_repos.html:31
2904 2905 #: rhodecode/templates/admin/my_account/my_account_watched.html:31
2905 2906 #: rhodecode/templates/admin/repo_groups/repo_groups.html:53
2906 2907 #: rhodecode/templates/admin/repos/repo_add_base.html:9
2907 #: rhodecode/templates/admin/repos/repo_edit_settings.html:12
2908 #: rhodecode/templates/admin/repos/repo_edit_settings.html:15
2908 2909 #: rhodecode/templates/admin/repos/repos.html:54
2909 2910 #: rhodecode/templates/admin/user_groups/user_groups.html:55
2910 #: rhodecode/templates/base/perms_summary.html:103
2911 #: rhodecode/templates/base/perms_summary.html:102
2911 2912 #: rhodecode/templates/bookmarks/bookmarks.html:59
2912 2913 #: rhodecode/templates/branches/branches.html:58
2913 2914 #: rhodecode/templates/files/files_browser.html:49
2914 2915 #: rhodecode/templates/pullrequests/pullrequests.html:100
2915 2916 #: rhodecode/templates/tags/tags.html:59
2916 2917 msgid "Name"
2917 2918 msgstr ""
2918 2919
2919 2920 #: rhodecode/templates/index_base.html:100
2920 2921 #: rhodecode/templates/index_base.html:125
2921 2922 #: rhodecode/templates/admin/gists/index.html:114
2922 2923 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:77
2923 2924 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:45
2924 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:30
2925 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:42
2925 2926 #: rhodecode/templates/admin/repo_groups/repo_groups.html:56
2926 2927 #: rhodecode/templates/admin/repos/repo_add_base.html:32
2927 2928 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:29
2928 #: rhodecode/templates/admin/repos/repo_edit_settings.html:83
2929 #: rhodecode/templates/admin/repos/repo_edit_settings.html:96
2929 2930 #: rhodecode/templates/admin/repos/repos.html:57
2930 2931 #: rhodecode/templates/admin/user_groups/user_group_add.html:43
2931 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:29
2932 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:42
2932 2933 #: rhodecode/templates/admin/user_groups/user_groups.html:57
2933 2934 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:74
2934 2935 #: rhodecode/templates/base/issue_tracker_settings.html:10
2935 2936 #: rhodecode/templates/changeset/changeset.html:53
2936 #: rhodecode/templates/compare/compare_cs.html:24
2937 #: rhodecode/templates/compare/compare_commits.html:24
2937 2938 #: rhodecode/templates/email_templates/pull_request_review.mako:30
2938 2939 #: rhodecode/templates/email_templates/pull_request_review.mako:67
2939 2940 #: rhodecode/templates/files/file_tree_detail.html:5
2940 2941 #: rhodecode/templates/files/file_tree_detail.html:12
2941 2942 #: rhodecode/templates/forks/fork.html:48
2942 #: rhodecode/templates/forks/forks_data.html:8
2943 #: rhodecode/templates/forks/forks_data.html:9
2943 2944 #: rhodecode/templates/pullrequests/pullrequest.html:47
2944 2945 #: rhodecode/templates/pullrequests/pullrequest_show.html:122
2945 2946 #: rhodecode/templates/summary/components.html:73
2946 2947 msgid "Description"
2947 2948 msgstr ""
2948 2949
2949 2950 #: rhodecode/templates/index_base.html:102
2950 2951 #: rhodecode/templates/index_base.html:133
2951 2952 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:5
2952 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:21
2953 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:24
2953 2954 #: rhodecode/templates/admin/repo_groups/repo_groups.html:60
2954 2955 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:5
2955 #: rhodecode/templates/admin/repos/repo_edit_settings.html:74
2956 #: rhodecode/templates/admin/repos/repo_edit_settings.html:78
2956 2957 #: rhodecode/templates/admin/repos/repos.html:65
2957 2958 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:5
2958 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:20
2959 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:24
2959 2960 #: rhodecode/templates/admin/user_groups/user_groups.html:64
2961 #: rhodecode/templates/forks/forks_data.html:7
2960 2962 #: rhodecode/templates/summary/components.html:195
2961 2963 msgid "Owner"
2962 2964 msgstr ""
2963 2965
2964 2966 #: rhodecode/templates/index_base.html:128
2965 2967 #: rhodecode/templates/admin/repos/repos.html:60
2966 2968 msgid "Last Change"
2967 2969 msgstr ""
2968 2970
2969 2971 #: rhodecode/templates/index_base.html:131
2970 2972 #: rhodecode/templates/admin/my_account/my_account_repos.html:35
2971 2973 #: rhodecode/templates/admin/my_account/my_account_watched.html:35
2972 2974 #: rhodecode/templates/admin/repos/repos.html:63
2973 2975 #: rhodecode/templates/bookmarks/bookmarks.html:66
2974 2976 #: rhodecode/templates/branches/branches.html:65
2975 2977 #: rhodecode/templates/changelog/changelog.html:106
2976 2978 #: rhodecode/templates/changelog/changelog_summary_data.html:6
2977 2979 #: rhodecode/templates/changeset/changeset.html:36
2978 #: rhodecode/templates/compare/compare_cs.html:22
2980 #: rhodecode/templates/compare/compare_commits.html:22
2979 2981 #: rhodecode/templates/email_templates/commit_comment.mako:16
2980 2982 #: rhodecode/templates/email_templates/commit_comment.mako:45
2981 2983 #: rhodecode/templates/search/search_commit.html:6
2982 2984 #: rhodecode/templates/tags/tags.html:66
2983 2985 msgid "Commit"
2984 2986 msgstr ""
2985 2987
2986 2988 #: rhodecode/templates/index_repo_group.html:5
2987 2989 #, python-format
2988 2990 msgid "%s Repository group dashboard"
2989 2991 msgstr ""
2990 2992
2991 2993 #: rhodecode/templates/index_repo_group.html:13
2992 2994 msgid "Home"
2993 2995 msgstr ""
2994 2996
2995 #: rhodecode/templates/login.html:6 rhodecode/templates/login.html:36
2996 #: rhodecode/templates/login.html:52 rhodecode/templates/base/base.html:326
2997 #: rhodecode/templates/login.html:5 rhodecode/templates/login.html:35
2998 #: rhodecode/templates/login.html:64 rhodecode/templates/base/base.html:328
2997 2999 #: rhodecode/templates/debug_style/login.html:60
2998 3000 msgid "Sign In"
2999 3001 msgstr ""
3000 3002
3001 #: rhodecode/templates/login.html:38
3003 #: rhodecode/templates/login.html:37
3002 3004 msgid "Go to the registration page to create a new account."
3003 3005 msgstr ""
3004 3006
3005 3007 #: rhodecode/templates/login.html:43 rhodecode/templates/register.html:41
3006 3008 #: rhodecode/templates/admin/admin_log.html:5
3007 3009 #: rhodecode/templates/admin/my_account/my_account_profile.html:24
3008 3010 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:21
3009 3011 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:66
3010 3012 #: rhodecode/templates/admin/users/user_add.html:35
3011 3013 #: rhodecode/templates/admin/users/user_edit_profile.html:39
3012 3014 #: rhodecode/templates/admin/users/users.html:89
3013 #: rhodecode/templates/base/base.html:303
3015 #: rhodecode/templates/base/base.html:305
3014 3016 #: rhodecode/templates/debug_style/login.html:36
3015 3017 #: rhodecode/templates/users/user_profile.html:27
3016 3018 msgid "Username"
3017 3019 msgstr ""
3018 3020
3019 #: rhodecode/templates/login.html:48
3021 #: rhodecode/templates/login.html:58
3020 3022 msgid "Remember me"
3021 3023 msgstr ""
3022 3024
3023 #: rhodecode/templates/login.html:50
3025 #: rhodecode/templates/login.html:61
3024 3026 msgid "Forgot your password?"
3025 3027 msgstr ""
3026 3028
3027 #: rhodecode/templates/login.html:62
3028 msgid "Sign In using one of external services"
3029 msgstr ""
3030
3031 3029 #: rhodecode/templates/password_reset.html:5
3032 #: rhodecode/templates/register.html:6
3030 #: rhodecode/templates/register.html:5
3033 3031 msgid "Create an Account"
3034 3032 msgstr ""
3035 3033
3036 3034 #: rhodecode/templates/password_reset.html:35
3037 3035 msgid "Reset your Password"
3038 3036 msgstr ""
3039 3037
3040 3038 #: rhodecode/templates/password_reset.html:36
3041 3039 msgid "Go to the login page to sign in."
3042 3040 msgstr ""
3043 3041
3044 3042 #: rhodecode/templates/password_reset.html:40
3045 3043 msgid "Email Address"
3046 3044 msgstr ""
3047 3045
3048 #: rhodecode/templates/password_reset.html:45
3049 #: rhodecode/templates/register.html:56
3046 #: rhodecode/templates/password_reset.html:49
3047 #: rhodecode/templates/register.html:85
3050 3048 msgid "Captcha"
3051 3049 msgstr ""
3052 3050
3053 #: rhodecode/templates/password_reset.html:51
3051 #: rhodecode/templates/password_reset.html:59
3054 3052 msgid "Send password reset email"
3055 3053 msgstr ""
3056 3054
3057 #: rhodecode/templates/password_reset.html:52
3055 #: rhodecode/templates/password_reset.html:60
3058 3056 msgid "Password reset link will be send to matching email address"
3059 3057 msgstr ""
3060 3058
3059 #: rhodecode/templates/register.html:35
3060 msgid "Create an account"
3061 msgstr ""
3062
3061 3063 #: rhodecode/templates/register.html:36
3062 msgid "Create an account"
3063 msgstr ""
3064
3065 #: rhodecode/templates/register.html:37
3066 3064 msgid "Go to the login page to sign in with an existing account."
3067 3065 msgstr ""
3068 3066
3069 #: rhodecode/templates/register.html:45
3067 #: rhodecode/templates/register.html:55
3070 3068 msgid "Re-enter password"
3071 3069 msgstr ""
3072 3070
3073 #: rhodecode/templates/register.html:47
3071 #: rhodecode/templates/register.html:62
3074 3072 #: rhodecode/templates/admin/my_account/my_account_profile.html:32
3075 3073 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:30
3076 3074 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:76
3077 3075 #: rhodecode/templates/admin/users/user_add.html:68
3078 3076 #: rhodecode/templates/admin/users/user_edit_profile.html:47
3079 3077 #: rhodecode/templates/admin/users/users.html:93
3080 3078 msgid "First Name"
3081 3079 msgstr ""
3082 3080
3083 #: rhodecode/templates/register.html:49
3081 #: rhodecode/templates/register.html:69
3084 3082 #: rhodecode/templates/admin/my_account/my_account_profile.html:40
3085 3083 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:39
3086 3084 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:85
3087 3085 #: rhodecode/templates/admin/users/user_add.html:77
3088 3086 #: rhodecode/templates/admin/users/user_edit_profile.html:56
3089 3087 #: rhodecode/templates/admin/users/users.html:95
3090 3088 msgid "Last Name"
3091 3089 msgstr ""
3092 3090
3093 #: rhodecode/templates/register.html:64
3091 #: rhodecode/templates/register.html:97
3094 3092 msgid "Account activation requires admin approval."
3095 3093 msgstr ""
3096 3094
3097 #: rhodecode/templates/register.html:71
3095 #: rhodecode/templates/register.html:104
3098 3096 msgid "Create Account"
3099 3097 msgstr ""
3100 3098
3101 #: rhodecode/templates/register.html:77
3102 msgid "Register using one of external services"
3103 msgstr ""
3104
3105 3099 #: rhodecode/templates/admin/admin.html:5
3106 3100 #: rhodecode/templates/admin/admin.html:15
3107 3101 #: rhodecode/templates/base/base.html:78
3108 3102 msgid "Admin journal"
3109 3103 msgstr ""
3110 3104
3111 3105 #: rhodecode/templates/admin/admin.html:13
3112 3106 msgid "journal filter..."
3113 3107 msgstr ""
3114 3108
3115 3109 #: rhodecode/templates/admin/admin.html:14
3116 3110 msgid "filter"
3117 3111 msgstr ""
3118 3112
3119 3113 #: rhodecode/templates/admin/admin.html:15
3120 3114 #: rhodecode/templates/journal/journal.html:14
3121 3115 #, python-format
3122 3116 msgid "%s entry"
3123 3117 msgid_plural "%s entries"
3124 3118 msgstr[0] ""
3125 3119 msgstr[1] ""
3126 3120
3127 3121 #: rhodecode/templates/admin/admin.html:17
3128 3122 #: rhodecode/templates/journal/journal.html:17
3129 3123 msgid "Example Queries"
3130 3124 msgstr ""
3131 3125
3132 3126 #: rhodecode/templates/admin/admin_log.html:6
3133 3127 #: rhodecode/templates/admin/my_account/my_account_repos.html:37
3134 3128 #: rhodecode/templates/admin/repo_groups/repo_groups.html:62
3135 3129 #: rhodecode/templates/admin/repos/repo_edit_fields.html:13
3136 3130 #: rhodecode/templates/admin/repos/repos.html:69
3137 3131 #: rhodecode/templates/admin/user_groups/user_groups.html:66
3138 3132 #: rhodecode/templates/admin/users/users.html:106
3139 3133 msgid "Action"
3140 3134 msgstr ""
3141 3135
3142 3136 #: rhodecode/templates/admin/admin_log.html:7
3143 3137 #: rhodecode/templates/admin/defaults/defaults.html:31
3144 3138 #: rhodecode/templates/admin/permissions/permissions_objects.html:13
3145 3139 #: rhodecode/templates/search/search_commit.html:5
3146 3140 #: rhodecode/templates/search/search_path.html:3
3147 3141 msgid "Repository"
3148 3142 msgstr ""
3149 3143
3150 3144 #: rhodecode/templates/admin/admin_log.html:8
3151 3145 #: rhodecode/templates/bookmarks/bookmarks.html:61
3152 3146 #: rhodecode/templates/branches/branches.html:60
3153 3147 #: rhodecode/templates/tags/tags.html:61
3154 3148 msgid "Date"
3155 3149 msgstr ""
3156 3150
3157 3151 #: rhodecode/templates/admin/admin_log.html:9
3158 3152 msgid "From IP"
3159 3153 msgstr ""
3160 3154
3161 3155 #: rhodecode/templates/admin/admin_log.html:44
3162 3156 msgid "No actions yet"
3163 3157 msgstr ""
3164 3158
3165 3159 #: rhodecode/templates/admin/auth/auth_settings.html:5
3166 3160 #: rhodecode/templates/admin/auth/plugin_settings.html:5
3167 3161 msgid "Authentication Settings"
3168 3162 msgstr ""
3169 3163
3170 3164 #: rhodecode/templates/admin/auth/auth_settings.html:12
3171 3165 #: rhodecode/templates/admin/auth/plugin_settings.html:12
3172 3166 #: rhodecode/templates/admin/defaults/defaults.html:12
3173 3167 #: rhodecode/templates/admin/permissions/permissions.html:12
3174 3168 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:12
3175 3169 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:12
3176 3170 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:14
3177 3171 #: rhodecode/templates/admin/repo_groups/repo_groups.html:13
3178 3172 #: rhodecode/templates/admin/repos/repo_add.html:13
3179 3173 #: rhodecode/templates/admin/repos/repo_add.html:17
3180 3174 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:15
3181 3175 #: rhodecode/templates/admin/repos/repos.html:13
3182 3176 #: rhodecode/templates/admin/settings/settings.html:12
3183 3177 #: rhodecode/templates/admin/user_groups/user_group_add.html:11
3184 3178 #: rhodecode/templates/admin/user_groups/user_group_edit.html:12
3185 3179 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:14
3186 3180 #: rhodecode/templates/admin/user_groups/user_groups.html:13
3187 3181 #: rhodecode/templates/admin/users/user_add.html:11
3188 3182 #: rhodecode/templates/admin/users/user_edit.html:12
3189 3183 #: rhodecode/templates/admin/users/users.html:13
3190 3184 #: rhodecode/templates/admin/users/users.html:102
3191 #: rhodecode/templates/base/base.html:403
3192 #: rhodecode/templates/base/base.html:410
3185 #: rhodecode/templates/base/base.html:405
3186 #: rhodecode/templates/base/base.html:412
3193 3187 msgid "Admin"
3194 3188 msgstr ""
3195 3189
3196 3190 #: rhodecode/templates/admin/auth/auth_settings.html:14
3197 3191 #: rhodecode/templates/admin/auth/plugin_settings.html:14
3198 3192 msgid "Authentication Plugins"
3199 3193 msgstr ""
3200 3194
3201 3195 #: rhodecode/templates/admin/auth/auth_settings.html:47
3202 3196 msgid "Enabled and Available Plugins"
3203 3197 msgstr ""
3204 3198
3205 3199 #: rhodecode/templates/admin/auth/auth_settings.html:53
3206 3200 msgid "Enabled Plugins"
3207 3201 msgstr ""
3208 3202
3209 3203 #: rhodecode/templates/admin/auth/auth_settings.html:58
3210 3204 msgid "Add a list of plugins, separated by commas. The order of the plugins is also the order in which RhodeCode Enterprise will try to authenticate a user."
3211 3205 msgstr ""
3212 3206
3213 3207 #: rhodecode/templates/admin/auth/auth_settings.html:65
3214 3208 msgid "Available Built-in Plugins"
3215 3209 msgstr ""
3216 3210
3217 3211 #: rhodecode/templates/admin/auth/auth_settings.html:71
3218 3212 msgid "enabled"
3219 3213 msgstr ""
3220 3214
3221 3215 #: rhodecode/templates/admin/auth/auth_settings.html:71
3222 3216 msgid "disabled"
3223 3217 msgstr ""
3224 3218
3225 3219 #: rhodecode/templates/admin/auth/auth_settings.html:81
3226 #: rhodecode/templates/admin/auth/plugin_settings.html:90
3220 #: rhodecode/templates/admin/auth/plugin_settings.html:87
3227 3221 #: rhodecode/templates/admin/defaults/defaults_repositories.html:63
3228 3222 #: rhodecode/templates/admin/my_account/my_account_password.html:36
3229 3223 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:103
3230 3224 #: rhodecode/templates/admin/permissions/permissions_application.html:50
3231 3225 #: rhodecode/templates/admin/permissions/permissions_objects.html:56
3232 3226 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:72
3233 3227 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:135
3234 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:55
3228 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:67
3235 3229 #: rhodecode/templates/admin/repos/repo_add_base.html:88
3236 3230 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:79
3237 3231 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:110
3238 #: rhodecode/templates/admin/repos/repo_edit_settings.html:145
3232 #: rhodecode/templates/admin/repos/repo_edit_settings.html:158
3239 3233 #: rhodecode/templates/admin/settings/settings_hooks.html:63
3240 3234 #: rhodecode/templates/admin/settings/settings_issuetracker.html:15
3241 3235 #: rhodecode/templates/admin/user_groups/user_group_add.html:60
3242 3236 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:120
3243 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:72
3237 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:89
3244 3238 #: rhodecode/templates/admin/users/user_add.html:125
3245 3239 #: rhodecode/templates/admin/users/user_edit_profile.html:134
3246 3240 #: rhodecode/templates/base/default_perms_box.html:88
3247 3241 msgid "Save"
3248 3242 msgstr ""
3249 3243
3250 3244 #: rhodecode/templates/admin/auth/plugin_settings.html:45
3251 3245 msgid "Plugin"
3252 3246 msgstr ""
3253 3247
3254 3248 #: rhodecode/templates/admin/defaults/defaults.html:5
3255 3249 #: rhodecode/templates/admin/defaults/defaults.html:14
3256 3250 msgid "Repositories defaults"
3257 3251 msgstr ""
3258 3252
3259 3253 #: rhodecode/templates/admin/defaults/defaults_repositories.html:3
3260 3254 msgid "Default Settings For New Repositories"
3261 3255 msgstr ""
3262 3256
3263 3257 #: rhodecode/templates/admin/defaults/defaults_repositories.html:14
3264 3258 #: rhodecode/templates/admin/gists/index.html:110
3265 3259 #: rhodecode/templates/admin/repos/repo_add_base.html:62
3266 3260 #: rhodecode/templates/admin/repos/repo_edit_fields.html:12
3267 3261 msgid "Type"
3268 3262 msgstr ""
3269 3263
3270 3264 #: rhodecode/templates/admin/defaults/defaults_repositories.html:23
3271 3265 #: rhodecode/templates/admin/repos/repo_add_base.html:80
3272 3266 msgid "Private Repository"
3273 3267 msgstr ""
3274 3268
3275 3269 #: rhodecode/templates/admin/defaults/defaults_repositories.html:27
3276 3270 #: rhodecode/templates/admin/repos/repo_add_base.html:84
3277 #: rhodecode/templates/admin/repos/repo_edit_settings.html:97
3271 #: rhodecode/templates/admin/repos/repo_edit_settings.html:110
3278 3272 #: rhodecode/templates/forks/fork.html:85
3279 3273 msgid "Private repositories are only visible to people explicitly added as collaborators."
3280 3274 msgstr ""
3281 3275
3282 3276 #: rhodecode/templates/admin/defaults/defaults_repositories.html:34
3283 3277 msgid "Enable Statistics"
3284 3278 msgstr ""
3285 3279
3286 3280 #: rhodecode/templates/admin/defaults/defaults_repositories.html:38
3287 3281 msgid "Enable a statistics window on the repository summary page."
3288 3282 msgstr ""
3289 3283
3290 3284 #: rhodecode/templates/admin/defaults/defaults_repositories.html:44
3291 3285 msgid "Enable Downloads"
3292 3286 msgstr ""
3293 3287
3294 3288 #: rhodecode/templates/admin/defaults/defaults_repositories.html:48
3295 3289 msgid "Enable the download option on the repository summary page."
3296 3290 msgstr ""
3297 3291
3298 3292 #: rhodecode/templates/admin/defaults/defaults_repositories.html:54
3299 3293 msgid "Enable Locking"
3300 3294 msgstr ""
3301 3295
3302 3296 #: rhodecode/templates/admin/defaults/defaults_repositories.html:58
3303 3297 msgid "Enable automatic repository locking. Pulling from a repository will lock it, and it is unlocked by pushing back by the same user."
3304 3298 msgstr ""
3305 3299
3306 3300 #: rhodecode/templates/admin/gists/edit.html:5
3307 3301 #: rhodecode/templates/admin/gists/edit.html:12
3308 3302 msgid "Edit Gist"
3309 3303 msgstr ""
3310 3304
3311 3305 #: rhodecode/templates/admin/gists/edit.html:29
3312 3306 #, python-format
3313 3307 msgid "Gist was updated since you started editing. Copy your changes and click %(here)s to reload the new version."
3314 3308 msgstr ""
3315 3309
3316 3310 #: rhodecode/templates/admin/gists/edit.html:39
3317 3311 #: rhodecode/templates/admin/gists/new.html:30
3318 3312 msgid "Gist description ..."
3319 3313 msgstr ""
3320 3314
3321 3315 #: rhodecode/templates/admin/gists/edit.html:44
3322 3316 #: rhodecode/templates/admin/gists/new.html:38
3323 3317 msgid "Gist lifetime"
3324 3318 msgstr ""
3325 3319
3326 3320 #: rhodecode/templates/admin/gists/edit.html:47
3327 3321 #: rhodecode/templates/admin/gists/new.html:41
3328 3322 msgid "Gist access level"
3329 3323 msgstr ""
3330 3324
3331 3325 #: rhodecode/templates/admin/gists/edit.html:59
3332 3326 #: rhodecode/templates/admin/gists/new.html:50
3333 3327 #: rhodecode/templates/files/files_add.html:74
3334 3328 #: rhodecode/templates/files/files_edit.html:78
3335 3329 msgid "plain"
3336 3330 msgstr ""
3337 3331
3338 3332 #: rhodecode/templates/admin/gists/edit.html:103
3339 3333 msgid "Update Gist"
3340 3334 msgstr ""
3341 3335
3342 3336 #: rhodecode/templates/admin/gists/edit.html:104
3343 3337 #: rhodecode/templates/base/issue_tracker_settings.html:74
3344 3338 #: rhodecode/templates/changeset/changeset_file_comment.html:139
3345 3339 #: rhodecode/templates/files/files_add.html:102
3346 3340 #: rhodecode/templates/files/files_delete.html:69
3347 3341 #: rhodecode/templates/files/files_edit.html:105
3348 3342 msgid "Cancel"
3349 3343 msgstr ""
3350 3344
3351 3345 #: rhodecode/templates/admin/gists/index.html:6
3352 3346 #: rhodecode/templates/admin/gists/index.html:20
3353 3347 #, python-format
3354 3348 msgid "Private Gists for user %s"
3355 3349 msgstr ""
3356 3350
3357 3351 #: rhodecode/templates/admin/gists/index.html:8
3358 3352 #: rhodecode/templates/admin/gists/index.html:22
3359 3353 #, python-format
3360 3354 msgid "Public Gists for user %s"
3361 3355 msgstr ""
3362 3356
3363 3357 #: rhodecode/templates/admin/gists/index.html:10
3364 3358 msgid "Public Gists"
3365 3359 msgstr ""
3366 3360
3367 3361 #: rhodecode/templates/admin/gists/index.html:24
3368 3362 #, python-format
3369 3363 msgid "All Gists for user %s"
3370 3364 msgstr ""
3371 3365
3372 3366 #: rhodecode/templates/admin/gists/index.html:26
3373 3367 msgid "All Public Gists"
3374 3368 msgstr ""
3375 3369
3376 3370 #: rhodecode/templates/admin/gists/index.html:44
3377 3371 #: rhodecode/templates/admin/gists/show.html:36
3378 3372 msgid "Create New Gist"
3379 3373 msgstr ""
3380 3374
3381 3375 #: rhodecode/templates/admin/gists/index.html:56
3382 3376 msgid "All gists"
3383 3377 msgstr ""
3384 3378
3385 3379 #: rhodecode/templates/admin/gists/index.html:58
3386 3380 msgid "All public"
3387 3381 msgstr ""
3388 3382
3389 3383 #: rhodecode/templates/admin/gists/index.html:60
3390 3384 msgid "My gists"
3391 3385 msgstr ""
3392 3386
3393 3387 #: rhodecode/templates/admin/gists/index.html:61
3394 3388 msgid "My private"
3395 3389 msgstr ""
3396 3390
3397 3391 #: rhodecode/templates/admin/gists/index.html:62
3398 3392 msgid "My public"
3399 3393 msgstr ""
3400 3394
3401 3395 #: rhodecode/templates/admin/gists/index.html:108
3396 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:24
3397 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:87
3402 3398 #: rhodecode/templates/bookmarks/bookmarks.html:63
3403 3399 #: rhodecode/templates/branches/branches.html:62
3404 3400 #: rhodecode/templates/changelog/changelog.html:102
3405 3401 #: rhodecode/templates/changelog/changelog_summary_data.html:10
3406 3402 #: rhodecode/templates/changeset/changeset.html:164
3407 #: rhodecode/templates/compare/compare_cs.html:21
3403 #: rhodecode/templates/compare/compare_commits.html:21
3408 3404 #: rhodecode/templates/files/files_browser.html:53
3409 3405 #: rhodecode/templates/pullrequests/pullrequest_show.html:169
3410 3406 #: rhodecode/templates/pullrequests/pullrequests.html:102
3411 #: rhodecode/templates/search/search_commit.html:10
3407 #: rhodecode/templates/search/search_commit.html:16
3412 3408 #: rhodecode/templates/tags/tags.html:63
3413 3409 msgid "Author"
3414 3410 msgstr ""
3415 3411
3416 3412 #: rhodecode/templates/admin/gists/index.html:116
3417 3413 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:6
3418 3414 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:6
3419 3415 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:6
3420 3416 #: rhodecode/templates/admin/users/user_edit_advanced.html:5
3421 3417 msgid "Created on"
3422 3418 msgstr ""
3423 3419
3424 3420 #: rhodecode/templates/admin/gists/index.html:118
3425 3421 #: rhodecode/templates/admin/gists/show.html:65
3426 3422 msgid "Expires"
3427 3423 msgstr ""
3428 3424
3429 3425 #: rhodecode/templates/admin/gists/new.html:5
3430 3426 #: rhodecode/templates/admin/gists/new.html:12
3431 3427 msgid "New Gist"
3432 3428 msgstr ""
3433 3429
3434 3430 #: rhodecode/templates/admin/gists/new.html:35
3435 3431 msgid "Gist id"
3436 3432 msgstr ""
3437 3433
3438 3434 #: rhodecode/templates/admin/gists/new.html:36
3439 3435 msgid "Auto generated"
3440 3436 msgstr ""
3441 3437
3442 3438 #: rhodecode/templates/admin/gists/new.html:49
3443 3439 msgid "name this file..."
3444 3440 msgstr ""
3445 3441
3446 3442 #: rhodecode/templates/admin/gists/new.html:60
3447 3443 msgid "Create Private Gist"
3448 3444 msgstr ""
3449 3445
3450 3446 #: rhodecode/templates/admin/gists/new.html:61
3451 3447 msgid "Create Public Gist"
3452 3448 msgstr ""
3453 3449
3454 3450 #: rhodecode/templates/admin/gists/new.html:62
3455 3451 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:27
3456 3452 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:84
3457 3453 #: rhodecode/templates/admin/my_account/my_account_emails.html:65
3458 3454 #: rhodecode/templates/admin/my_account/my_account_password.html:37
3459 3455 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:104
3460 3456 #: rhodecode/templates/admin/permissions/permissions_application.html:51
3461 3457 #: rhodecode/templates/admin/permissions/permissions_ips.html:61
3462 3458 #: rhodecode/templates/admin/permissions/permissions_objects.html:57
3463 3459 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:136
3464 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:56
3460 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:68
3465 3461 #: rhodecode/templates/admin/repos/repo_edit_fields.html:66
3466 3462 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:80
3467 3463 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:111
3468 #: rhodecode/templates/admin/repos/repo_edit_settings.html:146
3464 #: rhodecode/templates/admin/repos/repo_edit_settings.html:159
3469 3465 #: rhodecode/templates/admin/repos/repo_edit_vcs.html:46
3470 3466 #: rhodecode/templates/admin/settings/settings_global.html:110
3471 3467 #: rhodecode/templates/admin/settings/settings_issuetracker.html:16
3472 3468 #: rhodecode/templates/admin/settings/settings_labs.html:46
3473 3469 #: rhodecode/templates/admin/settings/settings_vcs.html:14
3474 3470 #: rhodecode/templates/admin/settings/settings_visual.html:220
3475 3471 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:121
3476 3472 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:23
3477 3473 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:81
3478 3474 #: rhodecode/templates/admin/users/user_edit_emails.html:63
3479 3475 #: rhodecode/templates/admin/users/user_edit_ips.html:70
3480 3476 #: rhodecode/templates/admin/users/user_edit_profile.html:135
3481 3477 #: rhodecode/templates/base/default_perms_box.html:89
3482 3478 msgid "Reset"
3483 3479 msgstr ""
3484 3480
3485 3481 #: rhodecode/templates/admin/gists/show.html:13
3486 3482 #: rhodecode/templates/admin/gists/show.html:20
3487 3483 msgid "Gist"
3488 3484 msgstr ""
3489 3485
3490 3486 #: rhodecode/templates/admin/gists/show.html:49
3491 3487 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:56
3492 3488 #: rhodecode/templates/admin/my_account/my_account_emails.html:32
3493 #: rhodecode/templates/admin/my_account/my_account_oauth.html:50
3494 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:34
3489 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:61
3495 3490 #: rhodecode/templates/admin/permissions/permissions_ips.html:26
3496 3491 #: rhodecode/templates/admin/repos/repo_edit_fields.html:25
3497 3492 #: rhodecode/templates/admin/settings/settings_hooks.html:46
3498 3493 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:52
3499 3494 #: rhodecode/templates/admin/users/user_edit_emails.html:31
3500 3495 #: rhodecode/templates/admin/users/user_edit_ips.html:34
3501 3496 #: rhodecode/templates/base/issue_tracker_settings.html:70
3502 3497 #: rhodecode/templates/base/vcs_settings.html:147
3503 3498 #: rhodecode/templates/base/vcs_settings.html:172
3504 3499 #: rhodecode/templates/changeset/changeset_file_comment.html:49
3505 3500 #: rhodecode/templates/changeset/changeset_file_comment.html:99
3506 3501 #: rhodecode/templates/data_table/_dt_elements.html:117
3507 3502 #: rhodecode/templates/data_table/_dt_elements.html:174
3508 3503 #: rhodecode/templates/data_table/_dt_elements.html:188
3509 3504 #: rhodecode/templates/data_table/_dt_elements.html:200
3510 3505 #: rhodecode/templates/debug_style/buttons.html:132
3511 3506 #: rhodecode/templates/files/files_source.html:33
3512 3507 #: rhodecode/templates/files/files_source.html:37
3513 3508 #: rhodecode/templates/files/files_source.html:40
3514 3509 msgid "Delete"
3515 3510 msgstr ""
3516 3511
3517 3512 #: rhodecode/templates/admin/gists/show.html:49
3518 3513 msgid "Confirm to delete this Gist"
3519 3514 msgstr ""
3520 3515
3521 3516 #: rhodecode/templates/admin/gists/show.html:56
3522 3517 #: rhodecode/templates/admin/my_account/my_account_profile.html:5
3523 3518 #: rhodecode/templates/base/issue_tracker_settings.html:61
3524 3519 #: rhodecode/templates/changeset/changeset_file_comment.html:145
3525 3520 #: rhodecode/templates/changeset/changeset_file_comment.html:292
3526 3521 #: rhodecode/templates/data_table/_dt_elements.html:112
3527 3522 #: rhodecode/templates/data_table/_dt_elements.html:170
3528 3523 #: rhodecode/templates/data_table/_dt_elements.html:183
3529 3524 #: rhodecode/templates/data_table/_dt_elements.html:196
3530 3525 #: rhodecode/templates/debug_style/buttons.html:128
3531 3526 #: rhodecode/templates/files/files_add.html:204
3532 3527 #: rhodecode/templates/files/files_edit.html:165
3533 3528 #: rhodecode/templates/files/files_source.html:36
3534 3529 #: rhodecode/templates/files/files_source.html:39
3535 3530 #: rhodecode/templates/pullrequests/pullrequest_show.html:50
3536 3531 #: rhodecode/templates/pullrequests/pullrequest_show.html:184
3537 3532 #: rhodecode/templates/users/user_profile.html:7
3538 3533 msgid "Edit"
3539 3534 msgstr ""
3540 3535
3541 3536 #: rhodecode/templates/admin/gists/show.html:58
3542 3537 msgid "Show as Raw"
3543 3538 msgstr ""
3544 3539
3545 3540 #: rhodecode/templates/admin/gists/show.html:62
3546 3541 msgid "Private Gist"
3547 3542 msgstr ""
3548 3543
3549 3544 #: rhodecode/templates/admin/gists/show.html:77
3550 3545 msgid "created"
3551 3546 msgstr ""
3552 3547
3553 3548 #: rhodecode/templates/admin/gists/show.html:91
3554 3549 #: rhodecode/templates/files/files_delete.html:50
3555 3550 #: rhodecode/templates/files/files_source.html:61
3556 3551 msgid "Show as raw"
3557 3552 msgstr ""
3558 3553
3559 3554 #: rhodecode/templates/admin/my_account/my_account.html:5
3560 #: rhodecode/templates/base/base.html:340
3555 #: rhodecode/templates/base/base.html:342
3561 3556 msgid "My account"
3562 3557 msgstr ""
3563 3558
3564 3559 #: rhodecode/templates/admin/my_account/my_account.html:12
3565 3560 msgid "My Account"
3566 3561 msgstr ""
3567 3562
3568 3563 #: rhodecode/templates/admin/my_account/my_account.html:29
3569 3564 #: rhodecode/templates/admin/my_account/my_account_profile.html:4
3570 3565 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:4
3571 3566 msgid "My Profile"
3572 3567 msgstr ""
3573 3568
3574 3569 #: rhodecode/templates/admin/my_account/my_account.html:31
3575 3570 msgid "Auth Tokens"
3576 3571 msgstr ""
3577 3572
3578 #: rhodecode/templates/admin/my_account/my_account.html:32
3573 #: rhodecode/templates/admin/my_account/my_account.html:34
3579 3574 msgid "OAuth Identities"
3580 3575 msgstr ""
3581 3576
3582 #: rhodecode/templates/admin/my_account/my_account.html:33
3583 msgid "My Emails"
3584 msgstr ""
3585
3586 #: rhodecode/templates/admin/my_account/my_account.html:34
3587 msgid "My Repositories"
3588 msgstr ""
3589
3590 #: rhodecode/templates/admin/my_account/my_account.html:35
3591 msgid "Watched"
3592 msgstr ""
3593
3594 #: rhodecode/templates/admin/my_account/my_account.html:36
3595 #: rhodecode/templates/admin/notifications/notifications.html:33
3596 #: rhodecode/templates/base/base.html:240
3597 msgid "Pull Requests"
3598 msgstr ""
3599
3600 3577 #: rhodecode/templates/admin/my_account/my_account.html:37
3578 msgid "My Emails"
3579 msgstr ""
3580
3581 #: rhodecode/templates/admin/my_account/my_account.html:38
3582 msgid "My Repositories"
3583 msgstr ""
3584
3585 #: rhodecode/templates/admin/my_account/my_account.html:39
3586 msgid "Watched"
3587 msgstr ""
3588
3589 #: rhodecode/templates/admin/my_account/my_account.html:40
3590 #: rhodecode/templates/admin/notifications/notifications.html:33
3591 #: rhodecode/templates/base/base.html:242
3592 msgid "Pull Requests"
3593 msgstr ""
3594
3595 #: rhodecode/templates/admin/my_account/my_account.html:41
3601 3596 msgid "My Permissions"
3602 3597 msgstr ""
3603 3598
3604 3599 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:3
3605 3600 msgid "Authentication Tokens"
3606 3601 msgstr ""
3607 3602
3608 3603 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:7
3609 3604 msgid "Built-in tokens can be used to authenticate with all possible options."
3610 3605 msgstr ""
3611 3606
3612 3607 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:8
3613 3608 msgid "Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg operations."
3614 3609 msgstr ""
3615 3610
3616 3611 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:14
3617 3612 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:11
3618 3613 msgid "Built-in"
3619 3614 msgstr ""
3620 3615
3621 3616 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:19
3622 3617 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:42
3623 3618 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:47
3624 3619 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:16
3625 3620 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:38
3626 3621 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:43
3627 3622 msgid "expires"
3628 3623 msgstr ""
3629 3624
3630 3625 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:25
3631 3626 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:22
3632 3627 #, python-format
3633 3628 msgid "Confirm to reset this auth token: %s"
3634 3629 msgstr ""
3635 3630
3636 3631 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:45
3637 3632 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:41
3638 3633 msgid "expired"
3639 3634 msgstr ""
3640 3635
3641 3636 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:55
3642 3637 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:51
3643 3638 #, python-format
3644 3639 msgid "Confirm to remove this auth token: %s"
3645 3640 msgstr ""
3646 3641
3647 3642 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:63
3648 3643 msgid "No additional auth token specified"
3649 3644 msgstr ""
3650 3645
3651 3646 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:74
3652 3647 msgid "New authentication token"
3653 3648 msgstr ""
3654 3649
3655 3650 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:83
3656 3651 #: rhodecode/templates/admin/my_account/my_account_emails.html:64
3657 3652 #: rhodecode/templates/admin/permissions/permissions_ips.html:60
3658 3653 #: rhodecode/templates/admin/repos/repo_edit_fields.html:65
3659 3654 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:80
3660 3655 #: rhodecode/templates/admin/users/user_edit_emails.html:62
3661 3656 #: rhodecode/templates/admin/users/user_edit_ips.html:69
3662 3657 msgid "Add"
3663 3658 msgstr ""
3664 3659
3665 3660 #: rhodecode/templates/admin/my_account/my_account_emails.html:5
3666 3661 msgid "Account Emails"
3667 3662 msgstr ""
3668 3663
3669 3664 #: rhodecode/templates/admin/my_account/my_account_emails.html:17
3670 3665 #: rhodecode/templates/admin/users/user_edit_emails.html:16
3671 3666 msgid "Primary"
3672 3667 msgstr ""
3673 3668
3674 3669 #: rhodecode/templates/admin/my_account/my_account_emails.html:31
3675 3670 #: rhodecode/templates/admin/users/user_edit_emails.html:30
3676 3671 #, python-format
3677 3672 msgid "Confirm to delete this email: %s"
3678 3673 msgstr ""
3679 3674
3680 3675 #: rhodecode/templates/admin/my_account/my_account_emails.html:42
3681 3676 #: rhodecode/templates/admin/users/user_edit_emails.html:41
3682 3677 msgid "No additional emails specified"
3683 3678 msgstr ""
3684 3679
3685 3680 #: rhodecode/templates/admin/my_account/my_account_emails.html:57
3686 3681 #: rhodecode/templates/admin/users/user_edit_emails.html:55
3687 3682 msgid "New email address"
3688 3683 msgstr ""
3689 3684
3690 #: rhodecode/templates/admin/my_account/my_account_oauth.html:5
3691 msgid "Oauth Identities"
3692 msgstr ""
3693
3694 #: rhodecode/templates/admin/my_account/my_account_oauth.html:9
3695 msgid "External services currently connected with your Rhodecode user"
3696 msgstr ""
3697
3698 #: rhodecode/templates/admin/my_account/my_account_oauth.html:15
3699 msgid "No social authentication plugins are enabled by administrator"
3700 msgstr ""
3701
3702 #: rhodecode/templates/admin/my_account/my_account_oauth.html:49
3703 msgid "Confirm to remove this provider from your account"
3704 msgstr ""
3705
3706 #: rhodecode/templates/admin/my_account/my_account_oauth.html:59
3707 msgid "You have no accounts linked yet"
3708 msgstr ""
3709
3710 3685 #: rhodecode/templates/admin/my_account/my_account_password.html:3
3711 3686 msgid "Change Your Account Password"
3712 3687 msgstr ""
3713 3688
3714 3689 #: rhodecode/templates/admin/my_account/my_account_password.html:10
3715 3690 msgid "Current Password"
3716 3691 msgstr ""
3717 3692
3718 3693 #: rhodecode/templates/admin/my_account/my_account_password.html:19
3719 3694 #: rhodecode/templates/admin/users/user_edit_profile.html:74
3720 3695 msgid "New Password"
3721 3696 msgstr ""
3722 3697
3723 3698 #: rhodecode/templates/admin/my_account/my_account_password.html:28
3724 3699 msgid "Confirm New Password"
3725 3700 msgstr ""
3726 3701
3727 3702 #: rhodecode/templates/admin/my_account/my_account_profile.html:11
3728 3703 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:52
3729 3704 #: rhodecode/templates/admin/users/user_edit_profile.html:25
3730 3705 #: rhodecode/templates/users/user_profile.html:14
3731 3706 msgid "Photo"
3732 3707 msgstr ""
3733 3708
3734 3709 #: rhodecode/templates/admin/my_account/my_account_profile.html:18
3735 3710 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:60
3736 3711 #: rhodecode/templates/admin/users/user_edit_profile.html:33
3737 3712 #: rhodecode/templates/users/user_profile.html:21
3738 3713 msgid "Avatars are disabled"
3739 3714 msgstr ""
3740 3715
3741 3716 #: rhodecode/templates/admin/my_account/my_account_profile.html:51
3742 3717 #: rhodecode/templates/users/user_profile.html:54
3743 3718 msgid "Missing email, please update your user email address."
3744 3719 msgstr ""
3745 3720
3746 3721 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:18
3747 3722 msgid "Your user account details are managed by an external source, i.e. LDAP. Details cannot be managed here."
3748 3723 msgstr ""
3749 3724
3750 3725 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:57
3751 3726 msgid "Change your avatar at"
3752 3727 msgstr ""
3753 3728
3754 3729 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:6
3755 3730 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:8
3756 3731 msgid "Show Closed Pull Requests"
3757 3732 msgstr ""
3758 3733
3759 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:16
3734 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:15
3760 3735 msgid "Pull Requests You Opened"
3761 3736 msgstr ""
3762 3737
3738 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:23
3739 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:86
3740 msgid "Target Repo"
3741 msgstr ""
3742
3743 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:26
3744 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:89
3745 #: rhodecode/templates/admin/settings/settings_global.html:9
3746 #: rhodecode/templates/email_templates/pull_request_review.mako:28
3747 #: rhodecode/templates/email_templates/pull_request_review.mako:65
3748 #: rhodecode/templates/pullrequests/pullrequest.html:38
3749 #: rhodecode/templates/pullrequests/pullrequests.html:104
3750 msgid "Title"
3751 msgstr ""
3752
3763 3753 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:27
3764 #, python-format
3765 msgid "Pull request #%s opened on %s"
3766 msgstr ""
3767
3768 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:29
3769 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:64
3754 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:90
3755 msgid "Opened On"
3756 msgstr ""
3757
3758 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:41
3759 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:103
3760 #: rhodecode/templates/changelog/changelog.html:141
3761 #: rhodecode/templates/compare/compare_commits.html:49
3762 #: rhodecode/templates/search/search_commit.html:36
3763 msgid "Expand commit message"
3764 msgstr ""
3765
3766 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:50
3767 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:112
3770 3768 #: rhodecode/templates/changeset/changeset_file_comment.html:284
3771 3769 #: rhodecode/templates/pullrequests/pullrequest_show.html:14
3772 3770 #: rhodecode/templates/pullrequests/pullrequest_show.html:112
3773 #: rhodecode/templates/pullrequests/pullrequests.html:51
3771 #: rhodecode/templates/pullrequests/pullrequests.html:52
3774 3772 msgid "Closed"
3775 3773 msgstr ""
3776 3774
3777 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:35
3775 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:62
3778 3776 msgid "Confirm to delete this pull request"
3779 3777 msgstr ""
3780 3778
3781 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:42
3779 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:69
3782 3780 msgid "You currently have no open pull requests."
3783 3781 msgstr ""
3784 3782
3785 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:50
3783 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:77
3786 3784 msgid "Pull Requests You Participate In"
3787 3785 msgstr ""
3788 3786
3789 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:61
3790 #, python-format
3791 msgid "Pull request #%s opened by %s on %s"
3792 msgstr ""
3793
3794 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:70
3787 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:125
3795 3788 msgid "There are currently no open pull requests requiring your participation."
3796 3789 msgstr ""
3797 3790
3798 3791 #: rhodecode/templates/admin/my_account/my_account_repos.html:3
3799 3792 msgid "Repositories You Own"
3800 3793 msgstr ""
3801 3794
3802 3795 #: rhodecode/templates/admin/my_account/my_account_watched.html:3
3803 3796 msgid "Your Watched Repositories"
3804 3797 msgstr ""
3805 3798
3806 3799 #: rhodecode/templates/admin/notifications/notifications.html:5
3807 3800 #: rhodecode/templates/admin/notifications/notifications.html:12
3808 3801 msgid "My Notifications"
3809 3802 msgstr ""
3810 3803
3811 3804 #: rhodecode/templates/admin/notifications/notifications.html:31
3812 3805 msgid "All"
3813 3806 msgstr ""
3814 3807
3815 3808 #: rhodecode/templates/admin/notifications/notifications.html:32
3816 3809 #: rhodecode/templates/changeset/changeset.html:140
3817 3810 #: rhodecode/templates/pullrequests/pullrequest_show.html:133
3818 3811 msgid "Comments"
3819 3812 msgstr ""
3820 3813
3821 3814 #: rhodecode/templates/admin/notifications/notifications.html:37
3822 3815 msgid "Mark all as read"
3823 3816 msgstr ""
3824 3817
3825 3818 #: rhodecode/templates/admin/notifications/notifications_data.html:39
3826 3819 msgid "No notifications here yet"
3827 3820 msgstr ""
3828 3821
3829 3822 #: rhodecode/templates/admin/notifications/show_notification.html:5
3830 3823 #: rhodecode/templates/admin/notifications/show_notification.html:14
3831 3824 msgid "Show notification"
3832 3825 msgstr ""
3833 3826
3834 3827 #: rhodecode/templates/admin/notifications/show_notification.html:12
3835 3828 msgid "Notifications"
3836 3829 msgstr ""
3837 3830
3838 3831 #: rhodecode/templates/admin/permissions/permissions.html:5
3839 3832 msgid "Permissions Administration"
3840 3833 msgstr ""
3841 3834
3842 3835 #: rhodecode/templates/admin/permissions/permissions.html:14
3843 3836 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:45
3844 3837 #: rhodecode/templates/admin/repos/repo_edit.html:42
3845 3838 #: rhodecode/templates/admin/user_groups/user_group_edit.html:34
3846 3839 #: rhodecode/templates/base/base.html:83
3847 3840 msgid "Permissions"
3848 3841 msgstr ""
3849 3842
3850 3843 #: rhodecode/templates/admin/permissions/permissions.html:33
3851 3844 msgid "Application"
3852 3845 msgstr ""
3853 3846
3854 3847 #: rhodecode/templates/admin/permissions/permissions.html:39
3855 3848 msgid "Object"
3856 3849 msgstr ""
3857 3850
3858 3851 #: rhodecode/templates/admin/permissions/permissions.html:42
3859 3852 msgid "IP Whitelist"
3860 3853 msgstr ""
3861 3854
3862 3855 #: rhodecode/templates/admin/permissions/permissions.html:45
3863 3856 msgid "Overview"
3864 3857 msgstr ""
3865 3858
3866 3859 #: rhodecode/templates/admin/permissions/permissions_application.html:3
3867 3860 msgid "System Wide Application Permissions"
3868 3861 msgstr ""
3869 3862
3870 3863 #: rhodecode/templates/admin/permissions/permissions_application.html:12
3871 3864 msgid "Anonymous Access"
3872 3865 msgstr ""
3873 3866
3874 3867 #: rhodecode/templates/admin/permissions/permissions_application.html:18
3875 3868 #, python-format
3876 3869 msgid "Allow access to RhodeCode Enterprise without requiring users to login. Anonymous users get the %s permission settings."
3877 3870 msgstr ""
3878 3871
3879 3872 #: rhodecode/templates/admin/permissions/permissions_application.html:24
3880 3873 msgid "Registration"
3881 3874 msgstr ""
3882 3875
3883 3876 #: rhodecode/templates/admin/permissions/permissions_application.html:33
3884 3877 msgid "Registration Page Message"
3885 3878 msgstr ""
3886 3879
3887 3880 #: rhodecode/templates/admin/permissions/permissions_application.html:37
3888 3881 msgid "Custom message to be displayed on the registration page. HTML syntax is supported."
3889 3882 msgstr ""
3890 3883
3891 3884 #: rhodecode/templates/admin/permissions/permissions_application.html:43
3892 3885 msgid "External Authentication Account Activation"
3893 3886 msgstr ""
3894 3887
3895 3888 #: rhodecode/templates/admin/permissions/permissions_ips.html:5
3896 3889 msgid "Default IP Whitelist For All Users"
3897 3890 msgstr ""
3898 3891
3899 3892 #: rhodecode/templates/admin/permissions/permissions_ips.html:27
3900 3893 #: rhodecode/templates/admin/users/user_edit_ips.html:35
3901 3894 #, python-format
3902 3895 msgid "Confirm to delete this ip: %s"
3903 3896 msgstr ""
3904 3897
3905 3898 #: rhodecode/templates/admin/permissions/permissions_ips.html:34
3906 3899 #: rhodecode/templates/admin/users/user_edit_ips.html:43
3907 3900 msgid "All IP addresses are allowed"
3908 3901 msgstr ""
3909 3902
3910 3903 #: rhodecode/templates/admin/permissions/permissions_ips.html:49
3911 3904 #: rhodecode/templates/admin/users/user_edit_ips.html:59
3912 3905 msgid "New IP Address"
3913 3906 msgstr ""
3914 3907
3915 3908 #: rhodecode/templates/admin/permissions/permissions_ips.html:53
3916 3909 #: rhodecode/templates/admin/users/user_edit_ips.html:62
3917 3910 msgid "Description..."
3918 3911 msgstr ""
3919 3912
3920 3913 #: rhodecode/templates/admin/permissions/permissions_ips.html:54
3921 3914 msgid ""
3922 3915 "Enter a comma separated list of IP Addresses like 127.0.0.1,\n"
3923 3916 "or use an IP Address with a mask 127.0.0.1/24, to create a network range.\n"
3924 3917 "To specify multiple addresses in a range, use the 127.0.0.1-127.0.0.10 syntax"
3925 3918 msgstr ""
3926 3919
3927 3920 #: rhodecode/templates/admin/permissions/permissions_objects.html:3
3928 3921 msgid "Default Permissions for Repositories, User Groups and Repository Groups."
3929 3922 msgstr ""
3930 3923
3931 3924 #: rhodecode/templates/admin/permissions/permissions_objects.html:6
3932 3925 msgid "Default system permissions. Each permissions management entity will be created with the following default settings. Check the overwrite checkbox to force any permission changes on already existing settings."
3933 3926 msgstr ""
3934 3927
3935 3928 #: rhodecode/templates/admin/permissions/permissions_objects.html:20
3936 3929 msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost"
3937 3930 msgstr ""
3938 3931
3939 3932 #: rhodecode/templates/admin/permissions/permissions_objects.html:21
3940 3933 #: rhodecode/templates/admin/permissions/permissions_objects.html:35
3941 3934 #: rhodecode/templates/admin/permissions/permissions_objects.html:49
3942 3935 msgid "Overwrite Existing Settings"
3943 3936 msgstr ""
3944 3937
3945 3938 #: rhodecode/templates/admin/permissions/permissions_objects.html:28
3946 3939 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:14
3947 3940 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:127
3948 3941 msgid "Repository Groups"
3949 3942 msgstr ""
3950 3943
3951 3944 #: rhodecode/templates/admin/permissions/permissions_objects.html:34
3952 3945 msgid "All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
3953 3946 msgstr ""
3954 3947
3955 3948 #: rhodecode/templates/admin/permissions/permissions_objects.html:42
3956 3949 #: rhodecode/templates/admin/user_groups/user_group_edit.html:14
3957 3950 msgid "User Groups"
3958 3951 msgstr ""
3959 3952
3960 3953 #: rhodecode/templates/admin/permissions/permissions_objects.html:48
3961 3954 msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
3962 3955 msgstr ""
3963 3956
3964 3957 #: rhodecode/templates/admin/permissions/permissions_perms.html:1
3965 3958 msgid "Default User Permissions Overview"
3966 3959 msgstr ""
3967 3960
3968 3961 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:5
3969 3962 #: rhodecode/templates/admin/users/user_add.html:116
3970 3963 msgid "Add repository group"
3971 3964 msgstr ""
3972 3965
3973 3966 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:14
3974 3967 #: rhodecode/templates/admin/users/user_edit_advanced.html:12
3975 #: rhodecode/templates/base/base.html:80 rhodecode/templates/base/base.html:150
3968 #: rhodecode/templates/base/base.html:80 rhodecode/templates/base/base.html:152
3976 3969 msgid "Repository groups"
3977 3970 msgstr ""
3978 3971
3979 3972 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:36
3980 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:13
3973 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:15
3981 3974 msgid "Group Name"
3982 3975 msgstr ""
3983 3976
3984 3977 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:54
3985 3978 msgid "Group Parent"
3986 3979 msgstr ""
3987 3980
3988 3981 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:63
3989 3982 #: rhodecode/templates/admin/repos/repo_add_base.html:53
3990 3983 msgid "Copy Parent Group Permissions"
3991 3984 msgstr ""
3992 3985
3993 3986 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:67
3994 3987 msgid "Copy permission settings from parent repository group."
3995 3988 msgstr ""
3996 3989
3997 3990 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:5
3998 3991 #, python-format
3999 3992 msgid "%s repository group settings"
4000 3993 msgstr ""
4001 3994
4002 3995 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:24
4003 3996 msgid "Add Child Group"
4004 3997 msgstr ""
4005 3998
4006 3999 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:44
4007 4000 #: rhodecode/templates/admin/repos/repo_edit.html:15
4008 4001 #: rhodecode/templates/admin/repos/repo_edit.html:39
4009 4002 #: rhodecode/templates/admin/settings/settings.html:14
4010 4003 #: rhodecode/templates/admin/user_groups/user_group_edit.html:33
4011 #: rhodecode/templates/base/base.html:86 rhodecode/templates/base/base.html:248
4004 #: rhodecode/templates/base/base.html:86 rhodecode/templates/base/base.html:250
4012 4005 msgid "Settings"
4013 4006 msgstr ""
4014 4007
4015 4008 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:46
4016 4009 #: rhodecode/templates/admin/repos/repo_edit.html:45
4017 4010 #: rhodecode/templates/admin/user_groups/user_group_edit.html:35
4018 4011 #: rhodecode/templates/admin/users/user_edit.html:35
4019 4012 msgid "Advanced"
4020 4013 msgstr ""
4021 4014
4022 4015 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:8
4023 4016 msgid "Total repositories"
4024 4017 msgstr ""
4025 4018
4026 4019 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:9
4027 4020 msgid "Top level repositories"
4028 4021 msgstr ""
4029 4022
4030 4023 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:11
4031 4024 msgid "Children groups"
4032 4025 msgstr ""
4033 4026
4034 4027 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:17
4035 4028 #, python-format
4036 4029 msgid "Repository Group: %s"
4037 4030 msgstr ""
4038 4031
4039 4032 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:27
4040 4033 msgid "Delete repository group"
4041 4034 msgstr ""
4042 4035
4043 4036 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:35
4044 4037 #, python-format
4045 4038 msgid "This repository group includes %s children repository group."
4046 4039 msgid_plural "This repository group includes %s children repository groups."
4047 4040 msgstr[0] ""
4048 4041 msgstr[1] ""
4049 4042
4050 4043 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:44
4051 4044 #, python-format
4052 4045 msgid "This repository group includes %s repository."
4053 4046 msgid_plural "This repository group includes %s repositories."
4054 4047 msgstr[0] ""
4055 4048 msgstr[1] ""
4056 4049
4057 4050 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:56
4058 4051 #, python-format
4059 4052 msgid "Confirm to delete this group: %s"
4060 4053 msgstr ""
4061 4054
4062 4055 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html:57
4063 4056 msgid "Delete this repository group"
4064 4057 msgstr ""
4065 4058
4066 4059 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:5
4067 4060 msgid "Repository Group Permissions"
4068 4061 msgstr ""
4069 4062
4070 4063 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:11
4071 4064 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:126
4072 4065 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:12
4073 4066 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:11
4074 4067 msgid "None"
4075 4068 msgstr ""
4076 4069
4077 4070 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:12
4078 4071 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:13
4079 4072 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:12
4080 4073 msgid "Read"
4081 4074 msgstr ""
4082 4075
4083 4076 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:13
4084 4077 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:14
4085 4078 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:13
4086 4079 msgid "Write"
4087 4080 msgstr ""
4088 4081
4089 4082 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:15
4090 4083 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:16
4091 4084 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:15
4092 4085 msgid "User/User Group"
4093 4086 msgstr ""
4094 4087
4095 4088 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:31
4096 4089 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:31
4097 4090 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:31
4098 4091 msgid "super admin"
4099 4092 msgstr ""
4100 4093
4101 4094 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:34
4102 4095 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:34
4103 4096 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:34
4104 #: rhodecode/templates/pullrequests/pullrequest_show.html:200
4097 #: rhodecode/templates/pullrequests/pullrequest_show.html:199
4105 4098 msgid "owner"
4106 4099 msgstr ""
4107 4100
4108 4101 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:52
4109 4102 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:76
4110 4103 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:61
4111 4104 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:52
4112 4105 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:76
4113 4106 msgid "permission for all other users"
4114 4107 msgstr ""
4115 4108
4116 4109 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:62
4117 4110 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:109
4118 4111 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:71
4119 4112 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:99
4120 4113 msgid "Revoke"
4121 4114 msgstr ""
4122 4115
4123 4116 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:80
4124 4117 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:80
4125 4118 msgid "delegated admin"
4126 4119 msgstr ""
4127 4120
4128 4121 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:118
4129 4122 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:107
4130 4123 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:117
4131 4124 #: rhodecode/templates/base/issue_tracker_settings.html:84
4132 4125 msgid "Add new"
4133 4126 msgstr ""
4134 4127
4135 4128 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:123
4136 4129 msgid "Apply to children"
4137 4130 msgstr ""
4138 4131
4139 4132 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:129
4140 4133 msgid "Both"
4141 4134 msgstr ""
4142 4135
4143 4136 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:130
4144 4137 msgid "Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen."
4145 4138 msgstr ""
4146 4139
4147 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:4
4140 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:6
4148 4141 #, python-format
4149 4142 msgid "Settings for Repository Group: %s"
4150 4143 msgstr ""
4151 4144
4152 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:25
4153 msgid "Change Repository Group Owner."
4154 msgstr ""
4155
4156 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:39
4157 msgid "Group parent"
4158 msgstr ""
4159
4160 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:47
4161 msgid "Enable Repository Locking"
4145 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:36
4146 msgid "Change owner of this repository group."
4162 4147 msgstr ""
4163 4148
4164 4149 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:51
4150 msgid "Group parent"
4151 msgstr ""
4152
4153 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:59
4154 msgid "Enable Repository Locking"
4155 msgstr ""
4156
4157 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:63
4165 4158 msgid "Repository locking will be enabled on all subgroups and repositories inside this repository group. Pulling from a repository locks it, and it is unlocked by pushing back by the same user."
4166 4159 msgstr ""
4167 4160
4168 4161 #: rhodecode/templates/admin/repo_groups/repo_groups.html:5
4169 4162 msgid "Repository groups administration"
4170 4163 msgstr ""
4171 4164
4172 4165 #: rhodecode/templates/admin/repo_groups/repo_groups.html:13
4173 4166 msgid "repository groups"
4174 4167 msgstr ""
4175 4168
4176 4169 #: rhodecode/templates/admin/repo_groups/repo_groups.html:58
4177 4170 msgid "Number of top level repositories"
4178 4171 msgstr ""
4179 4172
4180 4173 #: rhodecode/templates/admin/repos/repo_add.html:5
4181 4174 msgid "Add repository"
4182 4175 msgstr ""
4183 4176
4184 4177 #: rhodecode/templates/admin/repos/repo_add_base.html:14
4185 4178 msgid "Import Existing Repository ?"
4186 4179 msgstr ""
4187 4180
4188 4181 #: rhodecode/templates/admin/repos/repo_add_base.html:23
4189 #: rhodecode/templates/base/base.html:195
4182 #: rhodecode/templates/base/base.html:197
4190 4183 msgid "Clone from"
4191 4184 msgstr ""
4192 4185
4193 4186 #: rhodecode/templates/admin/repos/repo_add_base.html:27
4194 4187 msgid "Optional http[s] URL from which to clone a repository."
4195 4188 msgstr ""
4196 4189
4197 4190 #: rhodecode/templates/admin/repos/repo_add_base.html:36
4198 #: rhodecode/templates/admin/repos/repo_edit_settings.html:87
4191 #: rhodecode/templates/admin/repos/repo_edit_settings.html:100
4199 4192 #: rhodecode/templates/forks/fork.html:52
4200 4193 msgid "Keep it short and to the point. Use a README file for longer descriptions."
4201 4194 msgstr ""
4202 4195
4203 4196 #: rhodecode/templates/admin/repos/repo_add_base.html:41
4204 4197 msgid "Repository Group"
4205 4198 msgstr ""
4206 4199
4207 4200 #: rhodecode/templates/admin/repos/repo_add_base.html:46
4208 #: rhodecode/templates/admin/repos/repo_edit_settings.html:58
4201 #: rhodecode/templates/admin/repos/repo_edit_settings.html:61
4209 4202 #: rhodecode/templates/forks/fork.html:63
4210 4203 #, python-format
4211 4204 msgid "Select my personal group (%(repo_group_name)s)"
4212 4205 msgstr ""
4213 4206
4214 4207 #: rhodecode/templates/admin/repos/repo_add_base.html:48
4215 4208 #: rhodecode/templates/forks/fork.html:65
4216 4209 msgid "Optionally select a group to put this repository into."
4217 4210 msgstr ""
4218 4211
4219 4212 #: rhodecode/templates/admin/repos/repo_add_base.html:57
4220 4213 msgid "Copy permission set from the parent repository group."
4221 4214 msgstr ""
4222 4215
4223 4216 #: rhodecode/templates/admin/repos/repo_add_base.html:66
4224 4217 msgid "Set the type of repository to create."
4225 4218 msgstr ""
4226 4219
4227 4220 #: rhodecode/templates/admin/repos/repo_add_base.html:71
4228 #: rhodecode/templates/admin/repos/repo_edit_settings.html:65
4221 #: rhodecode/templates/admin/repos/repo_edit_settings.html:68
4229 4222 #: rhodecode/templates/forks/fork.html:71
4230 4223 msgid "Landing commit"
4231 4224 msgstr ""
4232 4225
4233 4226 #: rhodecode/templates/admin/repos/repo_add_base.html:75
4234 4227 msgid "The default commit for file pages, downloads, full text search index, and README generation."
4235 4228 msgstr ""
4236 4229
4237 4230 #: rhodecode/templates/admin/repos/repo_creating.html:9
4238 4231 #, python-format
4239 4232 msgid "%s Creating repository"
4240 4233 msgstr ""
4241 4234
4242 4235 #: rhodecode/templates/admin/repos/repo_creating.html:16
4243 4236 msgid "Creating repository"
4244 4237 msgstr ""
4245 4238
4246 4239 #: rhodecode/templates/admin/repos/repo_creating.html:30
4247 4240 #, python-format
4248 4241 msgid "Repository \"%(repo_name)s\" is being created, you will be redirected when this process is finished.repo_name"
4249 4242 msgstr ""
4250 4243
4251 4244 #: rhodecode/templates/admin/repos/repo_edit.html:8
4252 4245 #, python-format
4253 4246 msgid "%s repository settings"
4254 4247 msgstr ""
4255 4248
4256 4249 #: rhodecode/templates/admin/repos/repo_edit.html:51
4257 4250 msgid "Extra Fields"
4258 4251 msgstr ""
4259 4252
4260 4253 #: rhodecode/templates/admin/repos/repo_edit.html:57
4261 4254 msgid "Caches"
4262 4255 msgstr ""
4263 4256
4264 4257 #: rhodecode/templates/admin/repos/repo_edit.html:61
4265 4258 msgid "Remote"
4266 4259 msgstr ""
4267 4260
4268 4261 #: rhodecode/templates/admin/repos/repo_edit.html:65
4269 4262 #: rhodecode/templates/summary/components.html:135
4270 4263 msgid "Statistics"
4271 4264 msgstr ""
4272 4265
4273 4266 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:7
4274 4267 #: rhodecode/templates/pullrequests/pullrequests.html:108
4275 4268 msgid "Updated on"
4276 4269 msgstr ""
4277 4270
4278 4271 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:8
4279 4272 msgid "Cached Commit id"
4280 4273 msgstr ""
4281 4274
4282 4275 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:14
4283 4276 #, python-format
4284 4277 msgid "Repository: %s"
4285 4278 msgstr ""
4286 4279
4287 4280 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:24
4288 4281 msgid "Fork Reference"
4289 4282 msgstr ""
4290 4283
4291 4284 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:30
4292 4285 #, python-format
4293 4286 msgid "This repository is a fork of %(repo_link)s"
4294 4287 msgstr ""
4295 4288
4296 4289 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:36
4297 4290 #: rhodecode/templates/admin/repos/repo_edit_fork.html:5
4298 4291 msgid "Set"
4299 4292 msgstr ""
4300 4293
4301 4294 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:39
4302 4295 #: rhodecode/templates/admin/repos/repo_edit_fork.html:9
4303 4296 msgid "Manually set this repository as a fork of another from the list"
4304 4297 msgstr ""
4305 4298
4306 4299 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:48
4307 4300 msgid "Public Journal Visibility"
4308 4301 msgstr ""
4309 4302
4310 4303 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:56
4311 4304 msgid "Remove from Public Journal"
4312 4305 msgstr ""
4313 4306
4314 4307 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:60
4315 4308 msgid "Add to Public Journal"
4316 4309 msgstr ""
4317 4310
4318 4311 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:65
4319 4312 msgid "All actions made on this repository will be visible to everyone following the public journal."
4320 4313 msgstr ""
4321 4314
4322 4315 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:74
4323 4316 msgid "Locking state"
4324 4317 msgstr ""
4325 4318
4326 4319 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:83
4327 4320 msgid "This Repository is not currently locked."
4328 4321 msgstr ""
4329 4322
4330 4323 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:90
4331 4324 msgid "Confirm to unlock repository."
4332 4325 msgstr ""
4333 4326
4334 4327 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:92
4335 4328 msgid "Unlock repository"
4336 4329 msgstr ""
4337 4330
4338 4331 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:97
4339 4332 msgid "Confirm to lock repository."
4340 4333 msgstr ""
4341 4334
4342 4335 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:99
4343 4336 msgid "Lock Repository"
4344 4337 msgstr ""
4345 4338
4346 4339 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:105
4347 4340 msgid "Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again."
4348 4341 msgstr ""
4349 4342
4350 4343 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:114
4351 4344 msgid "Delete repository"
4352 4345 msgstr ""
4353 4346
4354 4347 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:121
4355 4348 #, python-format
4356 4349 msgid "This repository has %s fork."
4357 4350 msgid_plural "This repository has %s forks."
4358 4351 msgstr[0] ""
4359 4352 msgstr[1] ""
4360 4353
4361 4354 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:125
4362 4355 msgid "Detach forks"
4363 4356 msgstr ""
4364 4357
4365 4358 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:130
4366 4359 msgid "Delete forks"
4367 4360 msgstr ""
4368 4361
4369 4362 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:139
4370 4363 #: rhodecode/templates/data_table/_dt_elements.html:118
4371 4364 #, python-format
4372 4365 msgid "Confirm to delete this repository: %s"
4373 4366 msgstr ""
4374 4367
4375 4368 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:141
4376 4369 msgid "Delete This Repository"
4377 4370 msgstr ""
4378 4371
4379 4372 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:146
4380 4373 msgid "This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command."
4381 4374 msgstr ""
4382 4375
4383 4376 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:180
4384 4377 msgid "Change repository"
4385 4378 msgstr ""
4386 4379
4387 4380 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:180
4388 4381 msgid "Pick repository"
4389 4382 msgstr ""
4390 4383
4391 4384 #: rhodecode/templates/admin/repos/repo_edit_caches.html:3
4392 4385 msgid "Invalidate Cache for Repository"
4393 4386 msgstr ""
4394 4387
4395 4388 #: rhodecode/templates/admin/repos/repo_edit_caches.html:10
4396 4389 msgid "Invalidate repository cache"
4397 4390 msgstr ""
4398 4391
4399 4392 #: rhodecode/templates/admin/repos/repo_edit_caches.html:10
4400 4393 msgid "Confirm to invalidate repository cache"
4401 4394 msgstr ""
4402 4395
4403 4396 #: rhodecode/templates/admin/repos/repo_edit_caches.html:14
4404 4397 msgid "Manually invalidate the repository cache. On the next access a repository cache will be recreated."
4405 4398 msgstr ""
4406 4399
4407 4400 #: rhodecode/templates/admin/repos/repo_edit_caches.html:28
4408 4401 #, python-format
4409 4402 msgid "List of repository caches (%(count)s entry)"
4410 4403 msgid_plural "List of repository caches (%(count)s entries)"
4411 4404 msgstr[0] ""
4412 4405 msgstr[1] ""
4413 4406
4414 4407 #: rhodecode/templates/admin/repos/repo_edit_caches.html:35
4415 4408 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:32
4416 4409 #: rhodecode/templates/base/issue_tracker_settings.html:13
4417 4410 msgid "Prefix"
4418 4411 msgstr ""
4419 4412
4420 4413 #: rhodecode/templates/admin/repos/repo_edit_caches.html:36
4421 4414 #: rhodecode/templates/admin/repos/repo_edit_fields.html:11
4422 4415 msgid "Key"
4423 4416 msgstr ""
4424 4417
4425 4418 #: rhodecode/templates/admin/repos/repo_edit_caches.html:37
4426 4419 #: rhodecode/templates/admin/user_groups/user_group_add.html:52
4427 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:38
4420 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:51
4428 4421 #: rhodecode/templates/admin/user_groups/user_groups.html:62
4429 4422 #: rhodecode/templates/admin/users/user_add.html:97
4430 4423 #: rhodecode/templates/admin/users/user_edit_profile.html:90
4431 4424 #: rhodecode/templates/admin/users/users.html:100
4432 4425 msgid "Active"
4433 4426 msgstr ""
4434 4427
4435 4428 #: rhodecode/templates/admin/repos/repo_edit_fields.html:3
4436 4429 msgid "Custom extra fields for this repository"
4437 4430 msgstr ""
4438 4431
4439 4432 #: rhodecode/templates/admin/repos/repo_edit_fields.html:10
4440 4433 msgid "Label"
4441 4434 msgstr ""
4442 4435
4443 4436 #: rhodecode/templates/admin/repos/repo_edit_fields.html:24
4444 4437 #, python-format
4445 4438 msgid "Confirm to delete this field: %s"
4446 4439 msgstr ""
4447 4440
4448 4441 #: rhodecode/templates/admin/repos/repo_edit_fields.html:40
4449 4442 msgid "New Field Key"
4450 4443 msgstr ""
4451 4444
4452 4445 #: rhodecode/templates/admin/repos/repo_edit_fields.html:48
4453 4446 msgid "New Field Label"
4454 4447 msgstr ""
4455 4448
4456 4449 #: rhodecode/templates/admin/repos/repo_edit_fields.html:51
4457 4450 msgid "Enter short label"
4458 4451 msgstr ""
4459 4452
4460 4453 #: rhodecode/templates/admin/repos/repo_edit_fields.html:57
4461 4454 msgid "New Field Description"
4462 4455 msgstr ""
4463 4456
4464 4457 #: rhodecode/templates/admin/repos/repo_edit_fields.html:60
4465 4458 msgid "Enter a full description for the field"
4466 4459 msgstr ""
4467 4460
4468 4461 #: rhodecode/templates/admin/repos/repo_edit_fields.html:73
4469 4462 msgid "Extra fields are disabled. You can enable them from the Admin/Settings/Visual page."
4470 4463 msgstr ""
4471 4464
4472 4465 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:9
4473 4466 #: rhodecode/templates/admin/repos/repo_edit_vcs.html:9
4474 4467 msgid "Inherit from global settings"
4475 4468 msgstr ""
4476 4469
4477 4470 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:14
4478 4471 msgid "Select to inherit global patterns for issue tracker."
4479 4472 msgstr ""
4480 4473
4481 4474 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:24
4482 4475 msgid "Inherited Issue Tracker Patterns"
4483 4476 msgstr ""
4484 4477
4485 4478 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:30
4486 4479 #: rhodecode/templates/base/issue_tracker_settings.html:11
4487 4480 msgid "Pattern"
4488 4481 msgstr ""
4489 4482
4490 4483 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:31
4491 4484 #: rhodecode/templates/base/issue_tracker_settings.html:12
4492 4485 msgid "Url"
4493 4486 msgstr ""
4494 4487
4495 4488 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:70
4496 4489 #: rhodecode/templates/admin/settings/settings_issuetracker.html:5
4497 4490 msgid "Issue Tracker / Wiki Patterns"
4498 4491 msgstr ""
4499 4492
4500 4493 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.html:91
4501 4494 #: rhodecode/templates/admin/settings/settings_issuetracker.html:24
4502 4495 msgid "Test Patterns"
4503 4496 msgstr ""
4504 4497
4505 4498 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:5
4506 4499 msgid "Repository Permissions"
4507 4500 msgstr ""
4508 4501
4509 4502 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:43
4510 4503 msgid "private repository"
4511 4504 msgstr ""
4512 4505
4513 4506 #: rhodecode/templates/admin/repos/repo_edit_permissions.html:48
4514 4507 msgid "only users/user groups explicitly added here will have access"
4515 4508 msgstr ""
4516 4509
4517 4510 #: rhodecode/templates/admin/repos/repo_edit_remote.html:3
4518 4511 msgid "Remote url"
4519 4512 msgstr ""
4520 4513
4521 4514 #: rhodecode/templates/admin/repos/repo_edit_remote.html:9
4522 4515 msgid "Remote mirror url"
4523 4516 msgstr ""
4524 4517
4525 4518 #: rhodecode/templates/admin/repos/repo_edit_remote.html:14
4526 4519 #: rhodecode/templates/admin/repos/repo_edit_remote.html:22
4527 4520 msgid "Pull changes from remote location"
4528 4521 msgstr ""
4529 4522
4530 4523 #: rhodecode/templates/admin/repos/repo_edit_remote.html:14
4531 4524 msgid "Confirm to pull changes from remote side"
4532 4525 msgstr ""
4533 4526
4534 4527 #: rhodecode/templates/admin/repos/repo_edit_remote.html:19
4535 4528 msgid "This repository does not have any remote mirror url set."
4536 4529 msgstr ""
4537 4530
4538 #: rhodecode/templates/admin/repos/repo_edit_settings.html:3
4531 #: rhodecode/templates/admin/repos/repo_edit_settings.html:6
4539 4532 #, python-format
4540 4533 msgid "Settings for Repository: %s"
4541 4534 msgstr ""
4542 4535
4543 #: rhodecode/templates/admin/repos/repo_edit_settings.html:16
4544 msgid "Non-changeable id"
4545 msgstr ""
4546
4547 #: rhodecode/templates/admin/repos/repo_edit_settings.html:16
4548 msgid "what is that ?"
4549 msgstr ""
4550
4551 #: rhodecode/templates/admin/repos/repo_edit_settings.html:18
4552 msgid "URL by id"
4553 msgstr ""
4554
4555 4536 #: rhodecode/templates/admin/repos/repo_edit_settings.html:19
4537 msgid "Non-changeable id"
4538 msgstr ""
4539
4540 #: rhodecode/templates/admin/repos/repo_edit_settings.html:19
4541 msgid "what is that ?"
4542 msgstr ""
4543
4544 #: rhodecode/templates/admin/repos/repo_edit_settings.html:21
4545 msgid "URL by id"
4546 msgstr ""
4547
4548 #: rhodecode/templates/admin/repos/repo_edit_settings.html:22
4556 4549 msgid ""
4557 4550 "In case this repository is renamed or moved into another group the repository url changes.\n"
4558 4551 " Using above url guarantees that this repository will always be accessible under such url.\n"
4559 4552 " Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service."
4560 4553 msgstr ""
4561 4554
4562 #: rhodecode/templates/admin/repos/repo_edit_settings.html:27
4555 #: rhodecode/templates/admin/repos/repo_edit_settings.html:30
4563 4556 msgid "Remote uri"
4564 4557 msgstr ""
4565 4558
4566 #: rhodecode/templates/admin/repos/repo_edit_settings.html:33
4567 #: rhodecode/templates/base/perms_summary.html:80
4568 #: rhodecode/templates/base/perms_summary.html:142
4569 #: rhodecode/templates/base/perms_summary.html:144
4570 #: rhodecode/templates/debug_style/form-elements.html:45
4571 msgid "edit"
4572 msgstr ""
4573
4574 4559 #: rhodecode/templates/admin/repos/repo_edit_settings.html:36
4560 #: rhodecode/templates/base/perms_summary.html:79
4561 #: rhodecode/templates/base/perms_summary.html:149
4562 #: rhodecode/templates/base/perms_summary.html:151
4563 #: rhodecode/templates/debug_style/form-elements.html:45
4564 msgid "edit"
4565 msgstr ""
4566
4567 #: rhodecode/templates/admin/repos/repo_edit_settings.html:39
4575 4568 msgid "new value, leave empty to remove"
4576 4569 msgstr ""
4577 4570
4578 #: rhodecode/templates/admin/repos/repo_edit_settings.html:38
4571 #: rhodecode/templates/admin/repos/repo_edit_settings.html:41
4579 4572 msgid "cancel"
4580 4573 msgstr ""
4581 4574
4582 #: rhodecode/templates/admin/repos/repo_edit_settings.html:45
4575 #: rhodecode/templates/admin/repos/repo_edit_settings.html:48
4583 4576 msgid "http[s] url where from repository was imported, also used for doing remote pulls."
4584 4577 msgstr ""
4585 4578
4586 #: rhodecode/templates/admin/repos/repo_edit_settings.html:53
4579 #: rhodecode/templates/admin/repos/repo_edit_settings.html:56
4587 4580 #: rhodecode/templates/data_table/_dt_elements.html:158
4588 4581 #: rhodecode/templates/forks/fork.html:58
4589 4582 msgid "Repository group"
4590 4583 msgstr ""
4591 4584
4592 #: rhodecode/templates/admin/repos/repo_edit_settings.html:60
4585 #: rhodecode/templates/admin/repos/repo_edit_settings.html:63
4593 4586 msgid "Optional select a group to put this repository into."
4594 4587 msgstr ""
4595 4588
4596 #: rhodecode/templates/admin/repos/repo_edit_settings.html:69
4589 #: rhodecode/templates/admin/repos/repo_edit_settings.html:72
4597 4590 #: rhodecode/templates/forks/fork.html:75
4598 4591 msgid "Default commit for files page, downloads, whoosh and readme"
4599 4592 msgstr ""
4600 4593
4601 #: rhodecode/templates/admin/repos/repo_edit_settings.html:78
4594 #: rhodecode/templates/admin/repos/repo_edit_settings.html:90
4602 4595 msgid "Change owner of this repository."
4603 4596 msgstr ""
4604 4597
4605 #: rhodecode/templates/admin/repos/repo_edit_settings.html:93
4606 #: rhodecode/templates/data_table/_dt_elements.html:58
4607 msgid "Private repository"
4608 msgstr ""
4609
4610 #: rhodecode/templates/admin/repos/repo_edit_settings.html:102
4611 msgid "Enable statistics"
4612 msgstr ""
4613
4614 4598 #: rhodecode/templates/admin/repos/repo_edit_settings.html:106
4615 msgid "Enable statistics window on summary page."
4616 msgstr ""
4617
4618 #: rhodecode/templates/admin/repos/repo_edit_settings.html:111
4619 msgid "Enable downloads"
4599 #: rhodecode/templates/data_table/_dt_elements.html:58
4600 msgid "Private repository"
4620 4601 msgstr ""
4621 4602
4622 4603 #: rhodecode/templates/admin/repos/repo_edit_settings.html:115
4623 msgid "Enable download menu on summary page."
4624 msgstr ""
4625
4626 #: rhodecode/templates/admin/repos/repo_edit_settings.html:120
4627 msgid "Enable automatic locking"
4604 msgid "Enable statistics"
4605 msgstr ""
4606
4607 #: rhodecode/templates/admin/repos/repo_edit_settings.html:119
4608 msgid "Enable statistics window on summary page."
4628 4609 msgstr ""
4629 4610
4630 4611 #: rhodecode/templates/admin/repos/repo_edit_settings.html:124
4612 msgid "Enable downloads"
4613 msgstr ""
4614
4615 #: rhodecode/templates/admin/repos/repo_edit_settings.html:128
4616 msgid "Enable download menu on summary page."
4617 msgstr ""
4618
4619 #: rhodecode/templates/admin/repos/repo_edit_settings.html:133
4620 msgid "Enable automatic locking"
4621 msgstr ""
4622
4623 #: rhodecode/templates/admin/repos/repo_edit_settings.html:137
4631 4624 msgid "Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user"
4632 4625 msgstr ""
4633 4626
4634 4627 #: rhodecode/templates/admin/repos/repo_edit_statistics.html:3
4635 4628 msgid "Repository statistics"
4636 4629 msgstr ""
4637 4630
4638 4631 #: rhodecode/templates/admin/repos/repo_edit_statistics.html:11
4639 4632 msgid "Processed commits"
4640 4633 msgstr ""
4641 4634
4642 4635 #: rhodecode/templates/admin/repos/repo_edit_statistics.html:12
4643 4636 msgid "Processed progress"
4644 4637 msgstr ""
4645 4638
4646 4639 #: rhodecode/templates/admin/repos/repo_edit_statistics.html:15
4647 4640 msgid "Reset statistics"
4648 4641 msgstr ""
4649 4642
4650 4643 #: rhodecode/templates/admin/repos/repo_edit_statistics.html:15
4651 4644 msgid "Confirm to remove current statistics"
4652 4645 msgstr ""
4653 4646
4654 4647 #: rhodecode/templates/admin/repos/repo_edit_vcs.html:14
4655 4648 msgid "Select to inherit global vcs settings."
4656 4649 msgstr ""
4657 4650
4658 4651 #: rhodecode/templates/admin/repos/repo_edit_vcs.html:45
4659 4652 #: rhodecode/templates/admin/settings/settings_global.html:109
4660 4653 #: rhodecode/templates/admin/settings/settings_labs.html:45
4661 4654 #: rhodecode/templates/admin/settings/settings_vcs.html:13
4662 4655 #: rhodecode/templates/admin/settings/settings_visual.html:219
4663 4656 msgid "Save settings"
4664 4657 msgstr ""
4665 4658
4666 4659 #: rhodecode/templates/admin/repos/repos.html:5
4667 4660 msgid "Repositories administration"
4668 4661 msgstr ""
4669 4662
4670 4663 #: rhodecode/templates/admin/repos/repos.html:13
4671 4664 msgid "repositories"
4672 4665 msgstr ""
4673 4666
4674 4667 #: rhodecode/templates/admin/repos/repos.html:67
4675 4668 msgid "State"
4676 4669 msgstr ""
4677 4670
4678 4671 #: rhodecode/templates/admin/settings/settings.html:5
4679 4672 msgid "Settings administration"
4680 4673 msgstr ""
4681 4674
4682 4675 #: rhodecode/templates/admin/settings/settings_email.html:3
4683 4676 msgid "Email Configuration"
4684 4677 msgstr ""
4685 4678
4686 4679 #: rhodecode/templates/admin/settings/settings_email.html:8
4687 4680 msgid "Email prefix"
4688 4681 msgstr ""
4689 4682
4690 4683 #: rhodecode/templates/admin/settings/settings_email.html:9
4691 4684 msgid "RhodeCode email from"
4692 4685 msgstr ""
4693 4686
4694 4687 #: rhodecode/templates/admin/settings/settings_email.html:10
4695 4688 msgid "Error email from"
4696 4689 msgstr ""
4697 4690
4698 4691 #: rhodecode/templates/admin/settings/settings_email.html:11
4699 4692 msgid "Error email recipients"
4700 4693 msgstr ""
4701 4694
4702 4695 #: rhodecode/templates/admin/settings/settings_email.html:13
4703 4696 msgid "SMTP server"
4704 4697 msgstr ""
4705 4698
4706 4699 #: rhodecode/templates/admin/settings/settings_email.html:14
4707 4700 msgid "SMTP username"
4708 4701 msgstr ""
4709 4702
4710 4703 #: rhodecode/templates/admin/settings/settings_email.html:15
4711 4704 msgid "SMTP password"
4712 4705 msgstr ""
4713 4706
4714 4707 #: rhodecode/templates/admin/settings/settings_email.html:16
4715 4708 msgid "SMTP port"
4716 4709 msgstr ""
4717 4710
4718 4711 #: rhodecode/templates/admin/settings/settings_email.html:18
4719 4712 msgid "SMTP use TLS"
4720 4713 msgstr ""
4721 4714
4722 4715 #: rhodecode/templates/admin/settings/settings_email.html:19
4723 4716 msgid "SMTP use SSL"
4724 4717 msgstr ""
4725 4718
4726 4719 #: rhodecode/templates/admin/settings/settings_email.html:20
4727 4720 msgid "SMTP auth"
4728 4721 msgstr ""
4729 4722
4730 4723 #: rhodecode/templates/admin/settings/settings_email.html:34
4731 4724 msgid "Test Email"
4732 4725 msgstr ""
4733 4726
4734 4727 #: rhodecode/templates/admin/settings/settings_email.html:40
4735 4728 msgid "enter valid email"
4736 4729 msgstr ""
4737 4730
4738 4731 #: rhodecode/templates/admin/settings/settings_email.html:44
4739 4732 msgid "Send an auto-generated email from this server to above email..."
4740 4733 msgstr ""
4741 4734
4742 4735 #: rhodecode/templates/admin/settings/settings_email.html:48
4743 4736 msgid "Send"
4744 4737 msgstr ""
4745 4738
4746 4739 #: rhodecode/templates/admin/settings/settings_global.html:5
4747 4740 msgid "Branding"
4748 4741 msgstr ""
4749 4742
4750 #: rhodecode/templates/admin/settings/settings_global.html:9
4751 #: rhodecode/templates/email_templates/pull_request_review.mako:28
4752 #: rhodecode/templates/email_templates/pull_request_review.mako:65
4753 #: rhodecode/templates/pullrequests/pullrequest.html:38
4754 #: rhodecode/templates/pullrequests/pullrequests.html:104
4755 msgid "Title"
4756 msgstr ""
4757
4758 4743 #: rhodecode/templates/admin/settings/settings_global.html:16
4759 4744 msgid "Set a custom title for your RhodeCode instance (limited to 40 characters)."
4760 4745 msgstr ""
4761 4746
4762 4747 #: rhodecode/templates/admin/settings/settings_global.html:20
4763 4748 msgid "HTTP[S] authentication realm"
4764 4749 msgstr ""
4765 4750
4766 4751 #: rhodecode/templates/admin/settings/settings_global.html:27
4767 4752 msgid "Set a custom text that is shown as authentication message to clients trying to connect."
4768 4753 msgstr ""
4769 4754
4770 4755 #: rhodecode/templates/admin/settings/settings_global.html:35
4771 4756 msgid "Registration Captcha"
4772 4757 msgstr ""
4773 4758
4774 4759 #: rhodecode/templates/admin/settings/settings_global.html:39
4775 4760 msgid "Google ReCaptcha public key"
4776 4761 msgstr ""
4777 4762
4778 4763 #: rhodecode/templates/admin/settings/settings_global.html:46
4779 4764 msgid "Public key for reCaptcha system."
4780 4765 msgstr ""
4781 4766
4782 4767 #: rhodecode/templates/admin/settings/settings_global.html:51
4783 4768 msgid "Google ReCaptcha private key"
4784 4769 msgstr ""
4785 4770
4786 4771 #: rhodecode/templates/admin/settings/settings_global.html:58
4787 4772 msgid "Private key for reCaptcha system. Setting this value will enable captcha on registration"
4788 4773 msgstr ""
4789 4774
4790 4775 #: rhodecode/templates/admin/settings/settings_global.html:66
4791 4776 msgid "Custom Header Code"
4792 4777 msgstr ""
4793 4778
4794 4779 #: rhodecode/templates/admin/settings/settings_global.html:71
4795 4780 #: rhodecode/templates/admin/settings/settings_global.html:93
4796 4781 #: rhodecode/templates/debug_style/form-elements-small.html:59
4797 4782 #: rhodecode/templates/debug_style/form-elements.html:57
4798 4783 #: rhodecode/templates/debug_style/form-elements.html:82
4799 4784 #: rhodecode/templates/debug_style/form-elements.html:225
4800 4785 #: rhodecode/templates/debug_style/form-elements.html:381
4801 4786 #: rhodecode/templates/debug_style/form-elements.html:407
4802 4787 #: rhodecode/templates/debug_style/form-elements.html:515
4803 4788 #: rhodecode/templates/debug_style/form-elements.html:519
4804 4789 #: rhodecode/templates/debug_style/form-elements.html:537
4805 4790 #: rhodecode/templates/debug_style/form-elements.html:587
4806 4791 #: rhodecode/templates/debug_style/form-inline.html:38
4807 4792 #: rhodecode/templates/debug_style/form-inline.html:139
4808 4793 #: rhodecode/templates/debug_style/form-inline.html:147
4809 4794 #: rhodecode/templates/debug_style/form-vertical.html:60
4810 4795 #: rhodecode/templates/debug_style/forms.html:37
4811 4796 #: rhodecode/templates/debug_style/forms.html:60
4812 4797 #: rhodecode/templates/debug_style/forms.html:78
4813 4798 #: rhodecode/templates/debug_style/forms.html:96
4814 4799 #: rhodecode/templates/debug_style/layout-form-sidebar.html:44
4815 4800 msgid "Templates..."
4816 4801 msgstr ""
4817 4802
4818 4803 #: rhodecode/templates/admin/settings/settings_global.html:74
4819 4804 #: rhodecode/templates/admin/settings/settings_global.html:96
4820 4805 #: rhodecode/templates/debug_style/form-elements-small.html:62
4821 4806 #: rhodecode/templates/debug_style/form-elements.html:60
4822 4807 #: rhodecode/templates/debug_style/form-elements.html:85
4823 4808 #: rhodecode/templates/debug_style/form-elements.html:228
4824 4809 #: rhodecode/templates/debug_style/form-elements.html:384
4825 4810 #: rhodecode/templates/debug_style/form-elements.html:410
4826 4811 #: rhodecode/templates/debug_style/form-elements.html:518
4827 4812 #: rhodecode/templates/debug_style/form-elements.html:522
4828 4813 #: rhodecode/templates/debug_style/form-elements.html:540
4829 4814 #: rhodecode/templates/debug_style/form-elements.html:590
4830 4815 #: rhodecode/templates/debug_style/form-inline.html:41
4831 4816 #: rhodecode/templates/debug_style/form-inline.html:142
4832 4817 #: rhodecode/templates/debug_style/form-inline.html:150
4833 4818 #: rhodecode/templates/debug_style/form-vertical.html:63
4834 4819 #: rhodecode/templates/debug_style/forms.html:40
4835 4820 #: rhodecode/templates/debug_style/forms.html:63
4836 4821 #: rhodecode/templates/debug_style/forms.html:81
4837 4822 #: rhodecode/templates/debug_style/forms.html:99
4838 4823 #: rhodecode/templates/debug_style/layout-form-sidebar.html:47
4839 4824 msgid "Server Announcement"
4840 4825 msgstr ""
4841 4826
4842 4827 #: rhodecode/templates/admin/settings/settings_global.html:80
4843 4828 msgid "Custom js/css code added at the end of the <header> tag."
4844 4829 msgstr ""
4845 4830
4846 4831 #: rhodecode/templates/admin/settings/settings_global.html:81
4847 4832 #: rhodecode/templates/admin/settings/settings_global.html:103
4848 4833 msgid "Use <script> or <css> tags to define custom styling or scripting"
4849 4834 msgstr ""
4850 4835
4851 4836 #: rhodecode/templates/admin/settings/settings_global.html:88
4852 4837 msgid "Custom Footer Code"
4853 4838 msgstr ""
4854 4839
4855 4840 #: rhodecode/templates/admin/settings/settings_global.html:102
4856 4841 msgid "Custom js/css code added at the end of the <body> tag."
4857 4842 msgstr ""
4858 4843
4859 4844 #: rhodecode/templates/admin/settings/settings_hooks.html:3
4860 4845 msgid "Built in Mercurial hooks - read only"
4861 4846 msgstr ""
4862 4847
4863 4848 #: rhodecode/templates/admin/settings/settings_hooks.html:19
4864 4849 msgid "Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications."
4865 4850 msgstr ""
4866 4851
4867 4852 #: rhodecode/templates/admin/settings/settings_hooks.html:27
4868 4853 msgid "Custom hooks"
4869 4854 msgstr ""
4870 4855
4871 4856 #: rhodecode/templates/admin/settings/settings_labs.html:3
4872 4857 msgid "Labs Settings"
4873 4858 msgstr ""
4874 4859
4875 4860 #: rhodecode/templates/admin/settings/settings_mapping.html:5
4876 4861 msgid "Import New Groups or Repositories"
4877 4862 msgstr ""
4878 4863
4879 4864 #: rhodecode/templates/admin/settings/settings_mapping.html:10
4880 4865 msgid "Destroy old data"
4881 4866 msgstr ""
4882 4867
4883 4868 #: rhodecode/templates/admin/settings/settings_mapping.html:12
4884 4869 msgid "In case a repository or a group was deleted from the filesystem and it still exists in the database, check this option to remove obsolete data from the database."
4885 4870 msgstr ""
4886 4871
4887 4872 #: rhodecode/templates/admin/settings/settings_mapping.html:16
4888 4873 msgid "Invalidate cache for all repositories"
4889 4874 msgstr ""
4890 4875
4891 4876 #: rhodecode/templates/admin/settings/settings_mapping.html:18
4892 4877 msgid "Each cache data for repositories will be cleaned with this option selected. Use this to reload data and clear cache keys."
4893 4878 msgstr ""
4894 4879
4895 4880 #: rhodecode/templates/admin/settings/settings_mapping.html:21
4896 4881 msgid "Rescan Filesystem"
4897 4882 msgstr ""
4898 4883
4899 4884 #: rhodecode/templates/admin/settings/settings_open_source.html:3
4900 4885 msgid "Licenses of Third Party Packages"
4901 4886 msgstr ""
4902 4887
4903 4888 #: rhodecode/templates/admin/settings/settings_search.html:3
4904 4889 msgid "RhodeCode Full Text Search"
4905 4890 msgstr ""
4906 4891
4907 4892 #: rhodecode/templates/admin/settings/settings_supervisor_tail.html:1
4908 4893 #, python-format
4909 4894 msgid "Last %(size)s bytes of process logs, use ?offset=[num] GET param to set custom size"
4910 4895 msgstr ""
4911 4896
4912 4897 #: rhodecode/templates/admin/settings/settings_system.html:4
4898 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:4
4913 4899 msgid "RhodeCode Enterprise version"
4914 4900 msgstr ""
4915 4901
4916 4902 #: rhodecode/templates/admin/settings/settings_system.html:4
4917 4903 msgid "check for updates"
4918 4904 msgstr ""
4919 4905
4920 4906 #: rhodecode/templates/admin/settings/settings_system.html:5
4907 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:5
4921 4908 msgid "Upgrade info endpoint"
4922 4909 msgstr ""
4923 4910
4924 4911 #: rhodecode/templates/admin/settings/settings_system.html:5
4925 4912 msgid "Note: please make sure this server can access this url"
4926 4913 msgstr ""
4927 4914
4928 4915 #: rhodecode/templates/admin/settings/settings_system.html:6
4916 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:6
4929 4917 msgid "Configuration INI file"
4930 4918 msgstr ""
4931 4919
4932 4920 #: rhodecode/templates/admin/settings/settings_system.html:8
4921 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:8
4933 4922 msgid "RhodeCode Enterprise Server IP"
4934 4923 msgstr ""
4935 4924
4936 4925 #: rhodecode/templates/admin/settings/settings_system.html:9
4926 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:9
4937 4927 msgid "RhodeCode Enterprise Server ID"
4938 4928 msgstr ""
4939 4929
4940 4930 #: rhodecode/templates/admin/settings/settings_system.html:10
4931 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:10
4941 4932 msgid "Platform"
4942 4933 msgstr ""
4943 4934
4944 4935 #: rhodecode/templates/admin/settings/settings_system.html:11
4936 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:11
4945 4937 msgid "Uptime"
4946 4938 msgstr ""
4947 4939
4948 4940 #: rhodecode/templates/admin/settings/settings_system.html:12
4941 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:12
4949 4942 msgid "Storage location"
4950 4943 msgstr ""
4951 4944
4952 4945 #: rhodecode/templates/admin/settings/settings_system.html:13
4946 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:13
4953 4947 msgid "Storage disk space"
4954 4948 msgstr ""
4955 4949
4956 4950 #: rhodecode/templates/admin/settings/settings_system.html:15
4951 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:15
4957 4952 msgid "Search index storage"
4958 4953 msgstr ""
4959 4954
4960 4955 #: rhodecode/templates/admin/settings/settings_system.html:16
4956 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:16
4961 4957 msgid "Search index size"
4962 4958 msgstr ""
4963 4959
4964 4960 #: rhodecode/templates/admin/settings/settings_system.html:18
4961 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:18
4965 4962 msgid "Gist storage"
4966 4963 msgstr ""
4967 4964
4968 4965 #: rhodecode/templates/admin/settings/settings_system.html:19
4966 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:19
4969 4967 msgid "Gist storage size"
4970 4968 msgstr ""
4971 4969
4972 4970 #: rhodecode/templates/admin/settings/settings_system.html:21
4971 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:21
4973 4972 msgid "Archive cache"
4974 4973 msgstr ""
4975 4974
4976 4975 #: rhodecode/templates/admin/settings/settings_system.html:21
4977 4976 msgid "Enable this by setting archive_cache_dir=/path/to/cache option in the .ini file"
4978 4977 msgstr ""
4979 4978
4980 4979 #: rhodecode/templates/admin/settings/settings_system.html:22
4980 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:22
4981 4981 msgid "Archive cache size"
4982 4982 msgstr ""
4983 4983
4984 4984 #: rhodecode/templates/admin/settings/settings_system.html:24
4985 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:24
4985 4986 msgid "System memory"
4986 4987 msgstr ""
4987 4988
4988 4989 #: rhodecode/templates/admin/settings/settings_system.html:25
4990 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:25
4989 4991 msgid "CPU"
4990 4992 msgstr ""
4991 4993
4992 4994 #: rhodecode/templates/admin/settings/settings_system.html:26
4995 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:26
4993 4996 msgid "Load"
4994 4997 msgstr ""
4995 4998
4996 4999 #: rhodecode/templates/admin/settings/settings_system.html:29
5000 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:29
4997 5001 msgid "Python version"
4998 5002 msgstr ""
4999 5003
5000 5004 #: rhodecode/templates/admin/settings/settings_system.html:30
5005 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:30
5001 5006 msgid "Python path"
5002 5007 msgstr ""
5003 5008
5004 5009 #: rhodecode/templates/admin/settings/settings_system.html:31
5010 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:31
5005 5011 msgid "GIT version"
5006 5012 msgstr ""
5007 5013
5008 5014 #: rhodecode/templates/admin/settings/settings_system.html:32
5015 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:32
5009 5016 msgid "HG version"
5010 5017 msgstr ""
5011 5018
5012 5019 #: rhodecode/templates/admin/settings/settings_system.html:33
5020 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:33
5013 5021 msgid "SVN version"
5014 5022 msgstr ""
5015 5023
5016 5024 #: rhodecode/templates/admin/settings/settings_system.html:34
5025 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:34
5017 5026 msgid "Database"
5018 5027 msgstr ""
5019 5028
5020 5029 #: rhodecode/templates/admin/settings/settings_system.html:35
5030 #: rhodecode/templates/admin/settings/settings_system_snapshot.html:35
5021 5031 msgid "Database version"
5022 5032 msgstr ""
5023 5033
5024 5034 #: rhodecode/templates/admin/settings/settings_system.html:41
5025 5035 msgid "Checking for updates..."
5026 5036 msgstr ""
5027 5037
5028 #: rhodecode/templates/admin/settings/settings_system.html:61
5038 #: rhodecode/templates/admin/settings/settings_system.html:49
5039 msgid "create snapshot"
5040 msgstr ""
5041
5042 #: rhodecode/templates/admin/settings/settings_system.html:64
5029 5043 msgid "Python Packages"
5030 5044 msgstr ""
5031 5045
5032 5046 #: rhodecode/templates/admin/settings/settings_visual.html:5
5033 5047 #: rhodecode/templates/base/vcs_settings.html:10
5034 5048 msgid "General"
5035 5049 msgstr ""
5036 5050
5037 5051 #: rhodecode/templates/admin/settings/settings_visual.html:10
5038 5052 msgid "Use repository extra fields"
5039 5053 msgstr ""
5040 5054
5041 5055 #: rhodecode/templates/admin/settings/settings_visual.html:12
5042 5056 msgid "Allows storing additional customized fields per repository."
5043 5057 msgstr ""
5044 5058
5045 5059 #: rhodecode/templates/admin/settings/settings_visual.html:17
5046 5060 msgid "Show RhodeCode version"
5047 5061 msgstr ""
5048 5062
5049 5063 #: rhodecode/templates/admin/settings/settings_visual.html:19
5050 5064 msgid "Shows or hides a version number of RhodeCode displayed in the footer."
5051 5065 msgstr ""
5052 5066
5053 5067 #: rhodecode/templates/admin/settings/settings_visual.html:26
5054 5068 msgid "Gravatars"
5055 5069 msgstr ""
5056 5070
5057 5071 #: rhodecode/templates/admin/settings/settings_visual.html:31
5058 5072 msgid "Use Gravatars based avatars"
5059 5073 msgstr ""
5060 5074
5061 5075 #: rhodecode/templates/admin/settings/settings_visual.html:33
5062 5076 msgid "Use gravatar.com as avatar system for RhodeCode accounts. If this is disabled avatars are generated based on initials and email."
5063 5077 msgstr ""
5064 5078
5065 5079 #: rhodecode/templates/admin/settings/settings_visual.html:36
5066 5080 msgid "Gravatar URL"
5067 5081 msgstr ""
5068 5082
5069 5083 #: rhodecode/templates/admin/settings/settings_visual.html:44
5070 5084 msgid ""
5071 5085 "Gravatar url allows you to use other avatar server application.\n"
5072 5086 " Following variables of the URL will be replaced accordingly.\n"
5073 5087 " {scheme} 'http' or 'https' sent from running RhodeCode server,\n"
5074 5088 " {email} user email,\n"
5075 5089 " {md5email} md5 hash of the user email (like at gravatar.com),\n"
5076 5090 " {size} size of the image that is expected from the server application,\n"
5077 5091 " {netloc} network location/server host of running RhodeCode server"
5078 5092 msgstr ""
5079 5093
5080 5094 #: rhodecode/templates/admin/settings/settings_visual.html:59
5081 5095 msgid "Meta-Tagging"
5082 5096 msgstr ""
5083 5097
5084 5098 #: rhodecode/templates/admin/settings/settings_visual.html:64
5085 5099 msgid "Stylify recognised meta tags"
5086 5100 msgstr ""
5087 5101
5088 5102 #: rhodecode/templates/admin/settings/settings_visual.html:66
5089 5103 msgid "Parses meta tags from repository description field and turns them into colored tags."
5090 5104 msgstr ""
5091 5105
5092 5106 #: rhodecode/templates/admin/settings/settings_visual.html:86
5093 5107 msgid "Dashboard Items"
5094 5108 msgstr ""
5095 5109
5096 5110 #: rhodecode/templates/admin/settings/settings_visual.html:90
5097 5111 msgid "Main page dashboard items"
5098 5112 msgstr ""
5099 5113
5100 5114 #: rhodecode/templates/admin/settings/settings_visual.html:96
5101 5115 msgid "Number of items displayed in the main page dashboard before pagination is shown."
5102 5116 msgstr ""
5103 5117
5104 5118 #: rhodecode/templates/admin/settings/settings_visual.html:100
5105 5119 msgid "Admin pages items"
5106 5120 msgstr ""
5107 5121
5108 5122 #: rhodecode/templates/admin/settings/settings_visual.html:106
5109 5123 msgid "Number of items displayed in the admin pages grids before pagination is shown."
5110 5124 msgstr ""
5111 5125
5112 5126 #: rhodecode/templates/admin/settings/settings_visual.html:115
5113 5127 msgid "Commit ID Style"
5114 5128 msgstr ""
5115 5129
5116 5130 #: rhodecode/templates/admin/settings/settings_visual.html:119
5117 5131 msgid "Commit sha length"
5118 5132 msgstr ""
5119 5133
5120 5134 #: rhodecode/templates/admin/settings/settings_visual.html:126
5121 5135 msgid ""
5122 5136 "Number of chars to show in commit sha displayed in web interface.\n"
5123 5137 " By default it's shown as r123:9043a6a4c226 this value defines the\n"
5124 5138 " length of the sha after the `r123:` part."
5125 5139 msgstr ""
5126 5140
5127 5141 #: rhodecode/templates/admin/settings/settings_visual.html:134
5128 5142 msgid "Show commit ID numeric reference"
5129 5143 msgstr ""
5130 5144
5131 5145 #: rhodecode/templates/admin/settings/settings_visual.html:134
5132 5146 msgid "Commit show revision number"
5133 5147 msgstr ""
5134 5148
5135 5149 #: rhodecode/templates/admin/settings/settings_visual.html:136
5136 5150 msgid ""
5137 5151 "Show revision number in commit sha displayed in web interface.\n"
5138 5152 " By default it's shown as r123:9043a6a4c226 this value defines the\n"
5139 5153 " if the `r123:` part is shown."
5140 5154 msgstr ""
5141 5155
5142 5156 #: rhodecode/templates/admin/settings/settings_visual.html:145
5143 5157 #: rhodecode/templates/debug_style/index.html:62
5144 5158 msgid "Icons"
5145 5159 msgstr ""
5146 5160
5147 5161 #: rhodecode/templates/admin/settings/settings_visual.html:150
5148 5162 msgid "Show public repo icon on repositories"
5149 5163 msgstr ""
5150 5164
5151 5165 #: rhodecode/templates/admin/settings/settings_visual.html:156
5152 5166 msgid "Show private repo icon on repositories"
5153 5167 msgstr ""
5154 5168
5155 5169 #: rhodecode/templates/admin/settings/settings_visual.html:158
5156 5170 msgid "Show public/private icons next to repositories names."
5157 5171 msgstr ""
5158 5172
5159 5173 #: rhodecode/templates/admin/settings/settings_visual.html:165
5160 5174 msgid "Markup Renderer"
5161 5175 msgstr ""
5162 5176
5163 5177 #: rhodecode/templates/admin/settings/settings_visual.html:172
5164 5178 msgid "Default renderer used to render comments, pull request descriptions and other description elements. After change old entries will still work correctly."
5165 5179 msgstr ""
5166 5180
5167 5181 #: rhodecode/templates/admin/settings/settings_visual.html:179
5168 5182 msgid "Clone URL"
5169 5183 msgstr ""
5170 5184
5171 5185 #: rhodecode/templates/admin/settings/settings_visual.html:188
5172 5186 msgid ""
5173 5187 "Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:\n"
5174 5188 " {scheme} 'http' or 'https' sent from running RhodeCode server,\n"
5175 5189 " {user} current user username,\n"
5176 5190 " {netloc} network location/server host of running RhodeCode server,\n"
5177 5191 " {repo} full repository name,\n"
5178 5192 " {repoid} ID of repository, can be used to contruct clone-by-id"
5179 5193 msgstr ""
5180 5194
5181 5195 #: rhodecode/templates/admin/settings/settings_visual.html:201
5182 5196 msgid "Custom Support Link"
5183 5197 msgstr ""
5184 5198
5185 5199 #: rhodecode/templates/admin/settings/settings_visual.html:209
5186 5200 #, python-format
5187 5201 msgid ""
5188 5202 "Custom url for the support link located at the bottom.\n"
5189 5203 " The default is set to %(default_url)s. In case there's a need\n"
5190 5204 " to change the support link to internal issue tracker, it should be done here.\n"
5191 5205 " "
5192 5206 msgstr ""
5193 5207
5194 5208 #: rhodecode/templates/admin/user_groups/user_group_add.html:5
5195 5209 msgid "Add user group"
5196 5210 msgstr ""
5197 5211
5198 5212 #: rhodecode/templates/admin/user_groups/user_group_add.html:13
5199 5213 #: rhodecode/templates/admin/users/user_edit_advanced.html:13
5200 #: rhodecode/templates/base/base.html:82 rhodecode/templates/base/base.html:153
5214 #: rhodecode/templates/base/base.html:82 rhodecode/templates/base/base.html:155
5201 5215 msgid "User groups"
5202 5216 msgstr ""
5203 5217
5204 5218 #: rhodecode/templates/admin/user_groups/user_group_add.html:15
5205 5219 #: rhodecode/templates/admin/user_groups/user_groups.html:28
5206 5220 msgid "Add User Group"
5207 5221 msgstr ""
5208 5222
5209 5223 #: rhodecode/templates/admin/user_groups/user_group_add.html:35
5210 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:12
5224 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:15
5211 5225 msgid "Group name"
5212 5226 msgstr ""
5213 5227
5214 5228 #: rhodecode/templates/admin/user_groups/user_group_add.html:47
5215 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:33
5229 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:46
5216 5230 msgid "Short, optional description for this user group."
5217 5231 msgstr ""
5218 5232
5219 5233 #: rhodecode/templates/admin/user_groups/user_group_edit.html:5
5220 5234 #, python-format
5221 5235 msgid "%s user group settings"
5222 5236 msgstr ""
5223 5237
5224 5238 #: rhodecode/templates/admin/user_groups/user_group_edit.html:36
5225 5239 #: rhodecode/templates/admin/users/user_edit.html:36
5226 5240 msgid "Global permissions"
5227 5241 msgstr ""
5228 5242
5229 5243 #: rhodecode/templates/admin/user_groups/user_group_edit.html:37
5230 5244 #: rhodecode/templates/admin/users/user_edit.html:37
5231 5245 msgid "Permissions summary"
5232 5246 msgstr ""
5233 5247
5234 5248 #: rhodecode/templates/admin/user_groups/user_group_edit.html:38
5235 5249 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:8
5236 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:46
5237 5250 #: rhodecode/templates/admin/user_groups/user_groups.html:60
5238 5251 #: rhodecode/templates/debug_style/form-elements.html:509
5239 5252 msgid "Members"
5240 5253 msgstr ""
5241 5254
5242 5255 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:9
5243 5256 msgid "Assigned to repositories"
5244 5257 msgstr ""
5245 5258
5246 5259 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:10
5247 5260 msgid "Assigned to repo groups"
5248 5261 msgstr ""
5249 5262
5250 5263 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:17
5251 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:3
5264 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:6
5252 5265 #, python-format
5253 5266 msgid "User Group: %s"
5254 5267 msgstr ""
5255 5268
5256 5269 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:26
5257 5270 msgid "Delete User Group"
5258 5271 msgstr ""
5259 5272
5260 5273 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:32
5261 5274 #, python-format
5262 5275 msgid "Confirm to delete user group `%(ugroup)s` with all permission assignments"
5263 5276 msgstr ""
5264 5277
5265 5278 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.html:34
5266 5279 msgid "Delete This User Group"
5267 5280 msgstr ""
5268 5281
5269 5282 #: rhodecode/templates/admin/user_groups/user_group_edit_members.html:5
5270 5283 #, python-format
5271 5284 msgid "Members of User Group: %s"
5272 5285 msgstr ""
5273 5286
5274 5287 #: rhodecode/templates/admin/user_groups/user_group_edit_members.html:27
5275 5288 msgid "No members yet"
5276 5289 msgstr ""
5277 5290
5278 5291 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:5
5279 5292 msgid "User Group Permissions"
5280 5293 msgstr ""
5281 5294
5282 5295 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:62
5283 5296 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.html:109
5284 5297 msgid "revoke"
5285 5298 msgstr ""
5286 5299
5287 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:24
5300 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:36
5288 5301 msgid "Change owner of this user group."
5289 5302 msgstr ""
5290 5303
5291 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:50
5304 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:59
5305 #: rhodecode/templates/base/base.html:257
5306 #: rhodecode/templates/base/base.html:399
5307 #: rhodecode/templates/search/search.html:64
5308 msgid "Search"
5309 msgstr ""
5310
5311 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:66
5292 5312 #: rhodecode/templates/debug_style/form-elements.html:513
5293 5313 #: rhodecode/templates/debug_style/form-elements.html:571
5294 5314 #: rhodecode/templates/debug_style/forms.html:236
5295 5315 msgid "Chosen group members"
5296 5316 msgstr ""
5297 5317
5298 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:53
5318 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:69
5299 5319 #: rhodecode/templates/debug_style/form-elements.html:525
5300 5320 #: rhodecode/templates/debug_style/form-elements.html:575
5301 5321 #: rhodecode/templates/debug_style/forms.html:240
5302 5322 msgid "Remove all elements"
5303 5323 msgstr ""
5304 5324
5305 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:63
5306 #: rhodecode/templates/debug_style/form-elements.html:535
5307 #: rhodecode/templates/debug_style/form-elements.html:585
5308 #: rhodecode/templates/debug_style/forms.html:250
5309 msgid "Available members"
5310 msgstr ""
5311
5312 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:66
5325 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:79
5326 msgid "Available users"
5327 msgstr ""
5328
5329 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:83
5313 5330 #: rhodecode/templates/debug_style/form-elements.html:543
5314 5331 #: rhodecode/templates/debug_style/form-elements.html:593
5315 5332 #: rhodecode/templates/debug_style/forms.html:258
5316 5333 msgid "Add all elements"
5317 5334 msgstr ""
5318 5335
5319 5336 #: rhodecode/templates/admin/user_groups/user_groups.html:5
5320 5337 msgid "User groups administration"
5321 5338 msgstr ""
5322 5339
5323 5340 #: rhodecode/templates/admin/user_groups/user_groups.html:13
5324 5341 msgid "user groups"
5325 5342 msgstr ""
5326 5343
5327 5344 #: rhodecode/templates/admin/users/user_add.html:5
5328 5345 msgid "Add user"
5329 5346 msgstr ""
5330 5347
5331 5348 #: rhodecode/templates/admin/users/user_add.html:13
5332 5349 #: rhodecode/templates/admin/users/user_edit.html:14
5333 5350 #: rhodecode/templates/base/base.html:81
5334 5351 msgid "Users"
5335 5352 msgstr ""
5336 5353
5337 5354 #: rhodecode/templates/admin/users/user_add.html:15
5338 5355 #: rhodecode/templates/admin/users/users.html:27
5339 5356 msgid "Add User"
5340 5357 msgstr ""
5341 5358
5342 5359 #: rhodecode/templates/admin/users/user_add.html:53
5343 5360 msgid "Password confirmation"
5344 5361 msgstr ""
5345 5362
5346 5363 #: rhodecode/templates/admin/users/user_add.html:59
5347 5364 msgid "Generate password"
5348 5365 msgstr ""
5349 5366
5350 5367 #: rhodecode/templates/admin/users/user_add.html:106
5351 5368 msgid "Password change"
5352 5369 msgstr ""
5353 5370
5354 5371 #: rhodecode/templates/admin/users/user_add.html:110
5355 5372 msgid "Force user to change his password on the next login"
5356 5373 msgstr ""
5357 5374
5358 5375 #: rhodecode/templates/admin/users/user_add.html:120
5359 5376 msgid ""
5360 5377 "Add repository group with the same name as username. \n"
5361 5378 "User will be automatically set as this group owner."
5362 5379 msgstr ""
5363 5380
5364 #: rhodecode/templates/admin/users/user_add.html:137
5381 #: rhodecode/templates/admin/users/user_add.html:136
5365 5382 msgid "generated password:"
5366 5383 msgstr ""
5367 5384
5368 5385 #: rhodecode/templates/admin/users/user_edit.html:5
5369 5386 #, python-format
5370 5387 msgid "%s user settings"
5371 5388 msgstr ""
5372 5389
5373 5390 #: rhodecode/templates/admin/users/user_edit.html:33
5374 5391 #: rhodecode/templates/admin/users/user_edit_profile.html:5
5375 5392 msgid "User Profile"
5376 5393 msgstr ""
5377 5394
5378 5395 #: rhodecode/templates/admin/users/user_edit.html:34
5379 5396 msgid "Auth tokens"
5380 5397 msgstr ""
5381 5398
5382 5399 #: rhodecode/templates/admin/users/user_edit.html:38
5383 5400 msgid "Emails"
5384 5401 msgstr ""
5385 5402
5386 5403 #: rhodecode/templates/admin/users/user_edit.html:39
5387 5404 msgid "Ip Whitelist"
5388 5405 msgstr ""
5389 5406
5390 5407 #: rhodecode/templates/admin/users/user_edit_advanced.html:6
5391 5408 #: rhodecode/templates/admin/users/user_edit_profile.html:106
5392 5409 msgid "Source of Record"
5393 5410 msgstr ""
5394 5411
5395 5412 #: rhodecode/templates/admin/users/user_edit_advanced.html:8
5396 5413 #: rhodecode/templates/admin/users/users.html:98
5397 5414 msgid "Last login"
5398 5415 msgstr ""
5399 5416
5400 5417 #: rhodecode/templates/admin/users/user_edit_advanced.html:9
5401 5418 msgid "Last activity"
5402 5419 msgstr ""
5403 5420
5404 5421 #: rhodecode/templates/admin/users/user_edit_advanced.html:15
5405 5422 msgid "Member of User groups"
5406 5423 msgstr ""
5407 5424
5408 5425 #: rhodecode/templates/admin/users/user_edit_advanced.html:16
5409 5426 msgid "Force password change"
5410 5427 msgstr ""
5411 5428
5412 5429 #: rhodecode/templates/admin/users/user_edit_advanced.html:22
5413 5430 #, python-format
5414 5431 msgid "User: %s"
5415 5432 msgstr ""
5416 5433
5417 5434 #: rhodecode/templates/admin/users/user_edit_advanced.html:31
5418 5435 msgid "Force Password Reset"
5419 5436 msgstr ""
5420 5437
5421 5438 #: rhodecode/templates/admin/users/user_edit_advanced.html:39
5422 5439 msgid "Disable forced password reset"
5423 5440 msgstr ""
5424 5441
5425 5442 #: rhodecode/templates/admin/users/user_edit_advanced.html:41
5426 5443 msgid "Enable forced password reset"
5427 5444 msgstr ""
5428 5445
5429 5446 #: rhodecode/templates/admin/users/user_edit_advanced.html:47
5430 5447 msgid "When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface"
5431 5448 msgstr ""
5432 5449
5433 5450 #: rhodecode/templates/admin/users/user_edit_advanced.html:56
5434 5451 msgid "Personal Repository Group"
5435 5452 msgstr ""
5436 5453
5437 5454 #: rhodecode/templates/admin/users/user_edit_advanced.html:62
5438 5455 msgid "Users personal repository group"
5439 5456 msgstr ""
5440 5457
5441 5458 #: rhodecode/templates/admin/users/user_edit_advanced.html:64
5442 5459 msgid "This user currently does not have a personal repository group"
5443 5460 msgstr ""
5444 5461
5445 5462 #: rhodecode/templates/admin/users/user_edit_advanced.html:68
5446 5463 msgid "Create personal repository group"
5447 5464 msgstr ""
5448 5465
5449 5466 #: rhodecode/templates/admin/users/user_edit_advanced.html:77
5450 5467 msgid "Delete User"
5451 5468 msgstr ""
5452 5469
5453 5470 #: rhodecode/templates/admin/users/user_edit_advanced.html:85
5454 5471 #, python-format
5455 5472 msgid "This user owns %s repository."
5456 5473 msgid_plural "This user owns %s repositories."
5457 5474 msgstr[0] ""
5458 5475 msgstr[1] ""
5459 5476
5460 5477 #: rhodecode/templates/admin/users/user_edit_advanced.html:89
5461 5478 msgid "Detach repositories"
5462 5479 msgstr ""
5463 5480
5464 5481 #: rhodecode/templates/admin/users/user_edit_advanced.html:94
5465 5482 #: rhodecode/templates/admin/users/user_edit_advanced.html:110
5466 5483 #: rhodecode/templates/admin/users/user_edit_advanced.html:126
5467 5484 msgid "Delete repositories"
5468 5485 msgstr ""
5469 5486
5470 5487 #: rhodecode/templates/admin/users/user_edit_advanced.html:101
5471 5488 #, python-format
5472 5489 msgid "This user owns %s repository group."
5473 5490 msgid_plural "This user owns %s repository groups."
5474 5491 msgstr[0] ""
5475 5492 msgstr[1] ""
5476 5493
5477 5494 #: rhodecode/templates/admin/users/user_edit_advanced.html:105
5478 5495 msgid "Detach repository groups"
5479 5496 msgstr ""
5480 5497
5481 5498 #: rhodecode/templates/admin/users/user_edit_advanced.html:117
5482 5499 #, python-format
5483 5500 msgid "This user owns %s user group."
5484 5501 msgid_plural "This user owns %s user groups."
5485 5502 msgstr[0] ""
5486 5503 msgstr[1] ""
5487 5504
5488 5505 #: rhodecode/templates/admin/users/user_edit_advanced.html:121
5489 5506 msgid "Detach user groups"
5490 5507 msgstr ""
5491 5508
5492 5509 #: rhodecode/templates/admin/users/user_edit_advanced.html:135
5493 5510 #: rhodecode/templates/data_table/_dt_elements.html:189
5494 5511 #, python-format
5495 5512 msgid "Confirm to delete this user: %s"
5496 5513 msgstr ""
5497 5514
5498 5515 #: rhodecode/templates/admin/users/user_edit_advanced.html:137
5499 5516 msgid "Delete this user"
5500 5517 msgstr ""
5501 5518
5502 5519 #: rhodecode/templates/admin/users/user_edit_advanced.html:147
5503 5520 #, python-format
5504 5521 msgid "When selecting the detach option, the depending objects owned by this user will be assigned to the `%s` super admin in the system. The delete option will delete the user's repositories!"
5505 5522 msgstr ""
5506 5523
5507 5524 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:3
5508 5525 msgid "Authentication Access Tokens"
5509 5526 msgstr ""
5510 5527
5511 5528 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:59
5512 5529 msgid "No additional auth tokens specified"
5513 5530 msgstr ""
5514 5531
5515 5532 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:71
5516 5533 msgid "New auth token"
5517 5534 msgstr ""
5518 5535
5519 5536 #: rhodecode/templates/admin/users/user_edit_emails.html:5
5520 5537 msgid "Additional Email Addresses"
5521 5538 msgstr ""
5522 5539
5523 5540 #: rhodecode/templates/admin/users/user_edit_ips.html:3
5524 5541 msgid "Custom IP Whitelist"
5525 5542 msgstr ""
5526 5543
5527 5544 #: rhodecode/templates/admin/users/user_edit_ips.html:19
5528 5545 #, python-format
5529 5546 msgid "Inherited from %s"
5530 5547 msgstr ""
5531 5548
5532 5549 #: rhodecode/templates/admin/users/user_edit_ips.html:63
5533 5550 msgid ""
5534 5551 "Enter comma separated list of ip addresses like 127.0.0.1,\n"
5535 5552 "or use a ip address with a mask 127.0.0.1/24, to create a network range.\n"
5536 5553 "To specify multiple address range use 127.0.0.1-127.0.0.10 syntax"
5537 5554 msgstr ""
5538 5555
5539 5556 #: rhodecode/templates/admin/users/user_edit_profile.html:17
5540 5557 #, python-format
5541 5558 msgid "This user was created from external source (%s). Editing some of the settings is limited."
5542 5559 msgstr ""
5543 5560
5544 5561 #: rhodecode/templates/admin/users/user_edit_profile.html:30
5545 5562 msgid "Change the avatar at"
5546 5563 msgstr ""
5547 5564
5548 5565 #: rhodecode/templates/admin/users/user_edit_profile.html:82
5549 5566 msgid "New Password Confirmation"
5550 5567 msgstr ""
5551 5568
5552 5569 #: rhodecode/templates/admin/users/user_edit_profile.html:98
5553 5570 msgid "Super Admin"
5554 5571 msgstr ""
5555 5572
5556 5573 #: rhodecode/templates/admin/users/user_edit_profile.html:115
5557 5574 msgid "Name in Source of Record"
5558 5575 msgstr ""
5559 5576
5560 5577 #: rhodecode/templates/admin/users/user_edit_profile.html:124
5561 5578 msgid "Language"
5562 5579 msgstr ""
5563 5580
5564 5581 #: rhodecode/templates/admin/users/user_edit_profile.html:130
5565 5582 #, python-format
5566 5583 msgid "Help translate %(rc_link)s into your language."
5567 5584 msgstr ""
5568 5585
5569 5586 #: rhodecode/templates/admin/users/users.html:5
5570 5587 msgid "Users administration"
5571 5588 msgstr ""
5572 5589
5573 5590 #: rhodecode/templates/admin/users/users.html:104
5574 5591 msgid "Authentication type"
5575 5592 msgstr ""
5576 5593
5577 5594 #: rhodecode/templates/base/base.html:45
5578 5595 #: rhodecode/templates/errors/error_document.html:51
5579 5596 msgid "Support"
5580 5597 msgstr ""
5581 5598
5582 5599 #: rhodecode/templates/base/base.html:52
5583 5600 #, python-format
5584 5601 msgid "RhodeCode instance id: %s"
5585 5602 msgstr ""
5586 5603
5587 5604 #: rhodecode/templates/base/base.html:84
5588 5605 msgid "Authentication"
5589 5606 msgstr ""
5590 5607
5591 5608 #: rhodecode/templates/base/base.html:85
5592 5609 msgid "Defaults"
5593 5610 msgstr ""
5594 5611
5595 5612 #: rhodecode/templates/base/base.html:103
5596 5613 #: rhodecode/templates/files/files_pjax.html:24
5597 5614 #: rhodecode/templates/summary/components.html:42
5598 5615 msgid "Show More"
5599 5616 msgstr ""
5600 5617
5601 #: rhodecode/templates/base/base.html:187
5618 #: rhodecode/templates/base/base.html:189
5602 5619 msgid "Fork of"
5603 5620 msgstr ""
5604 5621
5605 #: rhodecode/templates/base/base.html:204
5622 #: rhodecode/templates/base/base.html:206
5606 5623 #, python-format
5607 5624 msgid "Repository locked by %(user)s"
5608 5625 msgstr ""
5609 5626
5610 #: rhodecode/templates/base/base.html:209
5627 #: rhodecode/templates/base/base.html:211
5611 5628 msgid "Repository not locked. Pull repository to lock it."
5612 5629 msgstr ""
5613 5630
5614 #: rhodecode/templates/base/base.html:227
5631 #: rhodecode/templates/base/base.html:229
5615 5632 #: rhodecode/templates/data_table/_dt_elements.html:12
5616 5633 #: rhodecode/templates/data_table/_dt_elements.html:13
5617 5634 #: rhodecode/templates/data_table/_dt_elements.html:147
5618 5635 msgid "Summary"
5619 5636 msgstr ""
5620 5637
5621 #: rhodecode/templates/base/base.html:228
5638 #: rhodecode/templates/base/base.html:230
5622 5639 #: rhodecode/templates/data_table/_dt_elements.html:17
5623 5640 #: rhodecode/templates/data_table/_dt_elements.html:18
5624 5641 msgid "Changelog"
5625 5642 msgstr ""
5626 5643
5627 #: rhodecode/templates/base/base.html:229
5644 #: rhodecode/templates/base/base.html:231
5628 5645 #: rhodecode/templates/data_table/_dt_elements.html:22
5629 5646 #: rhodecode/templates/data_table/_dt_elements.html:23
5630 5647 #: rhodecode/templates/files/files.html:15
5631 5648 msgid "Files"
5632 5649 msgstr ""
5633 5650
5634 #: rhodecode/templates/base/base.html:231
5651 #: rhodecode/templates/base/base.html:233
5635 5652 #: rhodecode/templates/bookmarks/bookmarks.html:68
5636 5653 #: rhodecode/templates/branches/branches.html:67
5637 5654 #: rhodecode/templates/files/file_diff.html:11
5638 5655 #: rhodecode/templates/files/file_diff.html:29
5639 5656 #: rhodecode/templates/tags/tags.html:68
5640 5657 msgid "Compare"
5641 5658 msgstr ""
5642 5659
5643 #: rhodecode/templates/base/base.html:236
5660 #: rhodecode/templates/base/base.html:238
5644 5661 #, python-format
5645 5662 msgid "Show Pull Requests for %s"
5646 5663 msgstr ""
5647 5664
5648 #: rhodecode/templates/base/base.html:245
5665 #: rhodecode/templates/base/base.html:247
5649 5666 msgid "Options"
5650 5667 msgstr ""
5651 5668
5652 #: rhodecode/templates/base/base.html:252
5653 #: rhodecode/templates/forks/forks_data.html:28
5669 #: rhodecode/templates/base/base.html:254
5670 #: rhodecode/templates/forks/forks_data.html:30
5654 5671 msgid "Compare fork"
5655 5672 msgstr ""
5656 5673
5657 #: rhodecode/templates/base/base.html:255
5658 #: rhodecode/templates/base/base.html:397
5659 #: rhodecode/templates/search/search.html:64
5660 msgid "Search"
5661 msgstr ""
5662
5663 #: rhodecode/templates/base/base.html:259
5664 msgid "Unlock"
5665 msgstr ""
5666
5667 5674 #: rhodecode/templates/base/base.html:261
5675 msgid "Unlock"
5676 msgstr ""
5677
5678 #: rhodecode/templates/base/base.html:263
5668 5679 msgid "Lock"
5669 5680 msgstr ""
5670 5681
5671 #: rhodecode/templates/base/base.html:266
5682 #: rhodecode/templates/base/base.html:268
5672 5683 #: rhodecode/templates/data_table/_dt_elements.html:27
5673 5684 #: rhodecode/templates/data_table/_dt_elements.html:28
5674 #: rhodecode/templates/forks/forks_data.html:7
5685 #: rhodecode/templates/forks/forks_data.html:8
5675 5686 #: rhodecode/templates/summary/components.html:103
5676 5687 msgid "Fork"
5677 5688 msgid_plural "Forks"
5678 5689 msgstr[0] ""
5679 5690 msgstr[1] ""
5680 5691
5681 #: rhodecode/templates/base/base.html:267
5692 #: rhodecode/templates/base/base.html:269
5682 5693 msgid "Create Pull Request"
5683 5694 msgstr ""
5684 5695
5685 #: rhodecode/templates/base/base.html:289
5696 #: rhodecode/templates/base/base.html:291
5686 5697 msgid "Sign in"
5687 5698 msgstr ""
5688 5699
5689 #: rhodecode/templates/base/base.html:297
5700 #: rhodecode/templates/base/base.html:299
5690 5701 #: rhodecode/templates/debug_style/login.html:28
5691 5702 msgid "Sign in to your account"
5692 5703 msgstr ""
5693 5704
5694 #: rhodecode/templates/base/base.html:313
5705 #: rhodecode/templates/base/base.html:315
5695 5706 #: rhodecode/templates/debug_style/login.html:46
5696 5707 msgid "(Forgot password?)"
5697 5708 msgstr ""
5698 5709
5699 #: rhodecode/templates/base/base.html:322
5710 #: rhodecode/templates/base/base.html:324
5700 5711 #: rhodecode/templates/debug_style/login.html:56
5701 5712 msgid "Don't have an account ?"
5702 5713 msgstr ""
5703 5714
5704 #: rhodecode/templates/base/base.html:343
5715 #: rhodecode/templates/base/base.html:345
5705 5716 msgid "Sign Out"
5706 5717 msgstr ""
5707 5718
5708 #: rhodecode/templates/base/base.html:379
5719 #: rhodecode/templates/base/base.html:381
5709 5720 msgid "Show activity journal"
5710 5721 msgstr ""
5711 5722
5712 #: rhodecode/templates/base/base.html:380
5723 #: rhodecode/templates/base/base.html:382
5713 5724 #: rhodecode/templates/journal/journal.html:4
5714 5725 #: rhodecode/templates/journal/journal.html:14
5715 5726 msgid "Journal"
5716 5727 msgstr ""
5717 5728
5718 #: rhodecode/templates/base/base.html:385
5729 #: rhodecode/templates/base/base.html:387
5719 5730 msgid "Show Public activity journal"
5720 5731 msgstr ""
5721 5732
5722 #: rhodecode/templates/base/base.html:386
5733 #: rhodecode/templates/base/base.html:388
5723 5734 msgid "Public journal"
5724 5735 msgstr ""
5725 5736
5726 #: rhodecode/templates/base/base.html:391
5737 #: rhodecode/templates/base/base.html:393
5727 5738 msgid "Show Gists"
5728 5739 msgstr ""
5729 5740
5730 #: rhodecode/templates/base/base.html:392
5741 #: rhodecode/templates/base/base.html:394
5731 5742 msgid "Gists"
5732 5743 msgstr ""
5733 5744
5734 #: rhodecode/templates/base/base.html:396
5745 #: rhodecode/templates/base/base.html:398
5735 5746 msgid "Search in repositories you have access to"
5736 5747 msgstr ""
5737 5748
5738 #: rhodecode/templates/base/base.html:402
5749 #: rhodecode/templates/base/base.html:404
5739 5750 msgid "Admin settings"
5740 5751 msgstr ""
5741 5752
5742 #: rhodecode/templates/base/base.html:409
5753 #: rhodecode/templates/base/base.html:411
5743 5754 msgid "Delegated Admin settings"
5744 5755 msgstr ""
5745 5756
5746 #: rhodecode/templates/base/base.html:419
5747 #: rhodecode/templates/base/base.html:420
5757 #: rhodecode/templates/base/base.html:421
5758 #: rhodecode/templates/base/base.html:422
5748 5759 #: rhodecode/templates/debug_style/buttons.html:5
5749 5760 #: rhodecode/templates/debug_style/code-block.html:6
5750 5761 #: rhodecode/templates/debug_style/collapsable-content.html:5
5751 5762 #: rhodecode/templates/debug_style/form-elements-small.html:5
5752 5763 #: rhodecode/templates/debug_style/form-elements.html:5
5753 5764 #: rhodecode/templates/debug_style/form-inline.html:5
5754 5765 #: rhodecode/templates/debug_style/form-vertical.html:5
5755 5766 #: rhodecode/templates/debug_style/forms.html:5
5756 5767 #: rhodecode/templates/debug_style/icons.html:5
5757 5768 #: rhodecode/templates/debug_style/index.html:12
5758 5769 #: rhodecode/templates/debug_style/labels.html:5
5759 5770 #: rhodecode/templates/debug_style/layout-form-sidebar.html:5
5760 5771 #: rhodecode/templates/debug_style/login.html:6
5761 5772 #: rhodecode/templates/debug_style/panels.html:5
5762 5773 #: rhodecode/templates/debug_style/tables-wide.html:5
5763 5774 #: rhodecode/templates/debug_style/tables.html:5
5764 5775 #: rhodecode/templates/debug_style/typography.html:5
5765 5776 msgid "Style"
5766 5777 msgstr ""
5767 5778
5768 #: rhodecode/templates/base/base.html:474
5779 #: rhodecode/templates/base/base.html:479
5769 5780 msgid "Go to"
5770 5781 msgstr ""
5771 5782
5772 #: rhodecode/templates/base/base.html:585
5783 #: rhodecode/templates/base/base.html:590
5773 5784 msgid "Keyboard shortcuts"
5774 5785 msgstr ""
5775 5786
5776 #: rhodecode/templates/base/base.html:593
5787 #: rhodecode/templates/base/base.html:598
5777 5788 msgid "Site-wide shortcuts"
5778 5789 msgstr ""
5779 5790
5780 5791 #: rhodecode/templates/base/default_perms_box.html:14
5781 5792 msgid "Inherited Permissions"
5782 5793 msgstr ""
5783 5794
5784 5795 #: rhodecode/templates/base/default_perms_box.html:15
5785 5796 msgid "Custom Permissions"
5786 5797 msgstr ""
5787 5798
5788 5799 #: rhodecode/templates/base/default_perms_box.html:17
5789 5800 msgid "Default Global Permissions"
5790 5801 msgstr ""
5791 5802
5792 5803 #: rhodecode/templates/base/default_perms_box.html:23
5793 5804 msgid "The following options configure the default permissions each user or group will inherit. You can override these permissions for each individual user or user group using individual permissions settings."
5794 5805 msgstr ""
5795 5806
5796 5807 #: rhodecode/templates/base/default_perms_box.html:27
5797 5808 msgid "Repository Creation"
5798 5809 msgstr ""
5799 5810
5800 5811 #: rhodecode/templates/base/default_perms_box.html:32
5801 5812 msgid "Permission to create root level repositories. When disabled, users can still create repositories inside their own repository groups."
5802 5813 msgstr ""
5803 5814
5804 5815 #: rhodecode/templates/base/default_perms_box.html:37
5805 5816 msgid "Repository Creation With Group Write Access"
5806 5817 msgstr ""
5807 5818
5808 5819 #: rhodecode/templates/base/default_perms_box.html:42
5809 5820 msgid "Write permission given on a repository group will allow creating repositories inside that group."
5810 5821 msgstr ""
5811 5822
5812 5823 #: rhodecode/templates/base/default_perms_box.html:47
5813 5824 msgid "Repository Forking"
5814 5825 msgstr ""
5815 5826
5816 5827 #: rhodecode/templates/base/default_perms_box.html:52
5817 5828 msgid "Permission to create root level repository forks. When disabled, users can still fork repositories inside their own repository groups."
5818 5829 msgstr ""
5819 5830
5820 5831 #: rhodecode/templates/base/default_perms_box.html:57
5821 5832 msgid "Repository Group Creation"
5822 5833 msgstr ""
5823 5834
5824 5835 #: rhodecode/templates/base/default_perms_box.html:62
5825 5836 msgid "Permission to create root level repository groups. When disabled, repository group admins can still create repository subgroups within their repository groups."
5826 5837 msgstr ""
5827 5838
5828 5839 #: rhodecode/templates/base/default_perms_box.html:67
5829 5840 msgid "User Group Creation"
5830 5841 msgstr ""
5831 5842
5832 5843 #: rhodecode/templates/base/default_perms_box.html:72
5833 5844 msgid "Permission to allow user group creation. When disabled, user group admins can still create subgroups within their user groups."
5834 5845 msgstr ""
5835 5846
5836 5847 #: rhodecode/templates/base/default_perms_box.html:78
5837 5848 msgid "Inherit Permissions From The Default User"
5838 5849 msgstr ""
5839 5850
5840 5851 #: rhodecode/templates/base/default_perms_box.html:83
5841 5852 msgid "Inherit default permissions from the default user. Turn off this option to force explicit permissions for users, even if they are more restrictive than the default user permissions."
5842 5853 msgstr ""
5843 5854
5844 5855 #: rhodecode/templates/base/default_perms_box.html:102
5845 5856 msgid "Inherit from default settings"
5846 5857 msgstr ""
5847 5858
5848 5859 #: rhodecode/templates/base/default_perms_box.html:107
5849 5860 #, python-format
5850 5861 msgid ""
5851 5862 "Select to inherit permissions from %s permissions settings, including default IP address whitelist and inheritance of \n"
5852 5863 "permission by members of user groups."
5853 5864 msgstr ""
5854 5865
5855 5866 #: rhodecode/templates/base/issue_tracker_settings.html:22
5856 5867 msgid "Read more"
5857 5868 msgstr ""
5858 5869
5859 5870 #: rhodecode/templates/base/issue_tracker_settings.html:92
5860 5871 msgid "New Entry"
5861 5872 msgstr ""
5862 5873
5863 5874 #: rhodecode/templates/base/issue_tracker_settings.html:96
5864 5875 msgid "Confirm to remove this pattern:"
5865 5876 msgstr ""
5866 5877
5867 5878 #: rhodecode/templates/base/issue_tracker_settings.html:192
5868 5879 #: rhodecode/templates/changeset/changeset_file_comment.html:144
5869 5880 #: rhodecode/templates/changeset/changeset_file_comment.html:291
5870 5881 #: rhodecode/templates/files/files_add.html:78
5871 5882 #: rhodecode/templates/files/files_add.html:224
5872 5883 #: rhodecode/templates/files/files_edit.html:82
5873 5884 #: rhodecode/templates/files/files_edit.html:185
5874 5885 msgid "Preview"
5875 5886 msgstr ""
5876 5887
5877 5888 #: rhodecode/templates/base/issue_tracker_settings.html:193
5878 5889 msgid "Test Pattern Preview"
5879 5890 msgstr ""
5880 5891
5892 #: rhodecode/templates/base/perms_summary.html:18
5893 msgid "show"
5894 msgstr ""
5895
5881 5896 #: rhodecode/templates/base/perms_summary.html:19
5882 msgid "show"
5897 msgid "none"
5883 5898 msgstr ""
5884 5899
5885 5900 #: rhodecode/templates/base/perms_summary.html:20
5886 msgid "none"
5901 msgid "read"
5887 5902 msgstr ""
5888 5903
5889 5904 #: rhodecode/templates/base/perms_summary.html:21
5890 msgid "read"
5905 msgid "write"
5891 5906 msgstr ""
5892 5907
5893 5908 #: rhodecode/templates/base/perms_summary.html:22
5894 msgid "write"
5895 msgstr ""
5896
5897 #: rhodecode/templates/base/perms_summary.html:23
5898 5909 msgid "admin"
5899 5910 msgstr ""
5900 5911
5901 #: rhodecode/templates/base/perms_summary.html:30
5912 #: rhodecode/templates/base/perms_summary.html:29
5902 5913 msgid "No permissions defined"
5903 5914 msgstr ""
5904 5915
5905 #: rhodecode/templates/base/perms_summary.html:38
5906 #: rhodecode/templates/base/perms_summary.html:104
5916 #: rhodecode/templates/base/perms_summary.html:37
5917 #: rhodecode/templates/base/perms_summary.html:103
5907 5918 msgid "Permission"
5908 5919 msgstr ""
5909 5920
5910 #: rhodecode/templates/base/perms_summary.html:40
5911 #: rhodecode/templates/base/perms_summary.html:106
5921 #: rhodecode/templates/base/perms_summary.html:39
5922 #: rhodecode/templates/base/perms_summary.html:105
5912 5923 msgid "Edit Permission"
5913 5924 msgstr ""
5914 5925
5915 #: rhodecode/templates/base/perms_summary.html:86
5926 #: rhodecode/templates/base/perms_summary.html:85
5916 5927 msgid "Super admin"
5917 5928 msgstr ""
5918 5929
5930 #: rhodecode/templates/base/perms_summary.html:87
5931 msgid "Repository default permission"
5932 msgstr ""
5933
5919 5934 #: rhodecode/templates/base/perms_summary.html:88
5920 msgid "Repository default permission"
5935 msgid "Repository group default permission"
5921 5936 msgstr ""
5922 5937
5923 5938 #: rhodecode/templates/base/perms_summary.html:89
5924 msgid "Repository group default permission"
5925 msgstr ""
5926
5927 #: rhodecode/templates/base/perms_summary.html:90
5928 5939 msgid "User group default permission"
5929 5940 msgstr ""
5930 5941
5942 #: rhodecode/templates/base/perms_summary.html:91
5943 msgid "Create repositories"
5944 msgstr ""
5945
5931 5946 #: rhodecode/templates/base/perms_summary.html:92
5932 msgid "Create repositories"
5947 msgid "Fork repositories"
5933 5948 msgstr ""
5934 5949
5935 5950 #: rhodecode/templates/base/perms_summary.html:93
5936 msgid "Fork repositories"
5951 msgid "Create repository groups"
5937 5952 msgstr ""
5938 5953
5939 5954 #: rhodecode/templates/base/perms_summary.html:94
5940 msgid "Create repository groups"
5941 msgstr ""
5942
5943 #: rhodecode/templates/base/perms_summary.html:95
5944 5955 msgid "Create user groups"
5945 5956 msgstr ""
5946 5957
5947 #: rhodecode/templates/base/perms_summary.html:155
5958 #: rhodecode/templates/base/perms_summary.html:162
5948 5959 msgid "No permission defined"
5949 5960 msgstr ""
5950 5961
5951 #: rhodecode/templates/base/root.html:150
5962 #: rhodecode/templates/base/root.html:151
5952 5963 msgid "Please enable JavaScript to use RhodeCode Enterprise"
5953 5964 msgstr ""
5954 5965
5955 #: rhodecode/templates/base/social_buttons.html:6
5956 msgid "Sign in with"
5957 msgstr ""
5958
5959 #: rhodecode/templates/base/social_buttons.html:8
5960 msgid "Connect with"
5961 msgstr ""
5962
5963 5966 #: rhodecode/templates/base/vcs_settings.html:16
5964 5967 msgid "Require SSL for vcs operations"
5965 5968 msgstr ""
5966 5969
5967 5970 #: rhodecode/templates/base/vcs_settings.html:19
5968 5971 msgid "Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable."
5969 5972 msgstr ""
5970 5973
5971 5974 #: rhodecode/templates/base/vcs_settings.html:29
5972 5975 msgid "Main Storage Location"
5973 5976 msgstr ""
5974 5977
5975 5978 #: rhodecode/templates/base/vcs_settings.html:37
5976 5979 msgid "Click to unlock. You must restart RhodeCode in order to make this setting take effect."
5977 5980 msgstr ""
5978 5981
5979 5982 #: rhodecode/templates/base/vcs_settings.html:41
5980 5983 msgid "Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file."
5981 5984 msgstr ""
5982 5985
5983 5986 #: rhodecode/templates/base/vcs_settings.html:48
5984 5987 msgid "Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required."
5985 5988 msgstr ""
5986 5989
5987 5990 #: rhodecode/templates/base/vcs_settings.html:57
5988 5991 msgid "Internal Hooks"
5989 5992 msgstr ""
5990 5993
5991 5994 #: rhodecode/templates/base/vcs_settings.html:63
5992 5995 msgid "Show repository size after push"
5993 5996 msgstr ""
5994 5997
5995 5998 #: rhodecode/templates/base/vcs_settings.html:67
5996 5999 msgid "Trigger a hook that calculates repository size after each push."
5997 6000 msgstr ""
5998 6001
5999 6002 #: rhodecode/templates/base/vcs_settings.html:71
6000 6003 msgid "Execute pre/post push hooks"
6001 6004 msgstr ""
6002 6005
6003 6006 #: rhodecode/templates/base/vcs_settings.html:74
6004 6007 msgid "Execute Built in pre/post push hooks. This also executes rcextensions hooks."
6005 6008 msgstr ""
6006 6009
6007 6010 #: rhodecode/templates/base/vcs_settings.html:78
6008 6011 msgid "Execute pre/post pull hooks"
6009 6012 msgstr ""
6010 6013
6011 6014 #: rhodecode/templates/base/vcs_settings.html:81
6012 6015 msgid "Execute Built in pre/post pull hooks. This also executes rcextensions hooks."
6013 6016 msgstr ""
6014 6017
6015 6018 #: rhodecode/templates/base/vcs_settings.html:91
6016 6019 msgid "Mercurial Settings"
6017 6020 msgstr ""
6018 6021
6019 6022 #: rhodecode/templates/base/vcs_settings.html:96
6020 6023 msgid "Enable largefiles extension"
6021 6024 msgstr ""
6022 6025
6023 6026 #: rhodecode/templates/base/vcs_settings.html:99
6024 6027 msgid "Enable Largefiles extensions for all repositories."
6025 6028 msgstr ""
6026 6029
6027 6030 #: rhodecode/templates/base/vcs_settings.html:103
6028 6031 msgid "Set repositories as publishing"
6029 6032 msgstr ""
6030 6033
6031 6034 #: rhodecode/templates/base/vcs_settings.html:103
6032 6035 msgid "Set repository as publishing"
6033 6036 msgstr ""
6034 6037
6035 6038 #: rhodecode/templates/base/vcs_settings.html:106
6036 6039 msgid "When this is enabled all commits in the repository are seen as public commits by clients."
6037 6040 msgstr ""
6038 6041
6039 6042 #: rhodecode/templates/base/vcs_settings.html:111
6040 6043 msgid "Enable hgsubversion extension"
6041 6044 msgstr ""
6042 6045
6043 6046 #: rhodecode/templates/base/vcs_settings.html:114
6044 6047 msgid "Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type."
6045 6048 msgstr ""
6046 6049
6047 6050 #: rhodecode/templates/base/vcs_settings.html:124
6048 6051 msgid "Subversion Settings"
6049 6052 msgstr ""
6050 6053
6051 6054 #: rhodecode/templates/base/vcs_settings.html:129
6052 6055 msgid "Repository patterns"
6053 6056 msgstr ""
6054 6057
6055 6058 #: rhodecode/templates/base/vcs_settings.html:133
6056 6059 msgid "Patterns for identifying SVN branches and tags. For recursive search, use \"*\". Eg.: \"/branches/*\""
6057 6060 msgstr ""
6058 6061
6059 6062 #: rhodecode/templates/base/vcs_settings.html:196
6060 6063 msgid "Pull Request Settings"
6061 6064 msgstr ""
6062 6065
6063 6066 #: rhodecode/templates/base/vcs_settings.html:201
6064 6067 msgid "Enable server-side merge for pull requests"
6065 6068 msgstr ""
6066 6069
6067 6070 #: rhodecode/templates/base/vcs_settings.html:204
6068 6071 msgid "Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface."
6069 6072 msgstr ""
6070 6073
6071 6074 #: rhodecode/templates/base/vcs_settings.html:208
6072 6075 msgid "Invalidate and relocate inline comments during update"
6073 6076 msgstr ""
6074 6077
6075 6078 #: rhodecode/templates/base/vcs_settings.html:211
6076 6079 msgid "During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden."
6077 6080 msgstr ""
6078 6081
6079 6082 #: rhodecode/templates/bookmarks/bookmarks.html:5
6080 6083 #, python-format
6081 6084 msgid "%s Bookmarks"
6082 6085 msgstr ""
6083 6086
6084 6087 #: rhodecode/templates/bookmarks/bookmarks.html:13
6085 6088 msgid "bookmarks"
6086 6089 msgstr ""
6087 6090
6088 6091 #: rhodecode/templates/bookmarks/bookmarks.html:31
6089 6092 msgid "Compare Selected Bookmarks"
6090 6093 msgstr ""
6091 6094
6092 6095 #: rhodecode/templates/bookmarks/bookmarks_data.html:13
6093 6096 #: rhodecode/templates/changelog/changelog.html:180
6094 6097 #: rhodecode/templates/changelog/changelog_summary_data.html:53
6095 6098 #: rhodecode/templates/changeset/changeset.html:92
6096 6099 #: rhodecode/templates/files/base.html:10
6097 6100 #, python-format
6098 6101 msgid "Bookmark %s"
6099 6102 msgstr ""
6100 6103
6101 6104 #: rhodecode/templates/branches/branches.html:5
6102 6105 #, python-format
6103 6106 msgid "%s Branches"
6104 6107 msgstr ""
6105 6108
6106 6109 #: rhodecode/templates/branches/branches.html:13
6107 6110 msgid "branches"
6108 6111 msgstr ""
6109 6112
6110 6113 #: rhodecode/templates/branches/branches.html:31
6111 6114 msgid "Compare Selected Branches"
6112 6115 msgstr ""
6113 6116
6114 6117 #: rhodecode/templates/branches/branches_data.html:12
6115 6118 #: rhodecode/templates/changelog/changelog.html:172
6116 6119 #: rhodecode/templates/changelog/changelog_summary_data.html:67
6117 6120 #: rhodecode/templates/changeset/changeset.html:105
6118 6121 #: rhodecode/templates/files/base.html:23
6119 6122 #, python-format
6120 6123 msgid "Branch %s"
6121 6124 msgstr ""
6122 6125
6123 6126 #: rhodecode/templates/changelog/changelog.html:6
6124 6127 #, python-format
6125 6128 msgid "%s Changelog"
6126 6129 msgstr ""
6127 6130
6128 6131 #: rhodecode/templates/changelog/changelog.html:19
6129 6132 #, python-format
6130 6133 msgid "showing %d out of %d commit"
6131 6134 msgid_plural "showing %d out of %d commits"
6132 6135 msgstr[0] ""
6133 6136 msgstr[1] ""
6134 6137
6135 6138 #: rhodecode/templates/changelog/changelog.html:41
6136 #: rhodecode/templates/forks/forks_data.html:26
6139 #: rhodecode/templates/forks/forks_data.html:28
6137 6140 #, python-format
6138 6141 msgid "Compare fork with %s"
6139 6142 msgstr ""
6140 6143
6141 6144 #: rhodecode/templates/changelog/changelog.html:53
6142 6145 #, python-format
6143 6146 msgid "Compare fork with Parent (%s)"
6144 6147 msgstr ""
6145 6148
6146 6149 #: rhodecode/templates/changelog/changelog.html:62
6147 6150 msgid "Open new pull request"
6148 6151 msgstr ""
6149 6152
6150 6153 #: rhodecode/templates/changelog/changelog.html:68
6151 6154 #: rhodecode/templates/changelog/changelog.html:69
6152 6155 msgid "Clear selection"
6153 6156 msgstr ""
6154 6157
6155 6158 #: rhodecode/templates/changelog/changelog.html:83
6156 6159 msgid "Clear filter"
6157 6160 msgstr ""
6158 6161
6159 6162 #: rhodecode/templates/changelog/changelog.html:103
6160 6163 #: rhodecode/templates/changelog/changelog_summary_data.html:9
6161 #: rhodecode/templates/search/search_commit.html:9
6162 6164 msgid "Age"
6163 6165 msgstr ""
6164 6166
6165 6167 #: rhodecode/templates/changelog/changelog.html:105
6166 6168 #: rhodecode/templates/files/files_add.html:93
6167 6169 #: rhodecode/templates/files/files_delete.html:60
6168 6170 #: rhodecode/templates/files/files_edit.html:96
6169 6171 msgid "Commit Message"
6170 6172 msgstr ""
6171 6173
6172 6174 #: rhodecode/templates/changelog/changelog.html:108
6173 6175 #: rhodecode/templates/changelog/changelog_summary_data.html:11
6174 6176 msgid "Refs"
6175 6177 msgstr ""
6176 6178
6177 6179 #: rhodecode/templates/changelog/changelog.html:122
6178 6180 #: rhodecode/templates/changelog/changelog_summary_data.html:22
6179 6181 #, python-format
6180 6182 msgid ""
6181 6183 "Commit status: %s\n"
6182 6184 "Click to open associated pull request #%s"
6183 6185 msgstr ""
6184 6186
6185 6187 #: rhodecode/templates/changelog/changelog.html:126
6186 6188 #, python-format
6187 6189 msgid "Commit status: %s"
6188 6190 msgstr ""
6189 6191
6190 #: rhodecode/templates/changelog/changelog.html:141
6191 #: rhodecode/templates/compare/compare_cs.html:47
6192 #: rhodecode/templates/search/search_commit.html:30
6193 msgid "Expand commit message"
6194 msgstr ""
6195
6196 6192 #: rhodecode/templates/changelog/changelog.html:162
6197 6193 #: rhodecode/templates/changelog/changelog_summary_data.html:33
6198 6194 msgid "Commit has comments"
6199 6195 msgstr ""
6200 6196
6201 6197 #: rhodecode/templates/changelog/changelog.html:188
6202 6198 #: rhodecode/templates/changelog/changelog_summary_data.html:60
6203 6199 #: rhodecode/templates/changeset/changeset.html:99
6204 6200 #: rhodecode/templates/files/base.html:17
6205 6201 #: rhodecode/templates/tags/tags_data.html:12
6206 6202 #, python-format
6207 6203 msgid "Tag %s"
6208 6204 msgstr ""
6209 6205
6210 6206 #: rhodecode/templates/changelog/changelog.html:338
6211 6207 msgid "Filter changelog"
6212 6208 msgstr ""
6213 6209
6214 6210 #: rhodecode/templates/changelog/changelog.html:411
6215 6211 msgid "There are no changes yet"
6216 6212 msgstr ""
6217 6213
6218 6214 #: rhodecode/templates/changelog/changelog_details.html:4
6219 6215 #: rhodecode/templates/pullrequests/pullrequest_show.html:358
6220 6216 msgid "Removed"
6221 6217 msgstr ""
6222 6218
6223 6219 #: rhodecode/templates/changelog/changelog_details.html:5
6224 6220 msgid "Changed"
6225 6221 msgstr ""
6226 6222
6227 6223 #: rhodecode/templates/changelog/changelog_details.html:6
6228 6224 msgid "Added"
6229 6225 msgstr ""
6230 6226
6231 6227 #: rhodecode/templates/changelog/changelog_details.html:8
6232 6228 #: rhodecode/templates/changelog/changelog_details.html:9
6233 6229 #: rhodecode/templates/changelog/changelog_details.html:10
6234 6230 #, python-format
6235 6231 msgid "Affected %s files"
6236 6232 msgstr ""
6237 6233
6238 6234 #: rhodecode/templates/changelog/changelog_file_history.html:20
6239 6235 #: rhodecode/templates/changeset/changeset.html:86
6240 6236 #: rhodecode/templates/files/base.html:4
6241 6237 msgid "merge"
6242 6238 msgstr ""
6243 6239
6244 6240 #: rhodecode/templates/changelog/changelog_file_history.html:39
6245 6241 #: rhodecode/templates/changeset/diff_block.html:65
6246 6242 #: rhodecode/templates/changeset/diff_block.html:70
6247 6243 msgid "Show File"
6248 6244 msgstr ""
6249 6245
6250 6246 #: rhodecode/templates/changelog/changelog_summary_data.html:8
6251 6247 #: rhodecode/templates/search/search_commit.html:8
6252 6248 msgid "Commit message"
6253 6249 msgstr ""
6254 6250
6255 6251 #: rhodecode/templates/changelog/changelog_summary_data.html:91
6256 6252 msgid "Add or upload files directly via RhodeCode:"
6257 6253 msgstr ""
6258 6254
6259 6255 #: rhodecode/templates/changelog/changelog_summary_data.html:94
6260 6256 #: rhodecode/templates/files/files_browser.html:25
6261 6257 msgid "Add New File"
6262 6258 msgstr ""
6263 6259
6264 6260 #: rhodecode/templates/changelog/changelog_summary_data.html:102
6265 6261 msgid "Push new repo:"
6266 6262 msgstr ""
6267 6263
6268 6264 #: rhodecode/templates/changelog/changelog_summary_data.html:113
6269 6265 msgid "Existing repository?"
6270 6266 msgstr ""
6271 6267
6272 6268 #: rhodecode/templates/changeset/changeset.html:7
6273 6269 #, python-format
6274 6270 msgid "%s Commit"
6275 6271 msgstr ""
6276 6272
6277 6273 #: rhodecode/templates/changeset/changeset.html:43
6278 6274 msgid "Parent Commit"
6279 6275 msgstr ""
6280 6276
6281 6277 #: rhodecode/templates/changeset/changeset.html:43
6282 6278 msgid "Parent"
6283 6279 msgstr ""
6284 6280
6285 6281 #: rhodecode/templates/changeset/changeset.html:47
6286 6282 msgid "Child Commit"
6287 6283 msgstr ""
6288 6284
6289 6285 #: rhodecode/templates/changeset/changeset.html:47
6290 6286 msgid "Child"
6291 6287 msgstr ""
6292 6288
6293 6289 #: rhodecode/templates/changeset/changeset.html:58
6294 6290 msgid "Expand"
6295 6291 msgstr ""
6296 6292
6297 6293 #: rhodecode/templates/changeset/changeset.html:66
6298 6294 #: rhodecode/templates/changeset/changeset.html:72
6299 6295 #: rhodecode/templates/changeset/changeset_file_comment.html:36
6300 6296 #: rhodecode/templates/changeset/changeset_file_comment.html:90
6301 6297 msgid "Commit status"
6302 6298 msgstr ""
6303 6299
6304 6300 #: rhodecode/templates/changeset/changeset.html:79
6305 6301 #: rhodecode/templates/files/file_tree_detail.html:21
6306 6302 #: rhodecode/templates/files/files_detail.html:20
6307 6303 msgid "References"
6308 6304 msgstr ""
6309 6305
6310 6306 #: rhodecode/templates/changeset/changeset.html:115
6311 6307 msgid "Diffs"
6312 6308 msgstr ""
6313 6309
6314 6310 #: rhodecode/templates/changeset/changeset.html:119
6315 6311 #: rhodecode/templates/changeset/diff_block.html:85
6316 6312 msgid "Raw diff"
6317 6313 msgstr ""
6318 6314
6319 6315 #: rhodecode/templates/changeset/changeset.html:120
6320 6316 #: rhodecode/templates/changeset/diff_block.html:86
6321 6317 msgid "Raw Diff"
6322 6318 msgstr ""
6323 6319
6324 6320 #: rhodecode/templates/changeset/changeset.html:123
6325 6321 msgid "Patch diff"
6326 6322 msgstr ""
6327 6323
6328 6324 #: rhodecode/templates/changeset/changeset.html:124
6329 6325 msgid "Patch Diff"
6330 6326 msgstr ""
6331 6327
6332 6328 #: rhodecode/templates/changeset/changeset.html:127
6333 6329 #: rhodecode/templates/changeset/diff_block.html:90
6334 6330 msgid "Download diff"
6335 6331 msgstr ""
6336 6332
6337 6333 #: rhodecode/templates/changeset/changeset.html:128
6338 6334 #: rhodecode/templates/changeset/diff_block.html:91
6339 6335 msgid "Download Diff"
6340 6336 msgstr ""
6341 6337
6342 6338 #: rhodecode/templates/changeset/changeset.html:145
6343 6339 #: rhodecode/templates/changeset/changeset.html:147
6344 6340 #: rhodecode/tests/functional/test_changeset_comments.py:217
6345 6341 #, python-format
6346 6342 msgid "%d Commit comment"
6347 6343 msgid_plural "%d Commit comments"
6348 6344 msgstr[0] ""
6349 6345 msgstr[1] ""
6350 6346
6351 6347 #: rhodecode/templates/changeset/changeset.html:151
6352 6348 #: rhodecode/templates/changeset/changeset.html:153
6353 6349 #: rhodecode/templates/pullrequests/pullrequest_show.html:145
6354 6350 #: rhodecode/templates/pullrequests/pullrequest_show.html:147
6355 6351 #: rhodecode/tests/functional/test_changeset_comments.py:224
6356 6352 #, python-format
6357 6353 msgid "%d Inline Comment"
6358 6354 msgid_plural "%d Inline Comments"
6359 6355 msgstr[0] ""
6360 6356 msgstr[1] ""
6361 6357
6362 6358 #: rhodecode/templates/changeset/changeset.html:177
6363 6359 msgid "Browse files at current commit"
6364 6360 msgstr ""
6365 6361
6366 6362 #: rhodecode/templates/changeset/changeset.html:177
6367 6363 msgid "Browse files"
6368 6364 msgstr ""
6369 6365
6370 6366 #: rhodecode/templates/changeset/changeset.html:179
6371 6367 #: rhodecode/templates/changeset/changeset_range.html:59
6372 6368 #: rhodecode/templates/compare/compare_diff.html:255
6373 6369 #: rhodecode/templates/files/file_diff.html:77
6374 6370 #: rhodecode/templates/pullrequests/pullrequest_show.html:265
6375 6371 msgid "Expand All"
6376 6372 msgstr ""
6377 6373
6378 6374 #: rhodecode/templates/changeset/changeset.html:179
6379 6375 #: rhodecode/templates/changeset/changeset_range.html:59
6380 6376 #: rhodecode/templates/compare/compare_diff.html:255
6381 6377 #: rhodecode/templates/files/file_diff.html:77
6382 6378 #: rhodecode/templates/pullrequests/pullrequest_show.html:265
6383 6379 msgid "Collapse All"
6384 6380 msgstr ""
6385 6381
6386 6382 #: rhodecode/templates/changeset/changeset.html:190
6387 6383 #: rhodecode/templates/compare/compare_diff.html:263
6388 6384 #: rhodecode/templates/pullrequests/pullrequest_show.html:274
6389 6385 msgid "No files"
6390 6386 msgstr ""
6391 6387
6392 6388 #: rhodecode/templates/changeset/changeset.html:227
6393 6389 #: rhodecode/templates/files/file_diff.html:128
6394 6390 msgid "Show comments"
6395 6391 msgstr ""
6396 6392
6397 6393 #: rhodecode/templates/changeset/changeset.html:228
6398 6394 #: rhodecode/templates/files/file_diff.html:129
6399 6395 msgid "Hide comments"
6400 6396 msgstr ""
6401 6397
6402 6398 #: rhodecode/templates/changeset/changeset.html:245
6403 6399 #: rhodecode/templates/changeset/diff_block.html:25
6404 6400 #: rhodecode/templates/changeset/diff_block.html:46
6405 6401 #: rhodecode/templates/files/file_diff.html:146
6406 6402 msgid "Diff was truncated. File content available only in full diff."
6407 6403 msgstr ""
6408 6404
6409 6405 #: rhodecode/templates/changeset/changeset.html:245
6410 6406 #: rhodecode/templates/changeset/diff_block.html:7
6411 6407 #: rhodecode/templates/changeset/diff_block.html:10
6412 6408 #: rhodecode/templates/changeset/diff_block.html:25
6413 6409 #: rhodecode/templates/changeset/diff_block.html:46
6414 6410 #: rhodecode/templates/changeset/diff_block.html:59
6415 6411 #: rhodecode/templates/files/file_diff.html:146
6416 6412 msgid "Showing a big diff might take some time and resources, continue?"
6417 6413 msgstr ""
6418 6414
6419 6415 #: rhodecode/templates/changeset/changeset.html:245
6420 6416 #: rhodecode/templates/changeset/diff_block.html:7
6421 6417 #: rhodecode/templates/changeset/diff_block.html:10
6422 6418 #: rhodecode/templates/changeset/diff_block.html:25
6423 6419 #: rhodecode/templates/changeset/diff_block.html:46
6424 6420 #: rhodecode/templates/files/file_diff.html:146
6425 6421 #: rhodecode/templates/pullrequests/pullrequest_show.html:386
6426 6422 #: rhodecode/templates/pullrequests/pullrequest_show.html:392
6427 6423 msgid "Show full diff"
6428 6424 msgstr ""
6429 6425
6430 6426 #: rhodecode/templates/changeset/changeset.html:314
6431 6427 msgid "No Child Commits"
6432 6428 msgstr ""
6433 6429
6434 6430 #: rhodecode/templates/changeset/changeset.html:350
6435 6431 msgid "No Parent Commits"
6436 6432 msgstr ""
6437 6433
6438 6434 #: rhodecode/templates/changeset/changeset_file_comment.html:21
6439 6435 #, python-format
6440 6436 msgid "Vote on pull request #%s"
6441 6437 msgstr ""
6442 6438
6443 6439 #: rhodecode/templates/changeset/changeset_file_comment.html:23
6444 6440 #, python-format
6445 6441 msgid "Comment on pull request #%s"
6446 6442 msgstr ""
6447 6443
6448 6444 #: rhodecode/templates/changeset/changeset_file_comment.html:28
6449 6445 msgid "Status change on commit"
6450 6446 msgstr ""
6451 6447
6452 6448 #: rhodecode/templates/changeset/changeset_file_comment.html:30
6453 6449 msgid "Comment on commit"
6454 6450 msgstr ""
6455 6451
6456 6452 #: rhodecode/templates/changeset/changeset_file_comment.html:58
6457 6453 msgid "Previous comment"
6458 6454 msgstr ""
6459 6455
6460 6456 #: rhodecode/templates/changeset/changeset_file_comment.html:62
6461 6457 msgid "Next comment"
6462 6458 msgstr ""
6463 6459
6464 6460 #: rhodecode/templates/changeset/changeset_file_comment.html:118
6465 6461 msgid "Create a comment on line {1}."
6466 6462 msgstr ""
6467 6463
6468 6464 #: rhodecode/templates/changeset/changeset_file_comment.html:121
6469 6465 #: rhodecode/templates/changeset/changeset_file_comment.html:251
6470 6466 #, python-format
6471 6467 msgid "Comments parsed using %s syntax with %s support."
6472 6468 msgstr ""
6473 6469
6474 6470 #: rhodecode/templates/changeset/changeset_file_comment.html:123
6475 6471 #: rhodecode/templates/changeset/changeset_file_comment.html:253
6476 6472 msgid "Use @username inside this text to send notification to this RhodeCode user"
6477 6473 msgstr ""
6478 6474
6479 6475 #: rhodecode/templates/changeset/changeset_file_comment.html:133
6480 6476 #: rhodecode/templates/changeset/changeset_file_comment.html:264
6481 6477 msgid "Comment preview"
6482 6478 msgstr ""
6483 6479
6484 6480 #: rhodecode/templates/changeset/changeset_file_comment.html:146
6485 6481 #: rhodecode/templates/changeset/changeset_file_comment.html:293
6486 6482 #: rhodecode/templates/compare/compare_diff.html:57
6487 6483 msgid "Comment"
6488 6484 msgstr ""
6489 6485
6490 6486 #: rhodecode/templates/changeset/changeset_file_comment.html:154
6491 6487 msgid "You need to be logged in to comment."
6492 6488 msgstr ""
6493 6489
6494 6490 #: rhodecode/templates/changeset/changeset_file_comment.html:154
6495 6491 msgid "Login now"
6496 6492 msgstr ""
6497 6493
6498 6494 #: rhodecode/templates/changeset/changeset_file_comment.html:158
6499 6495 msgid "Hide"
6500 6496 msgstr ""
6501 6497
6502 6498 #: rhodecode/templates/changeset/changeset_file_comment.html:171
6503 6499 #, python-format
6504 6500 msgid "%d Pull Request Comment"
6505 6501 msgid_plural "%d Pull Request Comments"
6506 6502 msgstr[0] ""
6507 6503 msgstr[1] ""
6508 6504
6509 6505 #: rhodecode/templates/changeset/changeset_file_comment.html:173
6510 6506 #, python-format
6511 6507 msgid "%d Commit Comment"
6512 6508 msgid_plural "%d Commit Comments"
6513 6509 msgstr[0] ""
6514 6510 msgstr[1] ""
6515 6511
6516 6512 #: rhodecode/templates/changeset/changeset_file_comment.html:223
6517 6513 msgid "Merge Pull Request"
6518 6514 msgstr ""
6519 6515
6520 6516 #: rhodecode/templates/changeset/changeset_file_comment.html:243
6521 6517 msgid "Create a comment on this Pull Request."
6522 6518 msgstr ""
6523 6519
6524 6520 #: rhodecode/templates/changeset/changeset_file_comment.html:245
6525 6521 msgid "Create comments on this Commit range."
6526 6522 msgstr ""
6527 6523
6528 6524 #: rhodecode/templates/changeset/changeset_file_comment.html:247
6529 6525 msgid "Create a comment on this Commit."
6530 6526 msgstr ""
6531 6527
6532 6528 #: rhodecode/templates/changeset/changeset_range.html:5
6533 6529 #, python-format
6534 6530 msgid "%s Commits"
6535 6531 msgstr ""
6536 6532
6537 6533 #: rhodecode/templates/changeset/changeset_range.html:9
6538 6534 #: rhodecode/templates/changeset/changeset_range.html:20
6539 6535 #, python-format
6540 6536 msgid "(%s commit)"
6541 6537 msgid_plural "(%s commits)"
6542 6538 msgstr[0] ""
6543 6539 msgstr[1] ""
6544 6540
6545 6541 #: rhodecode/templates/changeset/changeset_range.html:16
6546 6542 msgid "Commits"
6547 6543 msgstr ""
6548 6544
6549 6545 #: rhodecode/templates/changeset/changeset_range.html:41
6550 6546 msgid "Show combined compare"
6551 6547 msgstr ""
6552 6548
6553 6549 #: rhodecode/templates/changeset/diff_block.html:7
6554 6550 msgid "The requested commit is too big and content was truncated."
6555 6551 msgstr ""
6556 6552
6557 6553 #: rhodecode/templates/changeset/diff_block.html:10
6558 6554 msgid "The requested file is too big and its content is not shown."
6559 6555 msgstr ""
6560 6556
6561 6557 #: rhodecode/templates/changeset/diff_block.html:64
6562 6558 #, python-format
6563 6559 msgid "Show file at commit: %(commit_id)s"
6564 6560 msgstr ""
6565 6561
6566 6562 #: rhodecode/templates/changeset/diff_block.html:69
6567 6563 #, python-format
6568 6564 msgid "File no longer present at commit: %(commit_id)s"
6569 6565 msgstr ""
6570 6566
6571 6567 #: rhodecode/templates/changeset/diff_block.html:75
6572 6568 msgid "Show full diff for this file"
6573 6569 msgstr ""
6574 6570
6575 6571 #: rhodecode/templates/changeset/diff_block.html:76
6576 6572 msgid "Unified Diff"
6577 6573 msgstr ""
6578 6574
6579 6575 #: rhodecode/templates/changeset/diff_block.html:80
6580 6576 msgid "Show full side-by-side diff for this file"
6581 6577 msgstr ""
6582 6578
6583 6579 #: rhodecode/templates/changeset/diff_block.html:81
6584 6580 #: rhodecode/templates/files/diff_2way.html:40
6585 6581 msgid "Side-by-side Diff"
6586 6582 msgstr ""
6587 6583
6588 6584 #: rhodecode/templates/changeset/diff_block.html:97
6589 6585 #, python-format
6590 6586 msgid "%(num)s file changed"
6591 6587 msgid_plural "%(num)s files changed"
6592 6588 msgstr[0] ""
6593 6589 msgstr[1] ""
6594 6590
6595 6591 #: rhodecode/templates/changeset/diff_block.html:99
6596 6592 #, python-format
6597 6593 msgid "%(num)s file changed: %(linesadd)s inserted, %(linesdel)s deleted"
6598 6594 msgid_plural "%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted"
6599 6595 msgstr[0] ""
6600 6596 msgstr[1] ""
6601 6597
6602 #: rhodecode/templates/compare/compare_cs.html:5
6598 #: rhodecode/templates/compare/compare_commits.html:5
6603 6599 msgid "No Commits"
6604 6600 msgstr ""
6605 6601
6606 #: rhodecode/templates/compare/compare_cs.html:9
6602 #: rhodecode/templates/compare/compare_commits.html:9
6607 6603 msgid "Common Ancestor Commit"
6608 6604 msgstr ""
6609 6605
6610 #: rhodecode/templates/compare/compare_cs.html:20
6606 #: rhodecode/templates/compare/compare_commits.html:20
6611 6607 msgid "Time"
6612 6608 msgstr ""
6613 6609
6614 6610 #: rhodecode/templates/compare/compare_diff.html:6
6615 6611 #: rhodecode/templates/compare/compare_diff.html:8
6616 6612 #, python-format
6617 6613 msgid "%s Compare"
6618 6614 msgstr ""
6619 6615
6620 6616 #: rhodecode/templates/compare/compare_diff.html:16
6621 6617 #, python-format
6622 6618 msgid "%s commit"
6623 6619 msgid_plural "%s commits"
6624 6620 msgstr[0] ""
6625 6621 msgstr[1] ""
6626 6622
6627 6623 #: rhodecode/templates/compare/compare_diff.html:37
6628 6624 #: rhodecode/templates/compare/compare_diff.html:55
6629 6625 msgid "Compare Commits"
6630 6626 msgstr ""
6631 6627
6632 6628 #: rhodecode/templates/compare/compare_diff.html:46
6633 6629 #: rhodecode/templates/files/file_diff.html:56
6634 6630 #: rhodecode/templates/pullrequests/pullrequest_show.html:85
6635 6631 msgid "Target"
6636 6632 msgstr ""
6637 6633
6638 6634 #: rhodecode/templates/compare/compare_diff.html:47
6639 6635 #: rhodecode/templates/files/file_diff.html:62
6640 6636 #: rhodecode/templates/files/files_source.html:18
6641 6637 msgid "Source"
6642 6638 msgstr ""
6643 6639
6644 6640 #: rhodecode/templates/compare/compare_diff.html:53
6645 6641 msgid "Swap"
6646 6642 msgstr ""
6647 6643
6648 6644 #: rhodecode/templates/compare/compare_diff.html:245
6649 6645 msgid "Compare commits, branches, bookmarks or tags."
6650 6646 msgstr ""
6651 6647
6652 6648 #: rhodecode/templates/data_table/_dt_elements.html:49
6653 6649 msgid "Mercurial repository"
6654 6650 msgstr ""
6655 6651
6656 6652 #: rhodecode/templates/data_table/_dt_elements.html:51
6657 6653 msgid "Git repository"
6658 6654 msgstr ""
6659 6655
6660 6656 #: rhodecode/templates/data_table/_dt_elements.html:53
6661 6657 msgid "Subversion repository"
6662 6658 msgstr ""
6663 6659
6664 6660 #: rhodecode/templates/data_table/_dt_elements.html:60
6665 6661 msgid "Public repository"
6666 6662 msgstr ""
6667 6663
6668 6664 #: rhodecode/templates/data_table/_dt_elements.html:70
6669 6665 msgid "Repository creating in progress..."
6670 6666 msgstr ""
6671 6667
6672 6668 #: rhodecode/templates/data_table/_dt_elements.html:84
6673 6669 msgid "No commits yet"
6674 6670 msgstr ""
6675 6671
6676 6672 #: rhodecode/templates/data_table/_dt_elements.html:91
6677 6673 #: rhodecode/templates/data_table/_dt_elements.html:93
6678 6674 #, python-format
6679 6675 msgid "Subscribe to %s rss feed"
6680 6676 msgstr ""
6681 6677
6682 6678 #: rhodecode/templates/data_table/_dt_elements.html:99
6683 6679 #: rhodecode/templates/data_table/_dt_elements.html:101
6684 6680 #, python-format
6685 6681 msgid "Subscribe to %s atom feed"
6686 6682 msgstr ""
6687 6683
6688 6684 #: rhodecode/templates/data_table/_dt_elements.html:127
6689 6685 msgid "Creating"
6690 6686 msgstr ""
6691 6687
6692 6688 #: rhodecode/templates/data_table/_dt_elements.html:129
6693 6689 msgid "Created"
6694 6690 msgstr ""
6695 6691
6696 6692 #: rhodecode/templates/data_table/_dt_elements.html:175
6697 6693 #, python-format
6698 6694 msgid "Confirm to delete this group: %s with %s repository"
6699 6695 msgid_plural "Confirm to delete this group: %s with %s repositories"
6700 6696 msgstr[0] ""
6701 6697 msgstr[1] ""
6702 6698
6703 6699 #: rhodecode/templates/data_table/_dt_elements.html:201
6704 6700 #, python-format
6705 6701 msgid "Confirm to delete this user group: %s"
6706 6702 msgstr ""
6707 6703
6708 6704 #: rhodecode/templates/data_table/_dt_elements.html:218
6709 6705 msgid "User group"
6710 6706 msgstr ""
6711 6707
6712 6708 #: rhodecode/templates/data_table/_dt_elements.html:262
6713 6709 #: rhodecode/templates/forks/fork.html:81
6714 6710 msgid "Private"
6715 6711 msgstr ""
6716 6712
6717 6713 #: rhodecode/templates/data_table/_dt_elements.html:287
6718 6714 #, python-format
6719 6715 msgid "Pull request #%(pr_number)s"
6720 6716 msgstr ""
6721 6717
6722 6718 #: rhodecode/templates/debug_style/buttons.html:131
6723 6719 msgid "Confirm to remove this field: Field"
6724 6720 msgstr ""
6725 6721
6726 6722 #: rhodecode/templates/debug_style/form-elements.html:107
6727 6723 msgid "Default"
6728 6724 msgstr ""
6729 6725
6726 #: rhodecode/templates/debug_style/form-elements.html:535
6727 #: rhodecode/templates/debug_style/form-elements.html:585
6728 #: rhodecode/templates/debug_style/forms.html:250
6729 msgid "Available members"
6730 msgstr ""
6731
6730 6732 #: rhodecode/templates/debug_style/forms.html:119
6731 6733 msgid "Some text..."
6732 6734 msgstr ""
6733 6735
6734 6736 #: rhodecode/templates/debug_style/forms.html:122
6735 6737 #: rhodecode/templates/debug_style/forms.html:255
6736 6738 msgid "Variable Item"
6737 6739 msgstr ""
6738 6740
6739 6741 #: rhodecode/templates/debug_style/forms.html:252
6740 6742 msgid "Some example text..."
6741 6743 msgstr ""
6742 6744
6743 6745 #: rhodecode/templates/debug_style/index.html:5
6744 6746 msgid "Debug Style"
6745 6747 msgstr ""
6746 6748
6747 6749 #: rhodecode/templates/debug_style/index.html:54
6748 6750 msgid "Index"
6749 6751 msgstr ""
6750 6752
6751 6753 #: rhodecode/templates/debug_style/index.html:55
6752 6754 msgid "Typography"
6753 6755 msgstr ""
6754 6756
6755 6757 #: rhodecode/templates/debug_style/index.html:56
6756 6758 msgid "Forms"
6757 6759 msgstr ""
6758 6760
6759 6761 #: rhodecode/templates/debug_style/index.html:57
6760 6762 msgid "Buttons"
6761 6763 msgstr ""
6762 6764
6763 6765 #: rhodecode/templates/debug_style/index.html:58
6764 6766 msgid "Labels"
6765 6767 msgstr ""
6766 6768
6767 6769 #: rhodecode/templates/debug_style/index.html:59
6768 6770 msgid "Tables"
6769 6771 msgstr ""
6770 6772
6771 6773 #: rhodecode/templates/debug_style/index.html:60
6772 6774 msgid "Tables wide"
6773 6775 msgstr ""
6774 6776
6775 6777 #: rhodecode/templates/debug_style/index.html:61
6776 6778 msgid "Collapsable Content"
6777 6779 msgstr ""
6778 6780
6779 6781 #: rhodecode/templates/debug_style/index.html:63
6780 6782 msgid "Layout form with sidebar"
6781 6783 msgstr ""
6782 6784
6783 6785 #: rhodecode/templates/debug_style/index.html:64
6784 6786 msgid "Login"
6785 6787 msgstr ""
6786 6788
6787 6789 #: rhodecode/templates/debug_style/index.html:65
6788 6790 msgid "Login 2"
6789 6791 msgstr ""
6790 6792
6791 6793 #: rhodecode/templates/debug_style/index.html:66
6792 6794 msgid "Code blocks"
6793 6795 msgstr ""
6794 6796
6795 6797 #: rhodecode/templates/debug_style/index.html:69
6796 6798 msgid "Panels"
6797 6799 msgstr ""
6798 6800
6799 6801 #: rhodecode/templates/debug_style/index.html:72
6800 6802 msgid "Form elements"
6801 6803 msgstr ""
6802 6804
6803 6805 #: rhodecode/templates/debug_style/index.html:73
6804 6806 msgid "Form elements small"
6805 6807 msgstr ""
6806 6808
6807 6809 #: rhodecode/templates/debug_style/index.html:74
6808 6810 msgid "Form inline elements"
6809 6811 msgstr ""
6810 6812
6811 6813 #: rhodecode/templates/debug_style/index.html:75
6812 6814 msgid "Form vertical"
6813 6815 msgstr ""
6814 6816
6815 6817 #: rhodecode/templates/email_templates/base.mako:16
6816 6818 #, python-format
6817 6819 msgid "This is a notification from RhodeCode. %(instance_url)s"
6818 6820 msgstr ""
6819 6821
6820 6822 #: rhodecode/templates/email_templates/commit_comment.mako:5
6821 6823 #: rhodecode/templates/email_templates/pull_request_comment.mako:5
6822 6824 msgid "[mention]"
6823 6825 msgstr ""
6824 6826
6825 6827 #: rhodecode/templates/email_templates/commit_comment.mako:5
6826 6828 #, python-format
6827 6829 msgid "%(user)s commented on commit of %(repo_name)s"
6828 6830 msgstr ""
6829 6831
6830 6832 #: rhodecode/templates/email_templates/commit_comment.mako:14
6831 6833 #: rhodecode/templates/email_templates/commit_comment.mako:41
6832 6834 #: rhodecode/templates/email_templates/pull_request_comment.mako:15
6833 6835 #: rhodecode/templates/email_templates/pull_request_comment.mako:51
6834 6836 msgid "Comment link"
6835 6837 msgstr ""
6836 6838
6837 6839 #: rhodecode/templates/email_templates/commit_comment.mako:19
6838 6840 #: rhodecode/templates/email_templates/commit_comment.mako:43
6839 6841 #: rhodecode/templates/email_templates/pull_request_comment.mako:20
6840 6842 #: rhodecode/templates/email_templates/pull_request_comment.mako:54
6841 6843 #, python-format
6842 6844 msgid "File: %(comment_file)s on line %(comment_line)s"
6843 6845 msgstr ""
6844 6846
6845 6847 #: rhodecode/templates/email_templates/commit_comment.mako:28
6846 6848 #: rhodecode/templates/email_templates/commit_comment.mako:56
6847 6849 msgid "Commit status was changed to"
6848 6850 msgstr ""
6849 6851
6850 6852 #: rhodecode/templates/email_templates/commit_comment.mako:35
6851 6853 #, python-format
6852 6854 msgid "%(user)s commented on a file in commit of %(repo_url)s."
6853 6855 msgstr ""
6854 6856
6855 6857 #: rhodecode/templates/email_templates/commit_comment.mako:37
6856 6858 #, python-format
6857 6859 msgid "%(user)s commented on a commit of %(repo_url)s."
6858 6860 msgstr ""
6859 6861
6860 6862 #: rhodecode/templates/email_templates/commit_comment.mako:47
6861 6863 #: rhodecode/templates/files/files_detail.html:5
6862 6864 #: rhodecode/templates/files/files_detail.html:12
6863 6865 msgid "Commit Description"
6864 6866 msgstr ""
6865 6867
6866 6868 #: rhodecode/templates/email_templates/pull_request_comment.mako:5
6867 6869 #, python-format
6868 6870 msgid "%(user)s commented on pull request #%(pr_id)s: \"%(pr_title)s\""
6869 6871 msgstr ""
6870 6872
6871 6873 #: rhodecode/templates/email_templates/pull_request_comment.mako:17
6872 6874 #: rhodecode/templates/email_templates/pull_request_comment.mako:52
6873 6875 msgid "Source repository"
6874 6876 msgstr ""
6875 6877
6876 6878 #: rhodecode/templates/email_templates/pull_request_comment.mako:29
6877 6879 #: rhodecode/templates/email_templates/pull_request_comment.mako:63
6878 6880 msgid "Pull request status was changed to"
6879 6881 msgstr ""
6880 6882
6881 6883 #: rhodecode/templates/email_templates/pull_request_comment.mako:31
6882 6884 #: rhodecode/templates/email_templates/pull_request_comment.mako:65
6883 6885 msgid "Pull request was closed with status"
6884 6886 msgstr ""
6885 6887
6886 6888 #: rhodecode/templates/email_templates/pull_request_comment.mako:37
6887 6889 #, python-format
6888 6890 msgid "%(user)s commented on a file on pull request #%(pr_id)s: \"%(pr_title)s\"."
6889 6891 msgstr ""
6890 6892
6891 6893 #: rhodecode/templates/email_templates/pull_request_comment.mako:43
6892 6894 #, python-format
6893 6895 msgid "%(user)s commented on a pull request #%(pr_id)s \"%(pr_title)s\"."
6894 6896 msgstr ""
6895 6897
6896 6898 #: rhodecode/templates/email_templates/pull_request_review.mako:5
6897 6899 #, python-format
6898 6900 msgid "%(user)s wants you to review pull request #%(pr_url)s: \"%(pr_title)s\""
6899 6901 msgstr ""
6900 6902
6901 6903 #: rhodecode/templates/email_templates/pull_request_review.mako:17
6902 6904 #: rhodecode/templates/email_templates/pull_request_review.mako:54
6903 6905 #, python-format
6904 6906 msgid "Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s"
6905 6907 msgstr ""
6906 6908
6907 6909 #: rhodecode/templates/email_templates/pull_request_review.mako:26
6908 6910 #: rhodecode/templates/email_templates/pull_request_review.mako:63
6909 6911 msgid "Link"
6910 6912 msgstr ""
6911 6913
6912 6914 #: rhodecode/templates/email_templates/pull_request_review.mako:35
6913 6915 #: rhodecode/templates/email_templates/pull_request_review.mako:72
6914 6916 #, python-format
6915 6917 msgid "Commit (%(num)s)"
6916 6918 msgid_plural "Commits (%(num)s)"
6917 6919 msgstr[0] ""
6918 6920 msgstr[1] ""
6919 6921
6920 6922 #: rhodecode/templates/email_templates/pull_request_review.mako:47
6921 6923 #, python-format
6922 6924 msgid "%(user)s wants you to review pull request #%(pr_id)s: \"%(pr_title)s\"."
6923 6925 msgstr ""
6924 6926
6925 6927 #: rhodecode/templates/email_templates/test.mako:5
6926 6928 msgid "hello \"world\""
6927 6929 msgstr ""
6928 6930
6929 6931 #: rhodecode/templates/email_templates/test.mako:21
6930 6932 msgid "Translation"
6931 6933 msgstr ""
6932 6934
6933 6935 #: rhodecode/templates/errors/error_document.html:39
6934 6936 #, python-format
6935 6937 msgid "You will be redirected to %s in %s seconds"
6936 6938 msgstr ""
6937 6939
6938 6940 #: rhodecode/templates/feed/atom_feed_entry.mako:3
6939 6941 #, python-format
6940 6942 msgid "%(user)s commited on %(date)s UTC"
6941 6943 msgstr ""
6942 6944
6943 6945 #: rhodecode/templates/feed/atom_feed_entry.mako:26
6944 6946 #: rhodecode/templates/pullrequests/pullrequest_show.html:386
6945 6947 #: rhodecode/templates/pullrequests/pullrequest_show.html:392
6946 6948 msgid "Commit was too big and was cut off..."
6947 6949 msgstr ""
6948 6950
6949 6951 #: rhodecode/templates/files/diff_2way.html:15
6950 6952 #, python-format
6951 6953 msgid "%s File side-by-side diff"
6952 6954 msgstr ""
6953 6955
6954 6956 #: rhodecode/templates/files/diff_2way.html:79
6955 6957 msgid "Enable editor mode"
6956 6958 msgstr ""
6957 6959
6958 6960 #: rhodecode/templates/files/diff_2way.html:80
6959 6961 msgid "Disable editor mode"
6960 6962 msgstr ""
6961 6963
6962 6964 #: rhodecode/templates/files/diff_2way.html:84
6963 6965 msgid "Previous change"
6964 6966 msgstr ""
6965 6967
6966 6968 #: rhodecode/templates/files/diff_2way.html:85
6967 6969 msgid "Next change"
6968 6970 msgstr ""
6969 6971
6970 6972 #: rhodecode/templates/files/diff_2way.html:100
6971 6973 msgid "mode"
6972 6974 msgstr ""
6973 6975
6974 6976 #: rhodecode/templates/files/file_authors_box.html:6
6975 6977 msgid "Last Author"
6976 6978 msgstr ""
6977 6979
6978 6980 #: rhodecode/templates/files/file_authors_box.html:8
6979 6981 #, python-format
6980 6982 msgid "File Author (%s)"
6981 6983 msgid_plural "File Authors (%s)"
6982 6984 msgstr[0] ""
6983 6985 msgstr[1] ""
6984 6986
6985 6987 #: rhodecode/templates/files/file_authors_box.html:11
6986 6988 msgid "Show All"
6987 6989 msgstr ""
6988 6990
6989 6991 #: rhodecode/templates/files/file_authors_box.html:26
6990 6992 msgid "last author"
6991 6993 msgstr ""
6992 6994
6993 6995 #: rhodecode/templates/files/file_diff.html:4
6994 6996 #, python-format
6995 6997 msgid "%s File Diff"
6996 6998 msgstr ""
6997 6999
6998 7000 #: rhodecode/templates/files/file_diff.html:37
6999 7001 msgid "for"
7000 7002 msgstr ""
7001 7003
7002 7004 #: rhodecode/templates/files/file_diff.html:53
7003 7005 msgid "No commits"
7004 7006 msgstr ""
7005 7007
7006 7008 #: rhodecode/templates/files/file_diff.html:81
7007 7009 msgid "Cannot diff binary files"
7008 7010 msgstr ""
7009 7011
7010 7012 #: rhodecode/templates/files/file_diff.html:83
7011 7013 msgid "File was not changed in this commit range"
7012 7014 msgstr ""
7013 7015
7014 7016 #: rhodecode/templates/files/file_tree_author_box.html:5
7015 7017 msgid "Commit Author"
7016 7018 msgstr ""
7017 7019
7018 7020 #: rhodecode/templates/files/files.html:4
7019 7021 #: rhodecode/templates/files/files_pjax.html:2
7020 7022 #, python-format
7021 7023 msgid "%s Files"
7022 7024 msgstr ""
7023 7025
7024 7026 #: rhodecode/templates/files/files.html:143
7025 7027 msgid "Switch To Commit"
7026 7028 msgstr ""
7027 7029
7028 7030 #: rhodecode/templates/files/files_add.html:4
7029 7031 #, python-format
7030 7032 msgid "%s Files Add"
7031 7033 msgstr ""
7032 7034
7033 7035 #: rhodecode/templates/files/files_add.html:15
7034 7036 msgid "Add new file"
7035 7037 msgstr ""
7036 7038
7037 7039 #: rhodecode/templates/files/files_add.html:34
7038 7040 #: rhodecode/templates/files/files_delete.html:34
7039 7041 #: rhodecode/templates/files/files_edit.html:34
7040 7042 msgid "Path"
7041 7043 msgstr ""
7042 7044
7043 7045 #: rhodecode/templates/files/files_add.html:39
7044 7046 msgid "Specify Custom Path"
7045 7047 msgstr ""
7046 7048
7047 7049 #: rhodecode/templates/files/files_add.html:44
7048 7050 msgid "Remove Custom Path"
7049 7051 msgstr ""
7050 7052
7051 7053 #: rhodecode/templates/files/files_add.html:50
7052 7054 msgid "Filename"
7053 7055 msgstr ""
7054 7056
7055 7057 #: rhodecode/templates/files/files_add.html:54
7056 7058 #: rhodecode/templates/files/files_add.html:65
7057 7059 msgid "or"
7058 7060 msgstr ""
7059 7061
7060 7062 #: rhodecode/templates/files/files_add.html:54
7061 7063 msgid "Upload File"
7062 7064 msgstr ""
7063 7065
7064 7066 #: rhodecode/templates/files/files_add.html:59
7065 7067 msgid "Upload file"
7066 7068 msgstr ""
7067 7069
7068 7070 #: rhodecode/templates/files/files_add.html:63
7069 7071 msgid "No file selected"
7070 7072 msgstr ""
7071 7073
7072 7074 #: rhodecode/templates/files/files_add.html:65
7073 7075 msgid "Create New File"
7074 7076 msgstr ""
7075 7077
7076 7078 #: rhodecode/templates/files/files_add.html:75
7077 7079 #: rhodecode/templates/files/files_edit.html:79
7078 7080 msgid "line wraps"
7079 7081 msgstr ""
7080 7082
7081 7083 #: rhodecode/templates/files/files_add.html:76
7082 7084 #: rhodecode/templates/files/files_edit.html:80
7083 7085 msgid "on"
7084 7086 msgstr ""
7085 7087
7086 7088 #: rhodecode/templates/files/files_add.html:76
7087 7089 #: rhodecode/templates/files/files_edit.html:80
7088 7090 msgid "off"
7089 7091 msgstr ""
7090 7092
7091 7093 #: rhodecode/templates/files/files_add.html:103
7092 7094 #: rhodecode/templates/files/files_edit.html:106
7093 7095 msgid "Commit changes"
7094 7096 msgstr ""
7095 7097
7096 7098 #: rhodecode/templates/files/files_browser.html:9
7097 7099 msgid "Previous commit"
7098 7100 msgstr ""
7099 7101
7100 7102 #: rhodecode/templates/files/files_browser.html:13
7101 7103 msgid "Next commit"
7102 7104 msgstr ""
7103 7105
7104 7106 #: rhodecode/templates/files/files_browser.html:19
7105 7107 msgid "Search File List"
7106 7108 msgstr ""
7107 7109
7108 7110 #: rhodecode/templates/files/files_browser.html:22
7109 7111 msgid "Close File List"
7110 7112 msgstr ""
7111 7113
7112 7114 #: rhodecode/templates/files/files_browser.html:27
7113 7115 msgid "Add File"
7114 7116 msgstr ""
7115 7117
7116 7118 #: rhodecode/templates/files/files_browser.html:34
7117 7119 msgid "Loading file list..."
7118 7120 msgstr ""
7119 7121
7120 7122 #: rhodecode/templates/files/files_browser.html:50
7121 7123 msgid "Size"
7122 7124 msgstr ""
7123 7125
7124 7126 #: rhodecode/templates/files/files_browser.html:51
7125 7127 msgid "Modified"
7126 7128 msgstr ""
7127 7129
7128 7130 #: rhodecode/templates/files/files_browser.html:52
7129 7131 msgid "Last Commit"
7130 7132 msgstr ""
7131 7133
7132 7134 #: rhodecode/templates/files/files_browser.html:89
7133 7135 msgid "Loading..."
7134 7136 msgstr ""
7135 7137
7136 7138 #: rhodecode/templates/files/files_delete.html:4
7137 7139 #, python-format
7138 7140 msgid "%s Files Delete"
7139 7141 msgstr ""
7140 7142
7141 7143 #: rhodecode/templates/files/files_delete.html:15
7142 7144 msgid "Delete file"
7143 7145 msgstr ""
7144 7146
7145 7147 #: rhodecode/templates/files/files_delete.html:45
7146 7148 #: rhodecode/templates/files/files_source.html:49
7147 7149 #, python-format
7148 7150 msgid "Binary file (%s)"
7149 7151 msgstr ""
7150 7152
7151 7153 #: rhodecode/templates/files/files_delete.html:50
7152 7154 #: rhodecode/templates/files/files_source.html:61
7153 7155 msgid "File is too big to display"
7154 7156 msgstr ""
7155 7157
7156 7158 #: rhodecode/templates/files/files_delete.html:70
7157 7159 msgid "Delete File"
7158 7160 msgstr ""
7159 7161
7160 7162 #: rhodecode/templates/files/files_detail.html:35
7161 7163 msgid "File last commit"
7162 7164 msgstr ""
7163 7165
7164 7166 #: rhodecode/templates/files/files_detail.html:54
7165 7167 msgid "Diff to Commit"
7166 7168 msgstr ""
7167 7169
7168 7170 #: rhodecode/templates/files/files_detail.html:55
7169 7171 msgid "Show at Commit"
7170 7172 msgstr ""
7171 7173
7172 7174 #: rhodecode/templates/files/files_edit.html:4
7173 7175 #, python-format
7174 7176 msgid "%s File Edit"
7175 7177 msgstr ""
7176 7178
7177 7179 #: rhodecode/templates/files/files_edit.html:15
7178 7180 msgid "Edit file"
7179 7181 msgstr ""
7180 7182
7181 7183 #: rhodecode/templates/files/files_edit.html:55
7182 7184 msgid "history"
7183 7185 msgstr ""
7184 7186
7185 7187 #: rhodecode/templates/files/files_edit.html:61
7186 7188 msgid "source"
7187 7189 msgstr ""
7188 7190
7189 7191 #: rhodecode/templates/files/files_edit.html:63
7190 7192 #: rhodecode/templates/files/files_pjax.html:19
7191 7193 msgid "annotation"
7192 7194 msgstr ""
7193 7195
7194 7196 #: rhodecode/templates/files/files_edit.html:65
7195 7197 msgid "raw"
7196 7198 msgstr ""
7197 7199
7198 7200 #: rhodecode/templates/files/files_edit.html:67
7199 7201 msgid "download"
7200 7202 msgstr ""
7201 7203
7202 7204 #: rhodecode/templates/files/files_edit.html:74
7203 7205 msgid "Editing file"
7204 7206 msgstr ""
7205 7207
7206 7208 #: rhodecode/templates/files/files_pjax.html:17
7207 7209 msgid "Location"
7208 7210 msgstr ""
7209 7211
7210 7212 #: rhodecode/templates/files/files_source.html:6
7211 #: rhodecode/templates/search/search_content.html:20
7213 #: rhodecode/templates/search/search_content.html:57
7212 7214 msgid "line"
7213 7215 msgid_plural "lines"
7214 7216 msgstr[0] ""
7215 7217 msgstr[1] ""
7216 7218
7217 7219 #: rhodecode/templates/files/files_source.html:12
7218 7220 msgid "History"
7219 7221 msgstr ""
7220 7222
7221 7223 #: rhodecode/templates/files/files_source.html:15
7222 #: rhodecode/templates/search/search_content.html:31
7224 #: rhodecode/templates/search/search_content.html:68
7223 7225 msgid "Show Full History"
7224 7226 msgstr ""
7225 7227
7226 7228 #: rhodecode/templates/files/files_source.html:20
7227 #: rhodecode/templates/search/search_content.html:33
7229 #: rhodecode/templates/search/search_content.html:70
7228 7230 msgid "Annotation"
7229 7231 msgstr ""
7230 7232
7231 7233 #: rhodecode/templates/files/files_source.html:22
7232 #: rhodecode/templates/search/search_content.html:34
7234 #: rhodecode/templates/search/search_content.html:71
7233 7235 msgid "Raw"
7234 7236 msgstr ""
7235 7237
7236 7238 #: rhodecode/templates/files/files_source.html:24
7237 #: rhodecode/templates/search/search_content.html:36
7239 #: rhodecode/templates/search/search_content.html:73
7238 7240 msgid "Download"
7239 7241 msgstr ""
7240 7242
7241 7243 #: rhodecode/templates/files/files_source.html:31
7242 7244 #, python-format
7243 7245 msgid "Edit on Branch:%s"
7244 7246 msgstr ""
7245 7247
7246 7248 #: rhodecode/templates/files/files_source.html:36
7247 7249 msgid "Editing binary files not allowed"
7248 7250 msgstr ""
7249 7251
7250 7252 #: rhodecode/templates/files/files_source.html:39
7251 7253 msgid "Editing files allowed only when on branch head commit"
7252 7254 msgstr ""
7253 7255
7254 7256 #: rhodecode/templates/files/files_source.html:40
7255 7257 msgid "Deleting files allowed only when on branch head commit"
7256 7258 msgstr ""
7257 7259
7258 7260 #: rhodecode/templates/followers/followers.html:5
7259 7261 #: rhodecode/templates/followers/followers.html:27
7260 7262 #, python-format
7261 7263 msgid "%s Followers"
7262 7264 msgstr ""
7263 7265
7264 7266 #: rhodecode/templates/followers/followers.html:12
7265 7267 msgid "Followers"
7266 7268 msgstr ""
7267 7269
7268 7270 #: rhodecode/templates/followers/followers_data.html:5
7269 7271 msgid "Follower Name"
7270 7272 msgstr ""
7271 7273
7272 7274 #: rhodecode/templates/followers/followers_data.html:6
7273 7275 msgid "Following Since"
7274 7276 msgstr ""
7275 7277
7276 7278 #: rhodecode/templates/forks/fork.html:5
7277 7279 #, python-format
7278 7280 msgid "Fork repository %s"
7279 7281 msgstr ""
7280 7282
7281 7283 #: rhodecode/templates/forks/fork.html:12
7282 7284 msgid "New Fork"
7283 7285 msgstr ""
7284 7286
7285 7287 #: rhodecode/templates/forks/fork.html:37
7286 7288 msgid "Fork name"
7287 7289 msgstr ""
7288 7290
7289 7291 #: rhodecode/templates/forks/fork.html:91
7290 7292 msgid "Copy permissions"
7291 7293 msgstr ""
7292 7294
7293 7295 #: rhodecode/templates/forks/fork.html:95
7294 7296 msgid "Copy permissions from forked repository"
7295 7297 msgstr ""
7296 7298
7297 7299 #: rhodecode/templates/forks/fork.html:100
7298 7300 msgid "Fork this Repository"
7299 7301 msgstr ""
7300 7302
7301 7303 #: rhodecode/templates/forks/forks.html:5
7302 7304 #, python-format
7303 7305 msgid "%s Forks"
7304 7306 msgstr ""
7305 7307
7306 7308 #: rhodecode/templates/forks/forks.html:12
7307 7309 msgid "Forks"
7308 7310 msgstr ""
7309 7311
7310 7312 #: rhodecode/templates/forks/forks.html:32
7311 7313 msgid "Create new fork"
7312 7314 msgstr ""
7313 7315
7314 #: rhodecode/templates/forks/forks_data.html:9
7316 #: rhodecode/templates/forks/forks_data.html:10
7315 7317 msgid "Forked"
7316 7318 msgstr ""
7317 7319
7318 #: rhodecode/templates/forks/forks_data.html:46
7320 #: rhodecode/templates/forks/forks_data.html:48
7319 7321 msgid "There are no forks yet"
7320 7322 msgstr ""
7321 7323
7322 7324 #: rhodecode/templates/journal/journal.html:13
7323 7325 msgid "Filter"
7324 7326 msgstr ""
7325 7327
7326 7328 #: rhodecode/templates/journal/journal.html:23
7327 7329 msgid "ATOM journal feed"
7328 7330 msgstr ""
7329 7331
7330 7332 #: rhodecode/templates/journal/journal.html:24
7331 7333 msgid "RSS journal feed"
7332 7334 msgstr ""
7333 7335
7334 7336 #: rhodecode/templates/journal/journal_data.html:53
7335 7337 msgid "No entries yet"
7336 7338 msgstr ""
7337 7339
7338 7340 #: rhodecode/templates/journal/public_journal.html:4
7339 7341 #: rhodecode/templates/journal/public_journal.html:24
7340 7342 msgid "Public Journal"
7341 7343 msgstr ""
7342 7344
7343 7345 #: rhodecode/templates/journal/public_journal.html:16
7344 7346 msgid "ATOM public journal feed"
7345 7347 msgstr ""
7346 7348
7347 7349 #: rhodecode/templates/journal/public_journal.html:17
7348 7350 msgid "RSS public journal feed"
7349 7351 msgstr ""
7350 7352
7351 7353 #: rhodecode/templates/pullrequests/pullrequest.html:4
7352 7354 #: rhodecode/templates/pullrequests/pullrequest.html:8
7353 7355 msgid "New pull request"
7354 7356 msgstr ""
7355 7357
7356 7358 #: rhodecode/templates/pullrequests/pullrequest.html:52
7357 7359 msgid "Write a short description on this pull request"
7358 7360 msgstr ""
7359 7361
7360 7362 #: rhodecode/templates/pullrequests/pullrequest.html:59
7361 7363 msgid "Commit flow"
7362 7364 msgstr ""
7363 7365
7364 7366 #: rhodecode/templates/pullrequests/pullrequest.html:67
7365 7367 msgid "Origin repository"
7366 7368 msgstr ""
7367 7369
7368 7370 #: rhodecode/templates/pullrequests/pullrequest.html:85
7369 7371 msgid "Loading refs..."
7370 7372 msgstr ""
7371 7373
7372 7374 #: rhodecode/templates/pullrequests/pullrequest.html:96
7373 7375 msgid "Submit Pull Request"
7374 7376 msgstr ""
7375 7377
7376 7378 #: rhodecode/templates/pullrequests/pullrequest.html:109
7377 7379 #: rhodecode/templates/pullrequests/pullrequest_show.html:182
7378 7380 msgid "Pull request reviewers"
7379 7381 msgstr ""
7380 7382
7381 7383 #: rhodecode/templates/pullrequests/pullrequest.html:117
7382 7384 #: rhodecode/templates/pullrequests/pullrequest_show.html:215
7383 7385 msgid "Add reviewer"
7384 7386 msgstr ""
7385 7387
7386 #: rhodecode/templates/pullrequests/pullrequest.html:278
7387 #: rhodecode/templates/pullrequests/pullrequest.html:520
7388 #: rhodecode/templates/pullrequests/pullrequest.html:279
7389 #: rhodecode/templates/pullrequests/pullrequest.html:521
7388 7390 msgid "Please select origin and destination"
7389 7391 msgstr ""
7390 7392
7391 #: rhodecode/templates/pullrequests/pullrequest.html:284
7393 #: rhodecode/templates/pullrequests/pullrequest.html:285
7392 7394 msgid "Loading compare ..."
7393 7395 msgstr ""
7394 7396
7395 #: rhodecode/templates/pullrequests/pullrequest.html:331
7396 #: rhodecode/templates/pullrequests/pullrequest.html:333
7397 #: rhodecode/templates/pullrequests/pullrequest.html:332
7398 #: rhodecode/templates/pullrequests/pullrequest.html:334
7397 7399 msgid "This pull request will consist of __COMMITS__ commit."
7398 7400 msgid_plural "This pull request will consist of __COMMITS__ commits."
7399 7401 msgstr[0] ""
7400 7402 msgstr[1] ""
7401 7403
7402 #: rhodecode/templates/pullrequests/pullrequest.html:336
7404 #: rhodecode/templates/pullrequests/pullrequest.html:337
7403 7405 msgid "Show detailed compare."
7404 7406 msgstr ""
7405 7407
7406 #: rhodecode/templates/pullrequests/pullrequest.html:343
7408 #: rhodecode/templates/pullrequests/pullrequest.html:344
7407 7409 msgid "There are no commits to merge."
7408 7410 msgstr ""
7409 7411
7410 #: rhodecode/templates/pullrequests/pullrequest.html:450
7412 #: rhodecode/templates/pullrequests/pullrequest.html:451
7411 7413 msgid "Destination repository"
7412 7414 msgstr ""
7413 7415
7414 #: rhodecode/templates/pullrequests/pullrequest.html:461
7416 #: rhodecode/templates/pullrequests/pullrequest.html:462
7415 7417 msgid "Select commit reference"
7416 7418 msgstr ""
7417 7419
7418 7420 #: rhodecode/templates/pullrequests/pullrequest_show.html:4
7419 7421 #, python-format
7420 7422 msgid "%s Pull Request #%s"
7421 7423 msgstr ""
7422 7424
7423 7425 #: rhodecode/templates/pullrequests/pullrequest_show.html:48
7424 7426 msgid "From"
7425 7427 msgstr ""
7426 7428
7427 7429 #: rhodecode/templates/pullrequests/pullrequest_show.html:51
7428 7430 #: rhodecode/templates/pullrequests/pullrequest_show.html:185
7429 7431 msgid "Close"
7430 7432 msgstr ""
7431 7433
7432 7434 #: rhodecode/templates/pullrequests/pullrequest_show.html:58
7433 7435 msgid "Origin"
7434 7436 msgstr ""
7435 7437
7436 7438 #: rhodecode/templates/pullrequests/pullrequest_show.html:105
7437 7439 msgid "Review"
7438 7440 msgstr ""
7439 7441
7440 7442 #: rhodecode/templates/pullrequests/pullrequest_show.html:116
7441 7443 #, python-format
7442 7444 msgid "calculated based on %s reviewer vote"
7443 7445 msgid_plural "calculated based on %s reviewers votes"
7444 7446 msgstr[0] ""
7445 7447 msgstr[1] ""
7446 7448
7447 7449 #: rhodecode/templates/pullrequests/pullrequest_show.html:139
7448 7450 #: rhodecode/templates/pullrequests/pullrequest_show.html:141
7449 7451 #, python-format
7450 7452 msgid "%d Pull request comment"
7451 7453 msgid_plural "%d Pull request comments"
7452 7454 msgstr[0] ""
7453 7455 msgstr[1] ""
7454 7456
7455 7457 #: rhodecode/templates/pullrequests/pullrequest_show.html:151
7456 7458 #, python-format
7457 7459 msgid "%d Outdated Comment"
7458 7460 msgid_plural "%d Outdated Comments"
7459 7461 msgstr[0] ""
7460 7462 msgstr[1] ""
7461 7463
7462 7464 #: rhodecode/templates/pullrequests/pullrequest_show.html:151
7463 7465 msgid "(Show)"
7464 7466 msgstr ""
7465 7467
7466 7468 #: rhodecode/templates/pullrequests/pullrequest_show.html:160
7467 7469 #: rhodecode/templates/pullrequests/pullrequest_show.html:219
7468 7470 msgid "Save Changes"
7469 7471 msgstr ""
7470 7472
7471 #: rhodecode/templates/pullrequests/pullrequest_show.html:200
7473 #: rhodecode/templates/pullrequests/pullrequest_show.html:199
7472 7474 msgid "reviewer"
7473 7475 msgstr ""
7474 7476
7475 7477 #: rhodecode/templates/pullrequests/pullrequest_show.html:236
7476 7478 msgid "Missing requirements:"
7477 7479 msgstr ""
7478 7480
7479 7481 #: rhodecode/templates/pullrequests/pullrequest_show.html:237
7480 7482 msgid "These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled."
7481 7483 msgstr ""
7482 7484
7483 7485 #: rhodecode/templates/pullrequests/pullrequest_show.html:245
7484 7486 msgid "Missing commits"
7485 7487 msgstr ""
7486 7488
7487 7489 #: rhodecode/templates/pullrequests/pullrequest_show.html:246
7488 7490 msgid "This pull request cannot be displayed, because one or more commits no longer exist in the source repository."
7489 7491 msgstr ""
7490 7492
7491 7493 #: rhodecode/templates/pullrequests/pullrequest_show.html:247
7492 7494 msgid "Please update this pull request, push the commits back into the source repository, or consider closing this pull request."
7493 7495 msgstr ""
7494 7496
7495 7497 #: rhodecode/templates/pullrequests/pullrequest_show.html:254
7496 7498 msgid "Update commits"
7497 7499 msgstr ""
7498 7500
7499 7501 #: rhodecode/templates/pullrequests/pullrequest_show.html:257
7500 7502 #, python-format
7501 7503 msgid "Compare View: %s commit"
7502 7504 msgid_plural "Compare View: %s commits"
7503 7505 msgstr[0] ""
7504 7506 msgstr[1] ""
7505 7507
7506 7508 #: rhodecode/templates/pullrequests/pullrequest_show.html:330
7507 7509 #: rhodecode/templates/pullrequests/pullrequest_show.html:365
7508 7510 msgid "Outdated Inline Comments"
7509 7511 msgstr ""
7510 7512
7511 7513 #: rhodecode/templates/pullrequests/pullrequest_show.html:386
7512 7514 #: rhodecode/templates/pullrequests/pullrequest_show.html:392
7513 7515 msgid "Showing a huge diff might take some time and resources"
7514 7516 msgstr ""
7515 7517
7516 7518 #: rhodecode/templates/pullrequests/pullrequests.html:4
7517 7519 #, python-format
7518 7520 msgid "%s Pull Requests"
7519 7521 msgstr ""
7520 7522
7521 7523 #: rhodecode/templates/pullrequests/pullrequests.html:34
7522 7524 msgid "Open new Pull Request"
7523 7525 msgstr ""
7524 7526
7525 7527 #: rhodecode/templates/pullrequests/pullrequests.html:48
7526 7528 msgid "Opened"
7527 7529 msgstr ""
7528 7530
7529 7531 #: rhodecode/templates/pullrequests/pullrequests.html:49
7530 7532 msgid "Opened by me"
7531 7533 msgstr ""
7532 7534
7533 7535 #: rhodecode/templates/pullrequests/pullrequests.html:50
7536 msgid "Awaiting review"
7537 msgstr ""
7538
7539 #: rhodecode/templates/pullrequests/pullrequests.html:51
7534 7540 msgid "Awaiting my review"
7535 7541 msgstr ""
7536 7542
7537 #: rhodecode/templates/pullrequests/pullrequests.html:52
7538 msgid "Awaiting review"
7539 msgstr ""
7540
7541 7543 #: rhodecode/templates/pullrequests/pullrequests.html:53
7542 7544 msgid "From this repo"
7543 7545 msgstr ""
7544 7546
7545 7547 #: rhodecode/templates/pullrequests/pullrequests.html:62
7546 7548 #, python-format
7547 7549 msgid "Pull Requests from %(repo_name)s repository"
7548 7550 msgstr ""
7549 7551
7550 7552 #: rhodecode/templates/pullrequests/pullrequests.html:64
7551 7553 #, python-format
7552 7554 msgid "Closed Pull Requests to repository %(repo_name)s"
7553 7555 msgstr ""
7554 7556
7555 7557 #: rhodecode/templates/pullrequests/pullrequests.html:66
7556 7558 #, python-format
7557 7559 msgid "Pull Requests to %(repo_name)s repository opened by me"
7558 7560 msgstr ""
7559 7561
7560 7562 #: rhodecode/templates/pullrequests/pullrequests.html:68
7561 7563 #, python-format
7562 7564 msgid "Pull Requests to %(repo_name)s repository awaiting review"
7563 7565 msgstr ""
7564 7566
7565 7567 #: rhodecode/templates/pullrequests/pullrequests.html:70
7566 7568 #, python-format
7567 7569 msgid "Pull Requests to %(repo_name)s repository awaiting my review"
7568 7570 msgstr ""
7569 7571
7570 7572 #: rhodecode/templates/pullrequests/pullrequests.html:72
7571 7573 #, python-format
7572 7574 msgid "Pull Requests to %(repo_name)s repository"
7573 7575 msgstr ""
7574 7576
7575 7577 #: rhodecode/templates/search/search.html:6
7576 7578 #: rhodecode/templates/search/search.html:17
7577 7579 #, python-format
7578 7580 msgid "Search inside repository %(repo_name)s"
7579 7581 msgstr ""
7580 7582
7581 7583 #: rhodecode/templates/search/search.html:8
7582 7584 #: rhodecode/templates/search/search.html:19
7583 7585 msgid "Search inside all accessible repositories"
7584 7586 msgstr ""
7585 7587
7586 7588 #: rhodecode/templates/search/search.html:60
7587 7589 msgid "Search item"
7588 7590 msgstr ""
7589 7591
7590 7592 #: rhodecode/templates/search/search.html:63
7591 7593 msgid "File contents"
7592 7594 msgstr ""
7593 7595
7594 7596 #: rhodecode/templates/search/search.html:63
7595 7597 msgid "Commit messages"
7596 7598 msgstr ""
7597 7599
7598 7600 #: rhodecode/templates/search/search.html:63
7599 7601 msgid "File names"
7600 7602 msgstr ""
7601 7603
7604 #: rhodecode/templates/search/search_commit.html:11
7605 msgid "Age (new first)"
7606 msgstr ""
7607
7608 #: rhodecode/templates/search/search_commit.html:13
7609 msgid "Age (old first)"
7610 msgstr ""
7611
7612 #: rhodecode/templates/search/search_content.html:33
7613 msgid "more matches in this file"
7614 msgstr ""
7615
7602 7616 #: rhodecode/templates/search/search_path.html:4
7603 7617 msgid "File"
7604 7618 msgstr ""
7605 7619
7606 7620 #: rhodecode/templates/summary/base.html:5
7607 7621 #, python-format
7608 7622 msgid "%s Summary"
7609 7623 msgstr ""
7610 7624
7611 7625 #: rhodecode/templates/summary/base.html:13
7612 7626 #, python-format
7613 7627 msgid "%s ATOM feed"
7614 7628 msgstr ""
7615 7629
7616 7630 #: rhodecode/templates/summary/base.html:14
7617 7631 #, python-format
7618 7632 msgid "%s RSS feed"
7619 7633 msgstr ""
7620 7634
7621 7635 #: rhodecode/templates/summary/components.html:5
7622 7636 #, python-format
7623 7637 msgid "%(num)s Branch"
7624 7638 msgid_plural "%(num)s Branches"
7625 7639 msgstr[0] ""
7626 7640 msgstr[1] ""
7627 7641
7628 7642 #: rhodecode/templates/summary/components.html:12
7629 7643 #, python-format
7630 7644 msgid "%(num)s Closed Branch"
7631 7645 msgid_plural "%(num)s Closed Branches"
7632 7646 msgstr[0] ""
7633 7647 msgstr[1] ""
7634 7648
7635 7649 #: rhodecode/templates/summary/components.html:19
7636 7650 #, python-format
7637 7651 msgid "%(num)s Tag"
7638 7652 msgid_plural "%(num)s Tags"
7639 7653 msgstr[0] ""
7640 7654 msgstr[1] ""
7641 7655
7642 7656 #: rhodecode/templates/summary/components.html:26
7643 7657 #, python-format
7644 7658 msgid "%(num)s Bookmark"
7645 7659 msgid_plural "%(num)s Bookmarks"
7646 7660 msgstr[0] ""
7647 7661 msgstr[1] ""
7648 7662
7649 7663 #: rhodecode/templates/summary/components.html:49
7650 7664 msgid "Read-only url"
7651 7665 msgstr ""
7652 7666
7653 7667 #: rhodecode/templates/summary/components.html:54
7654 7668 #: rhodecode/templates/summary/components.html:65
7655 7669 msgid "Show by Name"
7656 7670 msgstr ""
7657 7671
7658 7672 #: rhodecode/templates/summary/components.html:55
7659 7673 #: rhodecode/templates/summary/components.html:66
7660 7674 msgid "Show by ID"
7661 7675 msgstr ""
7662 7676
7663 7677 #: rhodecode/templates/summary/components.html:56
7664 7678 msgid "SVN Protocol is disabled. To enable it, see the"
7665 7679 msgstr ""
7666 7680
7667 7681 #: rhodecode/templates/summary/components.html:56
7668 7682 msgid "documentation here"
7669 7683 msgstr ""
7670 7684
7671 7685 #: rhodecode/templates/summary/components.html:60
7672 7686 msgid "Clone url"
7673 7687 msgstr ""
7674 7688
7675 7689 #: rhodecode/templates/summary/components.html:86
7676 7690 msgid "Information"
7677 7691 msgstr ""
7678 7692
7679 7693 #: rhodecode/templates/summary/components.html:95
7680 7694 #: rhodecode/templates/summary/components.html:98
7681 7695 #, python-format
7682 7696 msgid "%(num)s Commit"
7683 7697 msgid_plural "%(num)s Commits"
7684 7698 msgstr[0] ""
7685 7699 msgstr[1] ""
7686 7700
7687 7701 #: rhodecode/templates/summary/components.html:102
7688 7702 msgid "Number of Repository Forks"
7689 7703 msgstr ""
7690 7704
7691 7705 #: rhodecode/templates/summary/components.html:110
7692 7706 msgid "Calculating Repository Size..."
7693 7707 msgstr ""
7694 7708
7695 7709 #: rhodecode/templates/summary/components.html:141
7696 7710 msgid "Calculating Code Statistics..."
7697 7711 msgstr ""
7698 7712
7699 7713 #: rhodecode/templates/summary/components.html:145
7700 7714 msgid "Statistics are disabled for this repository"
7701 7715 msgstr ""
7702 7716
7703 7717 #: rhodecode/templates/summary/components.html:148
7704 7718 msgid "enable statistics"
7705 7719 msgstr ""
7706 7720
7707 7721 #: rhodecode/templates/summary/components.html:159
7708 7722 msgid "Downloads"
7709 7723 msgstr ""
7710 7724
7711 7725 #: rhodecode/templates/summary/components.html:165
7712 7726 msgid "There are no downloads yet"
7713 7727 msgstr ""
7714 7728
7715 7729 #: rhodecode/templates/summary/components.html:169
7716 7730 msgid "Downloads are disabled for this repository"
7717 7731 msgstr ""
7718 7732
7719 7733 #: rhodecode/templates/summary/components.html:172
7720 7734 msgid "enable downloads"
7721 7735 msgstr ""
7722 7736
7723 7737 #: rhodecode/templates/summary/summary.html:17
7724 7738 #: rhodecode/templates/summary/summary.html:19
7725 7739 msgid "RSS Feed"
7726 7740 msgstr ""
7727 7741
7728 7742 #: rhodecode/templates/summary/summary.html:35
7729 7743 msgid "Quick start"
7730 7744 msgstr ""
7731 7745
7732 7746 #: rhodecode/templates/summary/summary.html:48
7733 7747 #, python-format
7734 7748 msgid "Readme file from commit %s:%s"
7735 7749 msgstr ""
7736 7750
7737 7751 #: rhodecode/templates/tags/tags.html:5
7738 7752 #, python-format
7739 7753 msgid "%s Tags"
7740 7754 msgstr ""
7741 7755
7742 7756 #: rhodecode/templates/tags/tags.html:13
7743 7757 msgid "tags"
7744 7758 msgstr ""
7745 7759
7746 7760 #: rhodecode/templates/tags/tags.html:31
7747 7761 msgid "Compare Selected Tags"
7748 7762 msgstr ""
7749 7763
7750 7764 #: rhodecode/templates/users/user.html:29
7751 7765 #: rhodecode/templates/users/user_profile.html:5
7752 7766 msgid "Profile"
7753 7767 msgstr ""
7754 7768
7755 7769 #: rhodecode/templates/users/user_profile.html:35
7756 7770 msgid "First name"
7757 7771 msgstr ""
7758 7772
7759 7773 #: rhodecode/templates/users/user_profile.html:43
7760 7774 msgid "Last name"
7761 7775 msgstr ""
7762 7776
7763 7777 #: rhodecode/tests/lib/test_ext_json.py:162
7764 7778 msgid "hello"
7765 7779 msgstr ""
7766 7780
7767 7781 #: rhodecode/tests/lib/test_ext_json.py:163
7768 7782 msgid "singular"
7769 7783 msgid_plural "plural"
7770 7784 msgstr[0] ""
7771 7785 msgstr[1] ""
7772 7786
@@ -1,28 +1,43 b''
1 1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
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 Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 from zope.interface import Attribute, Interface
20 20
21 21
22 22 class IUserRegistered(Interface):
23 23 """
24 24 An event type that is emitted whenever a new user registers a user
25 25 account.
26 26 """
27 27 user = Attribute('The user object.')
28 28 session = Attribute('The session while processing the register form post.')
29
30
31 class IUserPreCreate(Interface):
32 """
33 An event type that is emitted before a new user object is created.
34 """
35 user_data = Attribute('Data used to create the new user')
36
37
38 class IUserPreUpdate(Interface):
39 """
40 An event type that is emitted before a user object is updated.
41 """
42 user = Attribute('The not yet updated user object')
43 user_data = Attribute('Data used to update the user')
@@ -1,551 +1,551 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 The base Controller API
23 23 Provides the BaseController class for subclassing. And usage in different
24 24 controllers
25 25 """
26 26
27 27 import logging
28 28 import socket
29 29
30 30 import ipaddress
31 31
32 32 from paste.auth.basic import AuthBasicAuthenticator
33 33 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 34 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 35 from pylons import config, tmpl_context as c, request, session, url
36 36 from pylons.controllers import WSGIController
37 37 from pylons.controllers.util import redirect
38 38 from pylons.i18n import translation
39 39 # marcink: don't remove this import
40 40 from pylons.templating import render_mako as render # noqa
41 41 from pylons.i18n.translation import _
42 42 from webob.exc import HTTPFound
43 43
44 44
45 45 import rhodecode
46 46 from rhodecode.authentication.base import VCS_TYPE
47 47 from rhodecode.lib import auth, utils2
48 48 from rhodecode.lib import helpers as h
49 49 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 50 from rhodecode.lib.exceptions import UserCreationError
51 51 from rhodecode.lib.utils import (
52 52 get_repo_slug, set_rhodecode_config, password_changed,
53 53 get_enabled_hook_classes)
54 54 from rhodecode.lib.utils2 import (
55 55 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 56 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 57 from rhodecode.model import meta
58 58 from rhodecode.model.db import Repository, User
59 59 from rhodecode.model.notification import NotificationModel
60 60 from rhodecode.model.scm import ScmModel
61 61 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62 62
63 63
64 64 log = logging.getLogger(__name__)
65 65
66 66
67 67 def _filter_proxy(ip):
68 68 """
69 69 Passed in IP addresses in HEADERS can be in a special format of multiple
70 70 ips. Those comma separated IPs are passed from various proxies in the
71 71 chain of request processing. The left-most being the original client.
72 72 We only care about the first IP which came from the org. client.
73 73
74 74 :param ip: ip string from headers
75 75 """
76 76 if ',' in ip:
77 77 _ips = ip.split(',')
78 78 _first_ip = _ips[0].strip()
79 79 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
80 80 return _first_ip
81 81 return ip
82 82
83 83
84 84 def _filter_port(ip):
85 85 """
86 86 Removes a port from ip, there are 4 main cases to handle here.
87 87 - ipv4 eg. 127.0.0.1
88 88 - ipv6 eg. ::1
89 89 - ipv4+port eg. 127.0.0.1:8080
90 90 - ipv6+port eg. [::1]:8080
91 91
92 92 :param ip:
93 93 """
94 94 def is_ipv6(ip_addr):
95 95 if hasattr(socket, 'inet_pton'):
96 96 try:
97 97 socket.inet_pton(socket.AF_INET6, ip_addr)
98 98 except socket.error:
99 99 return False
100 100 else:
101 101 # fallback to ipaddress
102 102 try:
103 103 ipaddress.IPv6Address(ip_addr)
104 104 except Exception:
105 105 return False
106 106 return True
107 107
108 108 if ':' not in ip: # must be ipv4 pure ip
109 109 return ip
110 110
111 111 if '[' in ip and ']' in ip: # ipv6 with port
112 112 return ip.split(']')[0][1:].lower()
113 113
114 114 # must be ipv6 or ipv4 with port
115 115 if is_ipv6(ip):
116 116 return ip
117 117 else:
118 118 ip, _port = ip.split(':')[:2] # means ipv4+port
119 119 return ip
120 120
121 121
122 122 def get_ip_addr(environ):
123 123 proxy_key = 'HTTP_X_REAL_IP'
124 124 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
125 125 def_key = 'REMOTE_ADDR'
126 126 _filters = lambda x: _filter_port(_filter_proxy(x))
127 127
128 128 ip = environ.get(proxy_key)
129 129 if ip:
130 130 return _filters(ip)
131 131
132 132 ip = environ.get(proxy_key2)
133 133 if ip:
134 134 return _filters(ip)
135 135
136 136 ip = environ.get(def_key, '0.0.0.0')
137 137 return _filters(ip)
138 138
139 139
140 140 def get_server_ip_addr(environ, log_errors=True):
141 141 hostname = environ.get('SERVER_NAME')
142 142 try:
143 143 return socket.gethostbyname(hostname)
144 144 except Exception as e:
145 145 if log_errors:
146 146 # in some cases this lookup is not possible, and we don't want to
147 147 # make it an exception in logs
148 148 log.exception('Could not retrieve server ip address: %s', e)
149 149 return hostname
150 150
151 151
152 152 def get_server_port(environ):
153 153 return environ.get('SERVER_PORT')
154 154
155 155
156 156 def get_access_path(environ):
157 157 path = environ.get('PATH_INFO')
158 158 org_req = environ.get('pylons.original_request')
159 159 if org_req:
160 160 path = org_req.environ.get('PATH_INFO')
161 161 return path
162 162
163 163
164 164 def vcs_operation_context(
165 165 environ, repo_name, username, action, scm, check_locking=True):
166 166 """
167 167 Generate the context for a vcs operation, e.g. push or pull.
168 168
169 169 This context is passed over the layers so that hooks triggered by the
170 170 vcs operation know details like the user, the user's IP address etc.
171 171
172 172 :param check_locking: Allows to switch of the computation of the locking
173 173 data. This serves mainly the need of the simplevcs middleware to be
174 174 able to disable this for certain operations.
175 175
176 176 """
177 177 # Tri-state value: False: unlock, None: nothing, True: lock
178 178 make_lock = None
179 179 locked_by = [None, None, None]
180 180 is_anonymous = username == User.DEFAULT_USER
181 181 if not is_anonymous and check_locking:
182 182 log.debug('Checking locking on repository "%s"', repo_name)
183 183 user = User.get_by_username(username)
184 184 repo = Repository.get_by_repo_name(repo_name)
185 185 make_lock, __, locked_by = repo.get_locking_state(
186 186 action, user.user_id)
187 187
188 188 settings_model = VcsSettingsModel(repo=repo_name)
189 189 ui_settings = settings_model.get_ui_settings()
190 190
191 191 extras = {
192 192 'ip': get_ip_addr(environ),
193 193 'username': username,
194 194 'action': action,
195 195 'repository': repo_name,
196 196 'scm': scm,
197 197 'config': rhodecode.CONFIG['__file__'],
198 198 'make_lock': make_lock,
199 199 'locked_by': locked_by,
200 200 'server_url': utils2.get_server_url(environ),
201 201 'hooks': get_enabled_hook_classes(ui_settings),
202 202 }
203 203 return extras
204 204
205 205
206 206 class BasicAuth(AuthBasicAuthenticator):
207 207
208 208 def __init__(self, realm, authfunc, auth_http_code=None,
209 209 initial_call_detection=False):
210 210 self.realm = realm
211 211 self.initial_call = initial_call_detection
212 212 self.authfunc = authfunc
213 213 self._rc_auth_http_code = auth_http_code
214 214
215 215 def _get_response_from_code(self, http_code):
216 216 try:
217 217 return get_exception(safe_int(http_code))
218 218 except Exception:
219 219 log.exception('Failed to fetch response for code %s' % http_code)
220 220 return HTTPForbidden
221 221
222 222 def build_authentication(self):
223 223 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
224 224 if self._rc_auth_http_code and not self.initial_call:
225 225 # return alternative HTTP code if alternative http return code
226 226 # is specified in RhodeCode config, but ONLY if it's not the
227 227 # FIRST call
228 228 custom_response_klass = self._get_response_from_code(
229 229 self._rc_auth_http_code)
230 230 return custom_response_klass(headers=head)
231 231 return HTTPUnauthorized(headers=head)
232 232
233 233 def authenticate(self, environ):
234 234 authorization = AUTHORIZATION(environ)
235 235 if not authorization:
236 236 return self.build_authentication()
237 237 (authmeth, auth) = authorization.split(' ', 1)
238 238 if 'basic' != authmeth.lower():
239 239 return self.build_authentication()
240 240 auth = auth.strip().decode('base64')
241 241 _parts = auth.split(':', 1)
242 242 if len(_parts) == 2:
243 243 username, password = _parts
244 244 if self.authfunc(
245 245 username, password, environ, VCS_TYPE):
246 246 return username
247 247 if username and password:
248 248 # we mark that we actually executed authentication once, at
249 249 # that point we can use the alternative auth code
250 250 self.initial_call = False
251 251
252 252 return self.build_authentication()
253 253
254 254 __call__ = authenticate
255 255
256 256
257 257 def attach_context_attributes(context):
258 rc_config = SettingsModel().get_all_settings()
258 rc_config = SettingsModel().get_all_settings(cache=True)
259 259
260 260 context.rhodecode_version = rhodecode.__version__
261 261 context.rhodecode_edition = config.get('rhodecode.edition')
262 262 # unique secret + version does not leak the version but keep consistency
263 263 context.rhodecode_version_hash = md5(
264 264 config.get('beaker.session.secret', '') +
265 265 rhodecode.__version__)[:8]
266 266
267 267 # Default language set for the incoming request
268 268 context.language = translation.get_lang()[0]
269 269
270 270 # Visual options
271 271 context.visual = AttributeDict({})
272 272
273 273 # DB store
274 274 context.visual.show_public_icon = str2bool(
275 275 rc_config.get('rhodecode_show_public_icon'))
276 276 context.visual.show_private_icon = str2bool(
277 277 rc_config.get('rhodecode_show_private_icon'))
278 278 context.visual.stylify_metatags = str2bool(
279 279 rc_config.get('rhodecode_stylify_metatags'))
280 280 context.visual.dashboard_items = safe_int(
281 281 rc_config.get('rhodecode_dashboard_items', 100))
282 282 context.visual.admin_grid_items = safe_int(
283 283 rc_config.get('rhodecode_admin_grid_items', 100))
284 284 context.visual.repository_fields = str2bool(
285 285 rc_config.get('rhodecode_repository_fields'))
286 286 context.visual.show_version = str2bool(
287 287 rc_config.get('rhodecode_show_version'))
288 288 context.visual.use_gravatar = str2bool(
289 289 rc_config.get('rhodecode_use_gravatar'))
290 290 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
291 291 context.visual.default_renderer = rc_config.get(
292 292 'rhodecode_markup_renderer', 'rst')
293 293 context.visual.rhodecode_support_url = \
294 294 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
295 295
296 296 context.pre_code = rc_config.get('rhodecode_pre_code')
297 297 context.post_code = rc_config.get('rhodecode_post_code')
298 298 context.rhodecode_name = rc_config.get('rhodecode_title')
299 299 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
300 300 # if we have specified default_encoding in the request, it has more
301 301 # priority
302 302 if request.GET.get('default_encoding'):
303 303 context.default_encodings.insert(0, request.GET.get('default_encoding'))
304 304 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
305 305
306 306 # INI stored
307 307 context.labs_active = str2bool(
308 308 config.get('labs_settings_active', 'false'))
309 309 context.visual.allow_repo_location_change = str2bool(
310 310 config.get('allow_repo_location_change', True))
311 311 context.visual.allow_custom_hooks_settings = str2bool(
312 312 config.get('allow_custom_hooks_settings', True))
313 313 context.debug_style = str2bool(config.get('debug_style', False))
314 314
315 315 context.rhodecode_instanceid = config.get('instance_id')
316 316
317 317 # AppEnlight
318 318 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
319 319 context.appenlight_api_public_key = config.get(
320 320 'appenlight.api_public_key', '')
321 321 context.appenlight_server_url = config.get('appenlight.server_url', '')
322 322
323 323 # END CONFIG VARS
324 324
325 325 # TODO: This dosn't work when called from pylons compatibility tween.
326 326 # Fix this and remove it from base controller.
327 327 # context.repo_name = get_repo_slug(request) # can be empty
328 328
329 329 context.csrf_token = auth.get_csrf_token()
330 330 context.backends = rhodecode.BACKENDS.keys()
331 331 context.backends.sort()
332 332 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
333 333 context.rhodecode_user.user_id)
334 334
335 335
336 336 def get_auth_user(environ):
337 337 ip_addr = get_ip_addr(environ)
338 338 # make sure that we update permissions each time we call controller
339 339 _auth_token = (request.GET.get('auth_token', '') or
340 340 request.GET.get('api_key', ''))
341 341
342 342 if _auth_token:
343 343 # when using API_KEY we are sure user exists.
344 344 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
345 345 authenticated = False
346 346 else:
347 347 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
348 348 try:
349 349 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
350 350 ip_addr=ip_addr)
351 351 except UserCreationError as e:
352 352 h.flash(e, 'error')
353 353 # container auth or other auth functions that create users
354 354 # on the fly can throw this exception signaling that there's
355 355 # issue with user creation, explanation should be provided
356 356 # in Exception itself. We then create a simple blank
357 357 # AuthUser
358 358 auth_user = AuthUser(ip_addr=ip_addr)
359 359
360 360 if password_changed(auth_user, session):
361 361 session.invalidate()
362 362 cookie_store = CookieStoreWrapper(
363 363 session.get('rhodecode_user'))
364 364 auth_user = AuthUser(ip_addr=ip_addr)
365 365
366 366 authenticated = cookie_store.get('is_authenticated')
367 367
368 368 if not auth_user.is_authenticated and auth_user.is_user_object:
369 369 # user is not authenticated and not empty
370 370 auth_user.set_authenticated(authenticated)
371 371
372 372 return auth_user
373 373
374 374
375 375 class BaseController(WSGIController):
376 376
377 377 def __before__(self):
378 378 """
379 379 __before__ is called before controller methods and after __call__
380 380 """
381 381 # on each call propagate settings calls into global settings.
382 382 set_rhodecode_config(config)
383 383 attach_context_attributes(c)
384 384
385 385 # TODO: Remove this when fixed in attach_context_attributes()
386 386 c.repo_name = get_repo_slug(request) # can be empty
387 387
388 388 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
389 389 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
390 390 self.sa = meta.Session
391 391 self.scm_model = ScmModel(self.sa)
392 392
393 393 default_lang = c.language
394 394 user_lang = c.language
395 395 try:
396 396 user_obj = self._rhodecode_user.get_instance()
397 397 if user_obj:
398 398 user_lang = user_obj.user_data.get('language')
399 399 except Exception:
400 400 log.exception('Failed to fetch user language for user %s',
401 401 self._rhodecode_user)
402 402
403 403 if user_lang and user_lang != default_lang:
404 404 log.debug('set language to %s for user %s', user_lang,
405 405 self._rhodecode_user)
406 406 translation.set_lang(user_lang)
407 407
408 408 def _dispatch_redirect(self, with_url, environ, start_response):
409 409 resp = HTTPFound(with_url)
410 410 environ['SCRIPT_NAME'] = '' # handle prefix middleware
411 411 environ['PATH_INFO'] = with_url
412 412 return resp(environ, start_response)
413 413
414 414 def __call__(self, environ, start_response):
415 415 """Invoke the Controller"""
416 416 # WSGIController.__call__ dispatches to the Controller method
417 417 # the request is routed to. This routing information is
418 418 # available in environ['pylons.routes_dict']
419 419 from rhodecode.lib import helpers as h
420 420
421 421 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
422 422 if environ.get('debugtoolbar.wants_pylons_context', False):
423 423 environ['debugtoolbar.pylons_context'] = c._current_obj()
424 424
425 425 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
426 426 environ['pylons.routes_dict']['action']])
427 427
428 self.rc_config = SettingsModel().get_all_settings()
428 self.rc_config = SettingsModel().get_all_settings(cache=True)
429 429 self.ip_addr = get_ip_addr(environ)
430 430
431 431 # The rhodecode auth user is looked up and passed through the
432 432 # environ by the pylons compatibility tween in pyramid.
433 433 # So we can just grab it from there.
434 434 auth_user = environ['rc_auth_user']
435 435
436 436 # set globals for auth user
437 437 request.user = auth_user
438 438 c.rhodecode_user = self._rhodecode_user = auth_user
439 439
440 440 log.info('IP: %s User: %s accessed %s [%s]' % (
441 441 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
442 442 _route_name)
443 443 )
444 444
445 445 # TODO: Maybe this should be move to pyramid to cover all views.
446 446 # check user attributes for password change flag
447 447 user_obj = auth_user.get_instance()
448 448 if user_obj and user_obj.user_data.get('force_password_change'):
449 449 h.flash('You are required to change your password', 'warning',
450 450 ignore_duplicate=True)
451 451
452 452 skip_user_check_urls = [
453 453 'error.document', 'login.logout', 'login.index',
454 454 'admin/my_account.my_account_password',
455 455 'admin/my_account.my_account_password_update'
456 456 ]
457 457 if _route_name not in skip_user_check_urls:
458 458 return self._dispatch_redirect(
459 459 url('my_account_password'), environ, start_response)
460 460
461 461 return WSGIController.__call__(self, environ, start_response)
462 462
463 463
464 464 class BaseRepoController(BaseController):
465 465 """
466 466 Base class for controllers responsible for loading all needed data for
467 467 repository loaded items are
468 468
469 469 c.rhodecode_repo: instance of scm repository
470 470 c.rhodecode_db_repo: instance of db
471 471 c.repository_requirements_missing: shows that repository specific data
472 472 could not be displayed due to the missing requirements
473 473 c.repository_pull_requests: show number of open pull requests
474 474 """
475 475
476 476 def __before__(self):
477 477 super(BaseRepoController, self).__before__()
478 478 if c.repo_name: # extracted from routes
479 479 db_repo = Repository.get_by_repo_name(c.repo_name)
480 480 if not db_repo:
481 481 return
482 482
483 483 log.debug(
484 484 'Found repository in database %s with state `%s`',
485 485 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
486 486 route = getattr(request.environ.get('routes.route'), 'name', '')
487 487
488 488 # allow to delete repos that are somehow damages in filesystem
489 489 if route in ['delete_repo']:
490 490 return
491 491
492 492 if db_repo.repo_state in [Repository.STATE_PENDING]:
493 493 if route in ['repo_creating_home']:
494 494 return
495 495 check_url = url('repo_creating_home', repo_name=c.repo_name)
496 496 return redirect(check_url)
497 497
498 498 self.rhodecode_db_repo = db_repo
499 499
500 500 missing_requirements = False
501 501 try:
502 502 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
503 503 except RepositoryRequirementError as e:
504 504 missing_requirements = True
505 505 self._handle_missing_requirements(e)
506 506
507 507 if self.rhodecode_repo is None and not missing_requirements:
508 508 log.error('%s this repository is present in database but it '
509 509 'cannot be created as an scm instance', c.repo_name)
510 510
511 511 h.flash(_(
512 512 "The repository at %(repo_name)s cannot be located.") %
513 513 {'repo_name': c.repo_name},
514 514 category='error', ignore_duplicate=True)
515 515 redirect(url('home'))
516 516
517 517 # update last change according to VCS data
518 518 if not missing_requirements:
519 519 commit = db_repo.get_commit(
520 520 pre_load=["author", "date", "message", "parents"])
521 521 db_repo.update_commit_cache(commit)
522 522
523 523 # Prepare context
524 524 c.rhodecode_db_repo = db_repo
525 525 c.rhodecode_repo = self.rhodecode_repo
526 526 c.repository_requirements_missing = missing_requirements
527 527
528 528 self._update_global_counters(self.scm_model, db_repo)
529 529
530 530 def _update_global_counters(self, scm_model, db_repo):
531 531 """
532 532 Base variables that are exposed to every page of repository
533 533 """
534 534 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
535 535
536 536 def _handle_missing_requirements(self, error):
537 537 self.rhodecode_repo = None
538 538 log.error(
539 539 'Requirements are missing for repository %s: %s',
540 540 c.repo_name, error.message)
541 541
542 542 summary_url = url('summary_home', repo_name=c.repo_name)
543 543 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
544 544 settings_update_url = url('repo', repo_name=c.repo_name)
545 545 path = request.path
546 546 should_redirect = (
547 547 path not in (summary_url, settings_update_url)
548 548 and '/settings' not in path or path == statistics_url
549 549 )
550 550 if should_redirect:
551 551 redirect(summary_url)
@@ -1,220 +1,226 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2015-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import beaker
23 23 import logging
24 24
25 25 from beaker.cache import _cache_decorate, cache_regions, region_invalidate
26 26
27 27 from rhodecode.lib.utils import safe_str, md5
28 28 from rhodecode.model.db import Session, CacheKey, IntegrityError
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32 FILE_TREE = 'cache_file_tree'
33 33 FILE_TREE_META = 'cache_file_tree_metadata'
34 34 FILE_SEARCH_TREE_META = 'cache_file_search_metadata'
35 35 SUMMARY_STATS = 'cache_summary_stats'
36 36
37 37 # This list of caches gets purged when invalidation happens
38 38 USED_REPO_CACHES = (FILE_TREE, FILE_TREE_META, FILE_TREE_META)
39 39
40 40 DEFAULT_CACHE_MANAGER_CONFIG = {
41 41 'type': 'memorylru_base',
42 42 'max_items': 10240,
43 43 'key_length': 256,
44 44 'enabled': True
45 45 }
46 46
47 47
48 48 def configure_cache_region(
49 49 region_name, region_kw, default_cache_kw, default_expire=60):
50 50 default_type = default_cache_kw.get('type', 'memory')
51 51 default_lock_dir = default_cache_kw.get('lock_dir')
52 52 default_data_dir = default_cache_kw.get('data_dir')
53 53
54 54 region_kw['lock_dir'] = region_kw.get('lock_dir', default_lock_dir)
55 55 region_kw['data_dir'] = region_kw.get('data_dir', default_data_dir)
56 56 region_kw['type'] = region_kw.get('type', default_type)
57 57 region_kw['expire'] = int(region_kw.get('expire', default_expire))
58 58
59 59 beaker.cache.cache_regions[region_name] = region_kw
60 60
61 61
62 62 def get_cache_manager(region_name, cache_name, custom_ttl=None):
63 63 """
64 64 Creates a Beaker cache manager. Such instance can be used like that::
65 65
66 66 _namespace = caches.get_repo_namespace_key(caches.XXX, repo_name)
67 67 cache_manager = caches.get_cache_manager('repo_cache_long', _namespace)
68 68 _cache_key = caches.compute_key_from_params(repo_name, commit.raw_id)
69 69 def heavy_compute():
70 70 ...
71 71 result = cache_manager.get(_cache_key, createfunc=heavy_compute)
72 72
73 73 :param region_name: region from ini file
74 74 :param cache_name: custom cache name, usually prefix+repo_name. eg
75 75 file_switcher_repo1
76 76 :param custom_ttl: override .ini file timeout on this cache
77 77 :return: instance of cache manager
78 78 """
79 79
80 80 cache_config = cache_regions.get(region_name, DEFAULT_CACHE_MANAGER_CONFIG)
81 81 if custom_ttl:
82 82 log.debug('Updating region %s with custom ttl: %s',
83 83 region_name, custom_ttl)
84 84 cache_config.update({'expire': custom_ttl})
85 85
86 86 return beaker.cache.Cache._get_cache(cache_name, cache_config)
87 87
88 88
89 89 def clear_cache_manager(cache_manager):
90 """
91 namespace = 'foobar'
92 cache_manager = get_cache_manager('repo_cache_long', namespace)
93 clear_cache_manager(cache_manager)
94 """
95
90 96 log.debug('Clearing all values for cache manager %s', cache_manager)
91 97 cache_manager.clear()
92 98
93 99
94 100 def clear_repo_caches(repo_name):
95 101 # invalidate cache manager for this repo
96 102 for prefix in USED_REPO_CACHES:
97 103 namespace = get_repo_namespace_key(prefix, repo_name)
98 104 cache_manager = get_cache_manager('repo_cache_long', namespace)
99 105 clear_cache_manager(cache_manager)
100 106
101 107
102 108 def compute_key_from_params(*args):
103 109 """
104 110 Helper to compute key from given params to be used in cache manager
105 111 """
106 112 return md5("_".join(map(safe_str, args)))
107 113
108 114
109 115 def get_repo_namespace_key(prefix, repo_name):
110 116 return '{0}_{1}'.format(prefix, compute_key_from_params(repo_name))
111 117
112 118
113 119 def conditional_cache(region, prefix, condition, func):
114 120 """
115 121 Conditional caching function use like::
116 122 def _c(arg):
117 123 # heavy computation function
118 124 return data
119 125
120 126 # depending on the condition the compute is wrapped in cache or not
121 127 compute = conditional_cache('short_term', 'cache_desc',
122 128 condition=True, func=func)
123 129 return compute(arg)
124 130
125 131 :param region: name of cache region
126 132 :param prefix: cache region prefix
127 133 :param condition: condition for cache to be triggered, and
128 134 return data cached
129 135 :param func: wrapped heavy function to compute
130 136
131 137 """
132 138 wrapped = func
133 139 if condition:
134 140 log.debug('conditional_cache: True, wrapping call of '
135 141 'func: %s into %s region cache', region, func)
136 142 cached_region = _cache_decorate((prefix,), None, None, region)
137 143 wrapped = cached_region(func)
138 144 return wrapped
139 145
140 146
141 147 class ActiveRegionCache(object):
142 148 def __init__(self, context):
143 149 self.context = context
144 150
145 151 def invalidate(self, *args, **kwargs):
146 152 return False
147 153
148 154 def compute(self):
149 155 log.debug('Context cache: getting obj %s from cache', self.context)
150 156 return self.context.compute_func(self.context.cache_key)
151 157
152 158
153 159 class FreshRegionCache(ActiveRegionCache):
154 160 def invalidate(self):
155 161 log.debug('Context cache: invalidating cache for %s', self.context)
156 162 region_invalidate(
157 163 self.context.compute_func, None, self.context.cache_key)
158 164 return True
159 165
160 166
161 167 class InvalidationContext(object):
162 168 def __repr__(self):
163 169 return '<InvalidationContext:{}[{}]>'.format(
164 self.repo_name, self.cache_type)
170 safe_str(self.repo_name), safe_str(self.cache_type))
165 171
166 172 def __init__(self, compute_func, repo_name, cache_type,
167 173 raise_exception=False):
168 174 self.compute_func = compute_func
169 175 self.repo_name = repo_name
170 176 self.cache_type = cache_type
171 177 self.cache_key = compute_key_from_params(
172 178 repo_name, cache_type)
173 179 self.raise_exception = raise_exception
174 180
175 181 def get_cache_obj(self):
176 182 cache_key = CacheKey.get_cache_key(
177 183 self.repo_name, self.cache_type)
178 184 cache_obj = CacheKey.get_active_cache(cache_key)
179 185 if not cache_obj:
180 186 cache_obj = CacheKey(cache_key, self.repo_name)
181 187 return cache_obj
182 188
183 189 def __enter__(self):
184 190 """
185 191 Test if current object is valid, and return CacheRegion function
186 192 that does invalidation and calculation
187 193 """
188 194
189 195 self.cache_obj = self.get_cache_obj()
190 196 if self.cache_obj.cache_active:
191 197 # means our cache obj is existing and marked as it's
192 198 # cache is not outdated, we return BaseInvalidator
193 199 self.skip_cache_active_change = True
194 200 return ActiveRegionCache(self)
195 201
196 202 # the key is either not existing or set to False, we return
197 203 # the real invalidator which re-computes value. We additionally set
198 204 # the flag to actually update the Database objects
199 205 self.skip_cache_active_change = False
200 206 return FreshRegionCache(self)
201 207
202 208 def __exit__(self, exc_type, exc_val, exc_tb):
203 209
204 210 if self.skip_cache_active_change:
205 211 return
206 212
207 213 try:
208 214 self.cache_obj.cache_active = True
209 215 Session().add(self.cache_obj)
210 216 Session().commit()
211 217 except IntegrityError:
212 218 # if we catch integrity error, it means we inserted this object
213 219 # assumption is that's really an edge race-condition case and
214 220 # it's safe is to skip it
215 221 Session().rollback()
216 222 except Exception:
217 223 log.exception('Failed to commit on cache key update')
218 224 Session().rollback()
219 225 if self.raise_exception:
220 226 raise
@@ -1,143 +1,141 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 """
21 21 celery libs for RhodeCode
22 22 """
23 23
24 24
25 25 import socket
26 26 import logging
27 27
28 28 import rhodecode
29 29
30 30 from os.path import join as jn
31 31 from pylons import config
32 32
33 33 from decorator import decorator
34 34
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36
37 from rhodecode import CELERY_ENABLED, CELERY_EAGER
38 37 from rhodecode.config import utils
39 38 from rhodecode.lib.utils2 import safe_str, md5_safe, aslist
40 39 from rhodecode.lib.pidlock import DaemonLock, LockHeld
41 40 from rhodecode.lib.vcs import connect_vcs
42 41 from rhodecode.model import meta
43 42
44 43 log = logging.getLogger(__name__)
45 44
46 45
47 46 class ResultWrapper(object):
48 47 def __init__(self, task):
49 48 self.task = task
50 49
51 50 @LazyProperty
52 51 def result(self):
53 52 return self.task
54 53
55 54
56 55 def run_task(task, *args, **kwargs):
57 global CELERY_ENABLED
58 if CELERY_ENABLED:
56 if rhodecode.CELERY_ENABLED:
59 57 try:
60 58 t = task.apply_async(args=args, kwargs=kwargs)
61 59 log.info('running task %s:%s', t.task_id, task)
62 60 return t
63 61
64 62 except socket.error as e:
65 63 if isinstance(e, IOError) and e.errno == 111:
66 log.debug('Unable to connect to celeryd. Sync execution')
67 CELERY_ENABLED = False
64 log.error('Unable to connect to celeryd. Sync execution')
65 rhodecode.CELERY_ENABLED = False
68 66 else:
69 67 log.exception("Exception while connecting to celeryd.")
70 68 except KeyError as e:
71 log.debug('Unable to connect to celeryd. Sync execution')
69 log.error('Unable to connect to celeryd. Sync execution')
72 70 except Exception as e:
73 71 log.exception(
74 72 "Exception while trying to run task asynchronous. "
75 73 "Fallback to sync execution.")
76
77 log.debug('executing task %s in sync mode', task)
74 else:
75 log.debug('executing task %s in sync mode', task)
78 76 return ResultWrapper(task(*args, **kwargs))
79 77
80 78
81 79 def __get_lockkey(func, *fargs, **fkwargs):
82 80 params = list(fargs)
83 81 params.extend(['%s-%s' % ar for ar in fkwargs.items()])
84 82
85 83 func_name = str(func.__name__) if hasattr(func, '__name__') else str(func)
86 84 _lock_key = func_name + '-' + '-'.join(map(safe_str, params))
87 85 return 'task_%s.lock' % (md5_safe(_lock_key),)
88 86
89 87
90 88 def locked_task(func):
91 89 def __wrapper(func, *fargs, **fkwargs):
92 90 lockkey = __get_lockkey(func, *fargs, **fkwargs)
93 91 lockkey_path = config['app_conf']['cache_dir']
94 92
95 93 log.info('running task with lockkey %s' % lockkey)
96 94 try:
97 95 l = DaemonLock(file_=jn(lockkey_path, lockkey))
98 96 ret = func(*fargs, **fkwargs)
99 97 l.release()
100 98 return ret
101 99 except LockHeld:
102 100 log.info('LockHeld')
103 101 return 'Task with key %s already running' % lockkey
104 102
105 103 return decorator(__wrapper, func)
106 104
107 105
108 106 def get_session():
109 if CELERY_ENABLED:
107 if rhodecode.CELERY_ENABLED:
110 108 utils.initialize_database(config)
111 109 sa = meta.Session()
112 110 return sa
113 111
114 112
115 113 def dbsession(func):
116 114 def __wrapper(func, *fargs, **fkwargs):
117 115 try:
118 116 ret = func(*fargs, **fkwargs)
119 117 return ret
120 118 finally:
121 if CELERY_ENABLED and not CELERY_EAGER:
119 if rhodecode.CELERY_ENABLED and not rhodecode.CELERY_EAGER:
122 120 meta.Session.remove()
123 121
124 122 return decorator(__wrapper, func)
125 123
126 124
127 125 def vcsconnection(func):
128 126 def __wrapper(func, *fargs, **fkwargs):
129 if CELERY_ENABLED and not CELERY_EAGER:
127 if rhodecode.CELERY_ENABLED and not rhodecode.CELERY_EAGER:
130 128 backends = config['vcs.backends'] = aslist(
131 129 config.get('vcs.backends', 'hg,git'), sep=',')
132 130 for alias in rhodecode.BACKENDS.keys():
133 131 if alias not in backends:
134 132 del rhodecode.BACKENDS[alias]
135 133 utils.configure_pyro4(config)
136 134 utils.configure_vcs(config)
137 135 connect_vcs(
138 136 config['vcs.server'],
139 137 utils.get_vcs_server_protocol(config))
140 138 ret = func(*fargs, **fkwargs)
141 139 return ret
142 140
143 141 return decorator(__wrapper, func)
@@ -1,284 +1,284 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode task modules, containing all task that suppose to be run
23 23 by celery daemon
24 24 """
25 25
26 26
27 27 import os
28 28 import logging
29 29
30 30 from celery.task import task
31 31 from pylons import config
32 32
33 from rhodecode import CELERY_ENABLED
33 import rhodecode
34 34 from rhodecode.lib.celerylib import (
35 35 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 36 get_session, vcsconnection)
37 37 from rhodecode.lib.hooks_base import log_create_repository
38 38 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
39 39 from rhodecode.lib.utils import add_cache, action_logger
40 40 from rhodecode.lib.utils2 import safe_int, str2bool
41 41 from rhodecode.model.db import Repository, User
42 42
43 43
44 44 add_cache(config) # pragma: no cover
45 45
46 46
47 47 def get_logger(cls):
48 if CELERY_ENABLED:
48 if rhodecode.CELERY_ENABLED:
49 49 try:
50 50 log = cls.get_logger()
51 51 except Exception:
52 52 log = logging.getLogger(__name__)
53 53 else:
54 54 log = logging.getLogger(__name__)
55 55
56 56 return log
57 57
58 58
59 59 @task(ignore_result=True)
60 60 @dbsession
61 61 def send_email(recipients, subject, body='', html_body='', email_config=None):
62 62 """
63 63 Sends an email with defined parameters from the .ini files.
64 64
65 65 :param recipients: list of recipients, it this is empty the defined email
66 66 address from field 'email_to' is used instead
67 67 :param subject: subject of the mail
68 68 :param body: body of the mail
69 69 :param html_body: html version of body
70 70 """
71 71 log = get_logger(send_email)
72 72
73 73 email_config = email_config or config
74 74 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
75 75 if not recipients:
76 76 # if recipients are not defined we send to email_config + all admins
77 77 admins = [
78 78 u.email for u in User.query().filter(User.admin == True).all()]
79 79 recipients = [email_config.get('email_to')] + admins
80 80
81 81 mail_server = email_config.get('smtp_server') or None
82 82 if mail_server is None:
83 83 log.error("SMTP server information missing. Sending email failed. "
84 84 "Make sure that `smtp_server` variable is configured "
85 85 "inside the .ini file")
86 86 return False
87 87
88 88 mail_from = email_config.get('app_email_from', 'RhodeCode')
89 89 user = email_config.get('smtp_username')
90 90 passwd = email_config.get('smtp_password')
91 91 mail_port = email_config.get('smtp_port')
92 92 tls = str2bool(email_config.get('smtp_use_tls'))
93 93 ssl = str2bool(email_config.get('smtp_use_ssl'))
94 94 debug = str2bool(email_config.get('debug'))
95 95 smtp_auth = email_config.get('smtp_auth')
96 96
97 97 try:
98 98 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
99 99 mail_port, ssl, tls, debug=debug)
100 100 m.send(recipients, subject, body, html_body)
101 101 except Exception:
102 102 log.exception('Mail sending failed')
103 103 return False
104 104 return True
105 105
106 106
107 107 @task(ignore_result=False)
108 108 @dbsession
109 109 @vcsconnection
110 110 def create_repo(form_data, cur_user):
111 111 from rhodecode.model.repo import RepoModel
112 112 from rhodecode.model.user import UserModel
113 113 from rhodecode.model.settings import SettingsModel
114 114
115 115 log = get_logger(create_repo)
116 116 DBS = get_session()
117 117
118 118 cur_user = UserModel(DBS)._get_user(cur_user)
119 119 owner = cur_user
120 120
121 121 repo_name = form_data['repo_name']
122 122 repo_name_full = form_data['repo_name_full']
123 123 repo_type = form_data['repo_type']
124 124 description = form_data['repo_description']
125 125 private = form_data['repo_private']
126 126 clone_uri = form_data.get('clone_uri')
127 127 repo_group = safe_int(form_data['repo_group'])
128 128 landing_rev = form_data['repo_landing_rev']
129 129 copy_fork_permissions = form_data.get('copy_permissions')
130 130 copy_group_permissions = form_data.get('repo_copy_permissions')
131 131 fork_of = form_data.get('fork_parent_id')
132 132 state = form_data.get('repo_state', Repository.STATE_PENDING)
133 133
134 134 # repo creation defaults, private and repo_type are filled in form
135 135 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
136 136 enable_statistics = form_data.get(
137 137 'enable_statistics', defs.get('repo_enable_statistics'))
138 138 enable_locking = form_data.get(
139 139 'enable_locking', defs.get('repo_enable_locking'))
140 140 enable_downloads = form_data.get(
141 141 'enable_downloads', defs.get('repo_enable_downloads'))
142 142
143 143 try:
144 144 RepoModel(DBS)._create_repo(
145 145 repo_name=repo_name_full,
146 146 repo_type=repo_type,
147 147 description=description,
148 148 owner=owner,
149 149 private=private,
150 150 clone_uri=clone_uri,
151 151 repo_group=repo_group,
152 152 landing_rev=landing_rev,
153 153 fork_of=fork_of,
154 154 copy_fork_permissions=copy_fork_permissions,
155 155 copy_group_permissions=copy_group_permissions,
156 156 enable_statistics=enable_statistics,
157 157 enable_locking=enable_locking,
158 158 enable_downloads=enable_downloads,
159 159 state=state
160 160 )
161 161
162 162 action_logger(cur_user, 'user_created_repo',
163 163 repo_name_full, '', DBS)
164 164 DBS.commit()
165 165
166 166 # now create this repo on Filesystem
167 167 RepoModel(DBS)._create_filesystem_repo(
168 168 repo_name=repo_name,
169 169 repo_type=repo_type,
170 170 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
171 171 clone_uri=clone_uri,
172 172 )
173 173 repo = Repository.get_by_repo_name(repo_name_full)
174 174 log_create_repository(created_by=owner.username, **repo.get_dict())
175 175
176 176 # update repo commit caches initially
177 177 repo.update_commit_cache()
178 178
179 179 # set new created state
180 180 repo.set_state(Repository.STATE_CREATED)
181 181 DBS.commit()
182 182 except Exception as e:
183 183 log.warning('Exception %s occurred when creating repository, '
184 184 'doing cleanup...', e)
185 185 # rollback things manually !
186 186 repo = Repository.get_by_repo_name(repo_name_full)
187 187 if repo:
188 188 Repository.delete(repo.repo_id)
189 189 DBS.commit()
190 190 RepoModel(DBS)._delete_filesystem_repo(repo)
191 191 raise
192 192
193 193 # it's an odd fix to make celery fail task when exception occurs
194 194 def on_failure(self, *args, **kwargs):
195 195 pass
196 196
197 197 return True
198 198
199 199
200 200 @task(ignore_result=False)
201 201 @dbsession
202 202 @vcsconnection
203 203 def create_repo_fork(form_data, cur_user):
204 204 """
205 205 Creates a fork of repository using internal VCS methods
206 206
207 207 :param form_data:
208 208 :param cur_user:
209 209 """
210 210 from rhodecode.model.repo import RepoModel
211 211 from rhodecode.model.user import UserModel
212 212
213 213 log = get_logger(create_repo_fork)
214 214 DBS = get_session()
215 215
216 216 cur_user = UserModel(DBS)._get_user(cur_user)
217 217 owner = cur_user
218 218
219 219 repo_name = form_data['repo_name'] # fork in this case
220 220 repo_name_full = form_data['repo_name_full']
221 221 repo_type = form_data['repo_type']
222 222 description = form_data['description']
223 223 private = form_data['private']
224 224 clone_uri = form_data.get('clone_uri')
225 225 repo_group = safe_int(form_data['repo_group'])
226 226 landing_rev = form_data['landing_rev']
227 227 copy_fork_permissions = form_data.get('copy_permissions')
228 228 fork_id = safe_int(form_data.get('fork_parent_id'))
229 229
230 230 try:
231 231 fork_of = RepoModel(DBS)._get_repo(fork_id)
232 232 RepoModel(DBS)._create_repo(
233 233 repo_name=repo_name_full,
234 234 repo_type=repo_type,
235 235 description=description,
236 236 owner=owner,
237 237 private=private,
238 238 clone_uri=clone_uri,
239 239 repo_group=repo_group,
240 240 landing_rev=landing_rev,
241 241 fork_of=fork_of,
242 242 copy_fork_permissions=copy_fork_permissions
243 243 )
244 244 action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
245 245 fork_of.repo_name, '', DBS)
246 246 DBS.commit()
247 247
248 248 base_path = Repository.base_path()
249 249 source_repo_path = os.path.join(base_path, fork_of.repo_name)
250 250
251 251 # now create this repo on Filesystem
252 252 RepoModel(DBS)._create_filesystem_repo(
253 253 repo_name=repo_name,
254 254 repo_type=repo_type,
255 255 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
256 256 clone_uri=source_repo_path,
257 257 )
258 258 repo = Repository.get_by_repo_name(repo_name_full)
259 259 log_create_repository(created_by=owner.username, **repo.get_dict())
260 260
261 261 # update repo commit caches initially
262 262 config = repo._config
263 263 config.set('extensions', 'largefiles', '')
264 264 repo.update_commit_cache(config=config)
265 265
266 266 # set new created state
267 267 repo.set_state(Repository.STATE_CREATED)
268 268 DBS.commit()
269 269 except Exception as e:
270 270 log.warning('Exception %s occurred when forking repository, '
271 271 'doing cleanup...', e)
272 272 # rollback things manually !
273 273 repo = Repository.get_by_repo_name(repo_name_full)
274 274 if repo:
275 275 Repository.delete(repo.repo_id)
276 276 DBS.commit()
277 277 RepoModel(DBS)._delete_filesystem_repo(repo)
278 278 raise
279 279
280 280 # it's an odd fix to make celery fail task when exception occurs
281 281 def on_failure(self, *args, **kwargs):
282 282 pass
283 283
284 284 return True
@@ -1,597 +1,597 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args={}):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.db_exists = False
70 70 self.cli_args = cli_args
71 71 self.init_db(SESSION=SESSION)
72 72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73 73
74 74 def get_ask_ok_func(self, param):
75 75 if param not in [None]:
76 76 # return a function lambda that has a default set to param
77 77 return lambda *args, **kwargs: param
78 78 else:
79 79 from rhodecode.lib.utils import ask_ok
80 80 return ask_ok
81 81
82 82 def init_db(self, SESSION=None):
83 83 if SESSION:
84 84 self.sa = SESSION
85 85 else:
86 86 # init new sessions
87 87 engine = create_engine(self.dburi, echo=self.log_sql)
88 88 init_model(engine)
89 89 self.sa = Session()
90 90
91 91 def create_tables(self, override=False):
92 92 """
93 93 Create a auth database
94 94 """
95 95
96 96 log.info("Existing database with the same name is going to be destroyed.")
97 97 log.info("Setup command will run DROP ALL command on that database.")
98 98 if self.tests:
99 99 destroy = True
100 100 else:
101 101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 102 if not destroy:
103 103 log.info('Nothing done.')
104 104 sys.exit(0)
105 105 if destroy:
106 106 Base.metadata.drop_all()
107 107
108 108 checkfirst = not override
109 109 Base.metadata.create_all(checkfirst=checkfirst)
110 110 log.info('Created tables for %s' % self.dbname)
111 111
112 112 def set_db_version(self):
113 113 ver = DbMigrateVersion()
114 114 ver.version = __dbversion__
115 115 ver.repository_id = 'rhodecode_db_migrations'
116 116 ver.repository_path = 'versions'
117 117 self.sa.add(ver)
118 118 log.info('db version set to: %s' % __dbversion__)
119 119
120 120 def run_pre_migration_tasks(self):
121 121 """
122 122 Run various tasks before actually doing migrations
123 123 """
124 124 # delete cache keys on each upgrade
125 125 total = CacheKey.query().count()
126 126 log.info("Deleting (%s) cache keys now...", total)
127 127 CacheKey.delete_all_cache()
128 128
129 129 def upgrade(self):
130 130 """
131 131 Upgrades given database schema to given revision following
132 132 all needed steps, to perform the upgrade
133 133
134 134 """
135 135
136 136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 138 DatabaseNotControlledError
139 139
140 140 if 'sqlite' in self.dburi:
141 141 print (
142 142 '********************** WARNING **********************\n'
143 143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 144 'Earlier versions are known to fail on some migrations\n'
145 145 '*****************************************************\n')
146 146
147 147 upgrade = self.ask_ok(
148 148 'You are about to perform a database upgrade. Make '
149 149 'sure you have backed up your database. '
150 150 'Continue ? [y/n]')
151 151 if not upgrade:
152 152 log.info('No upgrade performed')
153 153 sys.exit(0)
154 154
155 155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 156 'rhodecode/lib/dbmigrate')
157 157 db_uri = self.dburi
158 158
159 159 try:
160 160 curr_version = api.db_version(db_uri, repository_path)
161 161 msg = ('Found current database under version '
162 162 'control with version %s' % curr_version)
163 163
164 164 except (RuntimeError, DatabaseNotControlledError):
165 165 curr_version = 1
166 166 msg = ('Current database is not under version control. Setting '
167 167 'as version %s' % curr_version)
168 168 api.version_control(db_uri, repository_path, curr_version)
169 169
170 170 notify(msg)
171 171
172 172 self.run_pre_migration_tasks()
173 173
174 174 if curr_version == __dbversion__:
175 175 log.info('This database is already at the newest version')
176 176 sys.exit(0)
177 177
178 178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 179 notify('attempting to upgrade database from '
180 180 'version %s to version %s' % (curr_version, __dbversion__))
181 181
182 182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 183 _step = None
184 184 for step in upgrade_steps:
185 185 notify('performing upgrade step %s' % step)
186 186 time.sleep(0.5)
187 187
188 188 api.upgrade(db_uri, repository_path, step)
189 189 self.sa.rollback()
190 190 notify('schema upgrade for step %s completed' % (step,))
191 191
192 192 _step = step
193 193
194 194 notify('upgrade to version %s successful' % _step)
195 195
196 196 def fix_repo_paths(self):
197 197 """
198 198 Fixes an old RhodeCode version path into new one without a '*'
199 199 """
200 200
201 201 paths = self.sa.query(RhodeCodeUi)\
202 202 .filter(RhodeCodeUi.ui_key == '/')\
203 203 .scalar()
204 204
205 205 paths.ui_value = paths.ui_value.replace('*', '')
206 206
207 207 try:
208 208 self.sa.add(paths)
209 209 self.sa.commit()
210 210 except Exception:
211 211 self.sa.rollback()
212 212 raise
213 213
214 214 def fix_default_user(self):
215 215 """
216 216 Fixes an old default user with some 'nicer' default values,
217 217 used mostly for anonymous access
218 218 """
219 219 def_user = self.sa.query(User)\
220 220 .filter(User.username == User.DEFAULT_USER)\
221 221 .one()
222 222
223 223 def_user.name = 'Anonymous'
224 224 def_user.lastname = 'User'
225 225 def_user.email = User.DEFAULT_USER_EMAIL
226 226
227 227 try:
228 228 self.sa.add(def_user)
229 229 self.sa.commit()
230 230 except Exception:
231 231 self.sa.rollback()
232 232 raise
233 233
234 234 def fix_settings(self):
235 235 """
236 236 Fixes rhodecode settings and adds ga_code key for google analytics
237 237 """
238 238
239 239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240 240
241 241 try:
242 242 self.sa.add(hgsettings3)
243 243 self.sa.commit()
244 244 except Exception:
245 245 self.sa.rollback()
246 246 raise
247 247
248 248 def create_admin_and_prompt(self):
249 249
250 250 # defaults
251 251 defaults = self.cli_args
252 252 username = defaults.get('username')
253 253 password = defaults.get('password')
254 254 email = defaults.get('email')
255 255
256 256 if username is None:
257 257 username = raw_input('Specify admin username:')
258 258 if password is None:
259 259 password = self._get_admin_password()
260 260 if not password:
261 261 # second try
262 262 password = self._get_admin_password()
263 263 if not password:
264 264 sys.exit()
265 265 if email is None:
266 266 email = raw_input('Specify admin email:')
267 267 api_key = self.cli_args.get('api_key')
268 268 self.create_user(username, password, email, True,
269 269 strict_creation_check=False,
270 270 api_key=api_key)
271 271
272 272 def _get_admin_password(self):
273 273 password = getpass.getpass('Specify admin password '
274 274 '(min 6 chars):')
275 275 confirm = getpass.getpass('Confirm password:')
276 276
277 277 if password != confirm:
278 278 log.error('passwords mismatch')
279 279 return False
280 280 if len(password) < 6:
281 281 log.error('password is too short - use at least 6 characters')
282 282 return False
283 283
284 284 return password
285 285
286 286 def create_test_admin_and_users(self):
287 287 log.info('creating admin and regular test users')
288 288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293 293
294 294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 295 TEST_USER_ADMIN_EMAIL, True)
296 296
297 297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 298 TEST_USER_REGULAR_EMAIL, False)
299 299
300 300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 301 TEST_USER_REGULAR2_EMAIL, False)
302 302
303 303 def create_ui_settings(self, repo_store_path):
304 304 """
305 305 Creates ui settings, fills out hooks
306 306 and disables dotencode
307 307 """
308 308 settings_model = SettingsModel(sa=self.sa)
309 309
310 310 # Build HOOKS
311 311 hooks = [
312 312 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
313 313
314 314 # HG
315 315 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
316 316 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
317 317 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
318 318 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
319 319
320 320 ]
321 321
322 322 for key, value in hooks:
323 323 hook_obj = settings_model.get_ui_by_key(key)
324 324 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
325 325 hooks2.ui_section = 'hooks'
326 326 hooks2.ui_key = key
327 327 hooks2.ui_value = value
328 328 self.sa.add(hooks2)
329 329
330 330 # enable largefiles
331 331 largefiles = RhodeCodeUi()
332 332 largefiles.ui_section = 'extensions'
333 333 largefiles.ui_key = 'largefiles'
334 334 largefiles.ui_value = ''
335 335 self.sa.add(largefiles)
336 336
337 337 # set default largefiles cache dir, defaults to
338 338 # /repo location/.cache/largefiles
339 339 largefiles = RhodeCodeUi()
340 340 largefiles.ui_section = 'largefiles'
341 341 largefiles.ui_key = 'usercache'
342 342 largefiles.ui_value = os.path.join(repo_store_path, '.cache',
343 343 'largefiles')
344 344 self.sa.add(largefiles)
345 345
346 346 # enable hgsubversion disabled by default
347 347 hgsubversion = RhodeCodeUi()
348 348 hgsubversion.ui_section = 'extensions'
349 349 hgsubversion.ui_key = 'hgsubversion'
350 350 hgsubversion.ui_value = ''
351 351 hgsubversion.ui_active = False
352 352 self.sa.add(hgsubversion)
353 353
354 354 # enable hggit disabled by default
355 355 hggit = RhodeCodeUi()
356 356 hggit.ui_section = 'extensions'
357 357 hggit.ui_key = 'hggit'
358 358 hggit.ui_value = ''
359 359 hggit.ui_active = False
360 360 self.sa.add(hggit)
361 361
362 362 # set svn branch defaults
363 363 branches = ["/branches/*", "/trunk"]
364 364 tags = ["/tags/*"]
365 365
366 366 for branch in branches:
367 367 settings_model.create_ui_section_value(
368 368 RhodeCodeUi.SVN_BRANCH_ID, branch)
369 369
370 370 for tag in tags:
371 371 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
372 372
373 373 def create_auth_plugin_options(self, skip_existing=False):
374 374 """
375 375 Create default auth plugin settings, and make it active
376 376
377 377 :param skip_existing:
378 378 """
379 379
380 380 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
381 381 ('auth_rhodecode_enabled', 'True', 'bool')]:
382 382 if (skip_existing and
383 383 SettingsModel().get_setting_by_name(k) is not None):
384 384 log.debug('Skipping option %s' % k)
385 385 continue
386 386 setting = RhodeCodeSetting(k, v, t)
387 387 self.sa.add(setting)
388 388
389 389 def create_default_options(self, skip_existing=False):
390 390 """Creates default settings"""
391 391
392 392 for k, v, t in [
393 393 ('default_repo_enable_locking', False, 'bool'),
394 394 ('default_repo_enable_downloads', False, 'bool'),
395 395 ('default_repo_enable_statistics', False, 'bool'),
396 396 ('default_repo_private', False, 'bool'),
397 397 ('default_repo_type', 'hg', 'unicode')]:
398 398
399 399 if (skip_existing and
400 400 SettingsModel().get_setting_by_name(k) is not None):
401 401 log.debug('Skipping option %s' % k)
402 402 continue
403 403 setting = RhodeCodeSetting(k, v, t)
404 404 self.sa.add(setting)
405 405
406 406 def fixup_groups(self):
407 407 def_usr = User.get_default_user()
408 408 for g in RepoGroup.query().all():
409 409 g.group_name = g.get_new_name(g.name)
410 410 self.sa.add(g)
411 411 # get default perm
412 412 default = UserRepoGroupToPerm.query()\
413 413 .filter(UserRepoGroupToPerm.group == g)\
414 414 .filter(UserRepoGroupToPerm.user == def_usr)\
415 415 .scalar()
416 416
417 417 if default is None:
418 418 log.debug('missing default permission for group %s adding' % g)
419 419 perm_obj = RepoGroupModel()._create_default_perms(g)
420 420 self.sa.add(perm_obj)
421 421
422 422 def reset_permissions(self, username):
423 423 """
424 424 Resets permissions to default state, useful when old systems had
425 425 bad permissions, we must clean them up
426 426
427 427 :param username:
428 428 """
429 429 default_user = User.get_by_username(username)
430 430 if not default_user:
431 431 return
432 432
433 433 u2p = UserToPerm.query()\
434 434 .filter(UserToPerm.user == default_user).all()
435 435 fixed = False
436 436 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
437 437 for p in u2p:
438 438 Session().delete(p)
439 439 fixed = True
440 440 self.populate_default_permissions()
441 441 return fixed
442 442
443 443 def update_repo_info(self):
444 444 RepoModel.update_repoinfo()
445 445
446 446 def config_prompt(self, test_repo_path='', retries=3):
447 447 defaults = self.cli_args
448 448 _path = defaults.get('repos_location')
449 449 if retries == 3:
450 450 log.info('Setting up repositories config')
451 451
452 452 if _path is not None:
453 453 path = _path
454 454 elif not self.tests and not test_repo_path:
455 455 path = raw_input(
456 456 'Enter a valid absolute path to store repositories. '
457 457 'All repositories in that path will be added automatically:'
458 458 )
459 459 else:
460 460 path = test_repo_path
461 461 path_ok = True
462 462
463 463 # check proper dir
464 464 if not os.path.isdir(path):
465 465 path_ok = False
466 466 log.error('Given path %s is not a valid directory' % (path,))
467 467
468 468 elif not os.path.isabs(path):
469 469 path_ok = False
470 470 log.error('Given path %s is not an absolute path' % (path,))
471 471
472 472 # check if path is at least readable.
473 473 if not os.access(path, os.R_OK):
474 474 path_ok = False
475 475 log.error('Given path %s is not readable' % (path,))
476 476
477 477 # check write access, warn user about non writeable paths
478 478 elif not os.access(path, os.W_OK) and path_ok:
479 479 log.warning('No write permission to given path %s' % (path,))
480 480
481 q = ('Given path %s is not writeable, do you want to '
482 'continue with read only mode ? [y/n]' % (path,))
483 if not self.ask_ok(q):
481 q = ('Given path %s is not writeable, do you want to '
482 'continue with read only mode ? [y/n]' % (path,))
483 if not self.ask_ok(q):
484 484 log.error('Canceled by user')
485 485 sys.exit(-1)
486 486
487 487 if retries == 0:
488 488 sys.exit('max retries reached')
489 489 if not path_ok:
490 490 retries -= 1
491 491 return self.config_prompt(test_repo_path, retries)
492 492
493 493 real_path = os.path.normpath(os.path.realpath(path))
494 494
495 495 if real_path != os.path.normpath(path):
496 496 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
497 497 'given path as %s ? [y/n]') % (real_path,)
498 498 if not self.ask_ok(q):
499 499 log.error('Canceled by user')
500 500 sys.exit(-1)
501 501
502 502 return real_path
503 503
504 504 def create_settings(self, path):
505 505
506 506 self.create_ui_settings(path)
507 507
508 508 ui_config = [
509 509 ('web', 'push_ssl', 'false'),
510 510 ('web', 'allow_archive', 'gz zip bz2'),
511 511 ('web', 'allow_push', '*'),
512 512 ('web', 'baseurl', '/'),
513 513 ('paths', '/', path),
514 514 ('phases', 'publish', 'true')
515 515 ]
516 516 for section, key, value in ui_config:
517 517 ui_conf = RhodeCodeUi()
518 518 setattr(ui_conf, 'ui_section', section)
519 519 setattr(ui_conf, 'ui_key', key)
520 520 setattr(ui_conf, 'ui_value', value)
521 521 self.sa.add(ui_conf)
522 522
523 523 # rhodecode app settings
524 524 settings = [
525 525 ('realm', 'RhodeCode', 'unicode'),
526 526 ('title', '', 'unicode'),
527 527 ('pre_code', '', 'unicode'),
528 528 ('post_code', '', 'unicode'),
529 529 ('show_public_icon', True, 'bool'),
530 530 ('show_private_icon', True, 'bool'),
531 531 ('stylify_metatags', False, 'bool'),
532 532 ('dashboard_items', 100, 'int'),
533 533 ('admin_grid_items', 25, 'int'),
534 534 ('show_version', True, 'bool'),
535 535 ('use_gravatar', False, 'bool'),
536 536 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
537 537 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
538 538 ('support_url', '', 'unicode'),
539 539 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
540 540 ('show_revision_number', True, 'bool'),
541 541 ('show_sha_length', 12, 'int'),
542 542 ]
543 543
544 544 for key, val, type_ in settings:
545 545 sett = RhodeCodeSetting(key, val, type_)
546 546 self.sa.add(sett)
547 547
548 548 self.create_auth_plugin_options()
549 549 self.create_default_options()
550 550
551 551 log.info('created ui config')
552 552
553 553 def create_user(self, username, password, email='', admin=False,
554 554 strict_creation_check=True, api_key=None):
555 555 log.info('creating user %s' % username)
556 556 user = UserModel().create_or_update(
557 557 username, password, email, firstname='RhodeCode', lastname='Admin',
558 558 active=True, admin=admin, extern_type="rhodecode",
559 559 strict_creation_check=strict_creation_check)
560 560
561 561 if api_key:
562 562 log.info('setting a provided api key for the user %s', username)
563 563 user.api_key = api_key
564 564
565 565 def create_default_user(self):
566 566 log.info('creating default user')
567 567 # create default user for handling default permissions.
568 568 user = UserModel().create_or_update(username=User.DEFAULT_USER,
569 569 password=str(uuid.uuid1())[:20],
570 570 email=User.DEFAULT_USER_EMAIL,
571 571 firstname='Anonymous',
572 572 lastname='User',
573 573 strict_creation_check=False)
574 574 # based on configuration options activate/deactive this user which
575 575 # controlls anonymous access
576 576 if self.cli_args.get('public_access') is False:
577 577 log.info('Public access disabled')
578 578 user.active = False
579 579 Session().add(user)
580 580 Session().commit()
581 581
582 582 def create_permissions(self):
583 583 """
584 584 Creates all permissions defined in the system
585 585 """
586 586 # module.(access|create|change|delete)_[name]
587 587 # module.(none|read|write|admin)
588 588 log.info('creating permissions')
589 589 PermissionModel(self.sa).create_permissions()
590 590
591 591 def populate_default_permissions(self):
592 592 """
593 593 Populate default permissions. It will create only the default
594 594 permissions that are missing, and not alter already defined ones
595 595 """
596 596 log.info('creating default user permissions')
597 597 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,61 +1,114 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Generic encryption library for RhodeCode
24 24 """
25 25
26 import hashlib
27 26 import base64
28 27
29 28 from Crypto.Cipher import AES
30 29 from Crypto import Random
30 from Crypto.Hash import HMAC, SHA256
31 31
32 32 from rhodecode.lib.utils2 import safe_str
33 33
34 34
35 class SignatureVerificationError(Exception):
36 pass
37
38
39 class InvalidDecryptedValue(str):
40
41 def __new__(cls, content):
42 """
43 This will generate something like this::
44 <InvalidDecryptedValue(QkWusFgLJXR6m42v...)>
45 And represent a safe indicator that encryption key is broken
46 """
47 content = '<{}({}...)>'.format(cls.__name__, content[:16])
48 return str.__new__(cls, content)
49
50
35 51 class AESCipher(object):
36 def __init__(self, key):
37 # create padding, trim to long enc key
52 def __init__(self, key, hmac=False, strict_verification=True):
38 53 if not key:
39 54 raise ValueError('passed key variable is empty')
55 self.strict_verification = strict_verification
40 56 self.block_size = 32
41 self.key = hashlib.sha256(safe_str(key)).digest()
57 self.hmac_size = 32
58 self.hmac = hmac
59
60 self.key = SHA256.new(safe_str(key)).digest()
61 self.hmac_key = SHA256.new(self.key).digest()
62
63 def verify_hmac_signature(self, raw_data):
64 org_hmac_signature = raw_data[-self.hmac_size:]
65 data_without_sig = raw_data[:-self.hmac_size]
66 recomputed_hmac = HMAC.new(
67 self.hmac_key, data_without_sig, digestmod=SHA256).digest()
68 return org_hmac_signature == recomputed_hmac
42 69
43 70 def encrypt(self, raw):
44 71 raw = self._pad(raw)
45 72 iv = Random.new().read(AES.block_size)
46 73 cipher = AES.new(self.key, AES.MODE_CBC, iv)
47 return base64.b64encode(iv + cipher.encrypt(raw))
74 enc_value = cipher.encrypt(raw)
75
76 hmac_signature = ''
77 if self.hmac:
78 # compute hmac+sha256 on iv + enc text, we use
79 # encrypt then mac method to create the signature
80 hmac_signature = HMAC.new(
81 self.hmac_key, iv + enc_value, digestmod=SHA256).digest()
82
83 return base64.b64encode(iv + enc_value + hmac_signature)
48 84
49 85 def decrypt(self, enc):
86 enc_org = enc
50 87 enc = base64.b64decode(enc)
88
89 if self.hmac and len(enc) > self.hmac_size:
90 if self.verify_hmac_signature(enc):
91 # cut off the HMAC verification digest
92 enc = enc[:-self.hmac_size]
93 else:
94 if self.strict_verification:
95 raise SignatureVerificationError(
96 "Encryption signature verification failed. "
97 "Please check your secret key, and/or encrypted value. "
98 "Secret key is stored as "
99 "`rhodecode.encrypted_values.secret` or "
100 "`beaker.session.secret` inside .ini file")
101
102 return InvalidDecryptedValue(enc_org)
103
51 104 iv = enc[:AES.block_size]
52 105 cipher = AES.new(self.key, AES.MODE_CBC, iv)
53 106 return self._unpad(cipher.decrypt(enc[AES.block_size:]))
54 107
55 108 def _pad(self, s):
56 109 return (s + (self.block_size - len(s) % self.block_size)
57 110 * chr(self.block_size - len(s) % self.block_size))
58 111
59 112 @staticmethod
60 113 def _unpad(s):
61 114 return s[:-ord(s[len(s)-1:])] No newline at end of file
@@ -1,1892 +1,1897 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Helper functions
23 23
24 24 Consists of functions to typically be used within templates, but also
25 25 available to Controllers. This module is available to both as 'h'.
26 26 """
27 27
28 28 import random
29 29 import hashlib
30 30 import StringIO
31 31 import urllib
32 32 import math
33 33 import logging
34 34 import re
35 35 import urlparse
36 36 import time
37 37 import string
38 38 import hashlib
39 39 import pygments
40 40
41 41 from datetime import datetime
42 42 from functools import partial
43 43 from pygments.formatters.html import HtmlFormatter
44 44 from pygments import highlight as code_highlight
45 45 from pygments.lexers import (
46 46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
47 47 from pylons import url
48 48 from pylons.i18n.translation import _, ungettext
49 49 from pyramid.threadlocal import get_current_request
50 50
51 51 from webhelpers.html import literal, HTML, escape
52 52 from webhelpers.html.tools import *
53 53 from webhelpers.html.builder import make_tag
54 54 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
55 55 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
56 56 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
57 57 submit, text, password, textarea, title, ul, xml_declaration, radio
58 58 from webhelpers.html.tools import auto_link, button_to, highlight, \
59 59 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
60 60 from webhelpers.pylonslib import Flash as _Flash
61 61 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
62 62 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
63 63 replace_whitespace, urlify, truncate, wrap_paragraphs
64 64 from webhelpers.date import time_ago_in_words
65 65 from webhelpers.paginate import Page as _Page
66 66 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
67 67 convert_boolean_attrs, NotGiven, _make_safe_id_component
68 68 from webhelpers2.number import format_byte_size
69 69
70 70 from rhodecode.lib.annotate import annotate_highlight
71 71 from rhodecode.lib.action_parser import action_parser
72 72 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
73 73 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
74 74 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
75 75 AttributeDict, safe_int, md5, md5_safe
76 76 from rhodecode.lib.markup_renderer import MarkupRenderer
77 77 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
78 78 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
79 79 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
80 80 from rhodecode.model.changeset_status import ChangesetStatusModel
81 81 from rhodecode.model.db import Permission, User, Repository
82 82 from rhodecode.model.repo_group import RepoGroupModel
83 83 from rhodecode.model.settings import IssueTrackerSettingsModel
84 84
85 85 log = logging.getLogger(__name__)
86 86
87 87 DEFAULT_USER = User.DEFAULT_USER
88 88 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
89 89
90 90
91 91 def html_escape(text, html_escape_table=None):
92 92 """Produce entities within text."""
93 93 if not html_escape_table:
94 94 html_escape_table = {
95 95 "&": "&amp;",
96 96 '"': "&quot;",
97 97 "'": "&apos;",
98 98 ">": "&gt;",
99 99 "<": "&lt;",
100 100 }
101 101 return "".join(html_escape_table.get(c, c) for c in text)
102 102
103 103
104 104 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
105 105 """
106 106 Truncate string ``s`` at the first occurrence of ``sub``.
107 107
108 108 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
109 109 """
110 110 suffix_if_chopped = suffix_if_chopped or ''
111 111 pos = s.find(sub)
112 112 if pos == -1:
113 113 return s
114 114
115 115 if inclusive:
116 116 pos += len(sub)
117 117
118 118 chopped = s[:pos]
119 119 left = s[pos:].strip()
120 120
121 121 if left and suffix_if_chopped:
122 122 chopped += suffix_if_chopped
123 123
124 124 return chopped
125 125
126 126
127 127 def shorter(text, size=20):
128 128 postfix = '...'
129 129 if len(text) > size:
130 130 return text[:size - len(postfix)] + postfix
131 131 return text
132 132
133 133
134 134 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
135 135 """
136 136 Reset button
137 137 """
138 138 _set_input_attrs(attrs, type, name, value)
139 139 _set_id_attr(attrs, id, name)
140 140 convert_boolean_attrs(attrs, ["disabled"])
141 141 return HTML.input(**attrs)
142 142
143 143 reset = _reset
144 144 safeid = _make_safe_id_component
145 145
146 146
147 147 def branding(name, length=40):
148 148 return truncate(name, length, indicator="")
149 149
150 150
151 151 def FID(raw_id, path):
152 152 """
153 153 Creates a unique ID for filenode based on it's hash of path and commit
154 154 it's safe to use in urls
155 155
156 156 :param raw_id:
157 157 :param path:
158 158 """
159 159
160 160 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
161 161
162 162
163 163 class _GetError(object):
164 164 """Get error from form_errors, and represent it as span wrapped error
165 165 message
166 166
167 167 :param field_name: field to fetch errors for
168 168 :param form_errors: form errors dict
169 169 """
170 170
171 171 def __call__(self, field_name, form_errors):
172 172 tmpl = """<span class="error_msg">%s</span>"""
173 173 if form_errors and field_name in form_errors:
174 174 return literal(tmpl % form_errors.get(field_name))
175 175
176 176 get_error = _GetError()
177 177
178 178
179 179 class _ToolTip(object):
180 180
181 181 def __call__(self, tooltip_title, trim_at=50):
182 182 """
183 183 Special function just to wrap our text into nice formatted
184 184 autowrapped text
185 185
186 186 :param tooltip_title:
187 187 """
188 188 tooltip_title = escape(tooltip_title)
189 189 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
190 190 return tooltip_title
191 191 tooltip = _ToolTip()
192 192
193 193
194 194 def files_breadcrumbs(repo_name, commit_id, file_path):
195 195 if isinstance(file_path, str):
196 196 file_path = safe_unicode(file_path)
197 197
198 198 # TODO: johbo: Is this always a url like path, or is this operating
199 199 # system dependent?
200 200 path_segments = file_path.split('/')
201 201
202 202 repo_name_html = escape(repo_name)
203 203 if len(path_segments) == 1 and path_segments[0] == '':
204 204 url_segments = [repo_name_html]
205 205 else:
206 206 url_segments = [
207 207 link_to(
208 208 repo_name_html,
209 209 url('files_home',
210 210 repo_name=repo_name,
211 211 revision=commit_id,
212 212 f_path=''),
213 213 class_='pjax-link')]
214 214
215 215 last_cnt = len(path_segments) - 1
216 216 for cnt, segment in enumerate(path_segments):
217 217 if not segment:
218 218 continue
219 219 segment_html = escape(segment)
220 220
221 221 if cnt != last_cnt:
222 222 url_segments.append(
223 223 link_to(
224 224 segment_html,
225 225 url('files_home',
226 226 repo_name=repo_name,
227 227 revision=commit_id,
228 228 f_path='/'.join(path_segments[:cnt + 1])),
229 229 class_='pjax-link'))
230 230 else:
231 231 url_segments.append(segment_html)
232 232
233 233 return literal('/'.join(url_segments))
234 234
235 235
236 236 class CodeHtmlFormatter(HtmlFormatter):
237 237 """
238 238 My code Html Formatter for source codes
239 239 """
240 240
241 241 def wrap(self, source, outfile):
242 242 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
243 243
244 244 def _wrap_code(self, source):
245 245 for cnt, it in enumerate(source):
246 246 i, t = it
247 247 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
248 248 yield i, t
249 249
250 250 def _wrap_tablelinenos(self, inner):
251 251 dummyoutfile = StringIO.StringIO()
252 252 lncount = 0
253 253 for t, line in inner:
254 254 if t:
255 255 lncount += 1
256 256 dummyoutfile.write(line)
257 257
258 258 fl = self.linenostart
259 259 mw = len(str(lncount + fl - 1))
260 260 sp = self.linenospecial
261 261 st = self.linenostep
262 262 la = self.lineanchors
263 263 aln = self.anchorlinenos
264 264 nocls = self.noclasses
265 265 if sp:
266 266 lines = []
267 267
268 268 for i in range(fl, fl + lncount):
269 269 if i % st == 0:
270 270 if i % sp == 0:
271 271 if aln:
272 272 lines.append('<a href="#%s%d" class="special">%*d</a>' %
273 273 (la, i, mw, i))
274 274 else:
275 275 lines.append('<span class="special">%*d</span>' % (mw, i))
276 276 else:
277 277 if aln:
278 278 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
279 279 else:
280 280 lines.append('%*d' % (mw, i))
281 281 else:
282 282 lines.append('')
283 283 ls = '\n'.join(lines)
284 284 else:
285 285 lines = []
286 286 for i in range(fl, fl + lncount):
287 287 if i % st == 0:
288 288 if aln:
289 289 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
290 290 else:
291 291 lines.append('%*d' % (mw, i))
292 292 else:
293 293 lines.append('')
294 294 ls = '\n'.join(lines)
295 295
296 296 # in case you wonder about the seemingly redundant <div> here: since the
297 297 # content in the other cell also is wrapped in a div, some browsers in
298 298 # some configurations seem to mess up the formatting...
299 299 if nocls:
300 300 yield 0, ('<table class="%stable">' % self.cssclass +
301 301 '<tr><td><div class="linenodiv" '
302 302 'style="background-color: #f0f0f0; padding-right: 10px">'
303 303 '<pre style="line-height: 125%">' +
304 304 ls + '</pre></div></td><td id="hlcode" class="code">')
305 305 else:
306 306 yield 0, ('<table class="%stable">' % self.cssclass +
307 307 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
308 308 ls + '</pre></div></td><td id="hlcode" class="code">')
309 309 yield 0, dummyoutfile.getvalue()
310 310 yield 0, '</td></tr></table>'
311 311
312 312
313 313 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
314 314 def __init__(self, **kw):
315 315 # only show these line numbers if set
316 316 self.only_lines = kw.pop('only_line_numbers', [])
317 317 self.query_terms = kw.pop('query_terms', [])
318 318 self.max_lines = kw.pop('max_lines', 5)
319 319 self.line_context = kw.pop('line_context', 3)
320 320 self.url = kw.pop('url', None)
321 321
322 322 super(CodeHtmlFormatter, self).__init__(**kw)
323 323
324 324 def _wrap_code(self, source):
325 325 for cnt, it in enumerate(source):
326 326 i, t = it
327 327 t = '<pre>%s</pre>' % t
328 328 yield i, t
329 329
330 330 def _wrap_tablelinenos(self, inner):
331 331 yield 0, '<table class="code-highlight %stable">' % self.cssclass
332 332
333 333 last_shown_line_number = 0
334 334 current_line_number = 1
335 335
336 336 for t, line in inner:
337 337 if not t:
338 338 yield t, line
339 339 continue
340 340
341 341 if current_line_number in self.only_lines:
342 342 if last_shown_line_number + 1 != current_line_number:
343 343 yield 0, '<tr>'
344 344 yield 0, '<td class="line">...</td>'
345 345 yield 0, '<td id="hlcode" class="code"></td>'
346 346 yield 0, '</tr>'
347 347
348 348 yield 0, '<tr>'
349 349 if self.url:
350 350 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
351 351 self.url, current_line_number, current_line_number)
352 352 else:
353 353 yield 0, '<td class="line"><a href="">%i</a></td>' % (
354 354 current_line_number)
355 355 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
356 356 yield 0, '</tr>'
357 357
358 358 last_shown_line_number = current_line_number
359 359
360 360 current_line_number += 1
361 361
362 362
363 363 yield 0, '</table>'
364 364
365 365
366 366 def extract_phrases(text_query):
367 367 """
368 368 Extracts phrases from search term string making sure phrases
369 369 contained in double quotes are kept together - and discarding empty values
370 370 or fully whitespace values eg.
371 371
372 372 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
373 373
374 374 """
375 375
376 376 in_phrase = False
377 377 buf = ''
378 378 phrases = []
379 379 for char in text_query:
380 380 if in_phrase:
381 381 if char == '"': # end phrase
382 382 phrases.append(buf)
383 383 buf = ''
384 384 in_phrase = False
385 385 continue
386 386 else:
387 387 buf += char
388 388 continue
389 389 else:
390 390 if char == '"': # start phrase
391 391 in_phrase = True
392 392 phrases.append(buf)
393 393 buf = ''
394 394 continue
395 395 elif char == ' ':
396 396 phrases.append(buf)
397 397 buf = ''
398 398 continue
399 399 else:
400 400 buf += char
401 401
402 402 phrases.append(buf)
403 403 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
404 404 return phrases
405 405
406 406
407 407 def get_matching_offsets(text, phrases):
408 408 """
409 409 Returns a list of string offsets in `text` that the list of `terms` match
410 410
411 411 >>> get_matching_offsets('some text here', ['some', 'here'])
412 412 [(0, 4), (10, 14)]
413 413
414 414 """
415 415 offsets = []
416 416 for phrase in phrases:
417 417 for match in re.finditer(phrase, text):
418 418 offsets.append((match.start(), match.end()))
419 419
420 420 return offsets
421 421
422 422
423 423 def normalize_text_for_matching(x):
424 424 """
425 425 Replaces all non alnum characters to spaces and lower cases the string,
426 426 useful for comparing two text strings without punctuation
427 427 """
428 428 return re.sub(r'[^\w]', ' ', x.lower())
429 429
430 430
431 431 def get_matching_line_offsets(lines, terms):
432 432 """ Return a set of `lines` indices (starting from 1) matching a
433 433 text search query, along with `context` lines above/below matching lines
434 434
435 435 :param lines: list of strings representing lines
436 436 :param terms: search term string to match in lines eg. 'some text'
437 437 :param context: number of lines above/below a matching line to add to result
438 438 :param max_lines: cut off for lines of interest
439 439 eg.
440 440
441 >>> get_matching_line_offsets('''
442 words words words
443 words words words
444 some text some
445 words words words
446 words words words
447 text here what
448 ''', 'text', context=1)
441 text = '''
442 words words words
443 words words words
444 some text some
445 words words words
446 words words words
447 text here what
448 '''
449 get_matching_line_offsets(text, 'text', context=1)
449 450 {3: [(5, 9)], 6: [(0, 4)]]
451
450 452 """
451 453 matching_lines = {}
452 454 phrases = [normalize_text_for_matching(phrase)
453 455 for phrase in extract_phrases(terms)]
454 456
455 457 for line_index, line in enumerate(lines, start=1):
456 458 match_offsets = get_matching_offsets(
457 459 normalize_text_for_matching(line), phrases)
458 460 if match_offsets:
459 461 matching_lines[line_index] = match_offsets
460 462
461 463 return matching_lines
462 464
465
463 466 def get_lexer_safe(mimetype=None, filepath=None):
464 467 """
465 468 Tries to return a relevant pygments lexer using mimetype/filepath name,
466 469 defaulting to plain text if none could be found
467 470 """
468 471 lexer = None
469 472 try:
470 473 if mimetype:
471 474 lexer = get_lexer_for_mimetype(mimetype)
472 475 if not lexer:
473 lexer = get_lexer_for_filename(path)
476 lexer = get_lexer_for_filename(filepath)
474 477 except pygments.util.ClassNotFound:
475 478 pass
476 479
477 480 if not lexer:
478 481 lexer = get_lexer_by_name('text')
479 482
480 483 return lexer
481 484
482 485
483 486 def pygmentize(filenode, **kwargs):
484 487 """
485 488 pygmentize function using pygments
486 489
487 490 :param filenode:
488 491 """
489 492 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
490 493 return literal(code_highlight(filenode.content, lexer,
491 494 CodeHtmlFormatter(**kwargs)))
492 495
493 496
494 497 def pygmentize_annotation(repo_name, filenode, **kwargs):
495 498 """
496 499 pygmentize function for annotation
497 500
498 501 :param filenode:
499 502 """
500 503
501 504 color_dict = {}
502 505
503 506 def gen_color(n=10000):
504 507 """generator for getting n of evenly distributed colors using
505 508 hsv color and golden ratio. It always return same order of colors
506 509
507 510 :returns: RGB tuple
508 511 """
509 512
510 513 def hsv_to_rgb(h, s, v):
511 514 if s == 0.0:
512 515 return v, v, v
513 516 i = int(h * 6.0) # XXX assume int() truncates!
514 517 f = (h * 6.0) - i
515 518 p = v * (1.0 - s)
516 519 q = v * (1.0 - s * f)
517 520 t = v * (1.0 - s * (1.0 - f))
518 521 i = i % 6
519 522 if i == 0:
520 523 return v, t, p
521 524 if i == 1:
522 525 return q, v, p
523 526 if i == 2:
524 527 return p, v, t
525 528 if i == 3:
526 529 return p, q, v
527 530 if i == 4:
528 531 return t, p, v
529 532 if i == 5:
530 533 return v, p, q
531 534
532 535 golden_ratio = 0.618033988749895
533 536 h = 0.22717784590367374
534 537
535 538 for _ in xrange(n):
536 539 h += golden_ratio
537 540 h %= 1
538 541 HSV_tuple = [h, 0.95, 0.95]
539 542 RGB_tuple = hsv_to_rgb(*HSV_tuple)
540 543 yield map(lambda x: str(int(x * 256)), RGB_tuple)
541 544
542 545 cgenerator = gen_color()
543 546
544 547 def get_color_string(commit_id):
545 548 if commit_id in color_dict:
546 549 col = color_dict[commit_id]
547 550 else:
548 551 col = color_dict[commit_id] = cgenerator.next()
549 552 return "color: rgb(%s)! important;" % (', '.join(col))
550 553
551 554 def url_func(repo_name):
552 555
553 556 def _url_func(commit):
554 557 author = commit.author
555 558 date = commit.date
556 559 message = tooltip(commit.message)
557 560
558 561 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
559 562 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
560 563 "</b> %s<br/></div>")
561 564
562 565 tooltip_html = tooltip_html % (author, date, message)
563 566 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
564 567 uri = link_to(
565 568 lnk_format,
566 569 url('changeset_home', repo_name=repo_name,
567 570 revision=commit.raw_id),
568 571 style=get_color_string(commit.raw_id),
569 572 class_='tooltip',
570 573 title=tooltip_html
571 574 )
572 575
573 576 uri += '\n'
574 577 return uri
575 578 return _url_func
576 579
577 580 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
578 581
579 582
580 583 def is_following_repo(repo_name, user_id):
581 584 from rhodecode.model.scm import ScmModel
582 585 return ScmModel().is_following_repo(repo_name, user_id)
583 586
584 587
585 588 class _Message(object):
586 589 """A message returned by ``Flash.pop_messages()``.
587 590
588 591 Converting the message to a string returns the message text. Instances
589 592 also have the following attributes:
590 593
591 594 * ``message``: the message text.
592 595 * ``category``: the category specified when the message was created.
593 596 """
594 597
595 598 def __init__(self, category, message):
596 599 self.category = category
597 600 self.message = message
598 601
599 602 def __str__(self):
600 603 return self.message
601 604
602 605 __unicode__ = __str__
603 606
604 607 def __html__(self):
605 608 return escape(safe_unicode(self.message))
606 609
607 610
608 611 class Flash(_Flash):
609 612
610 613 def pop_messages(self):
611 614 """Return all accumulated messages and delete them from the session.
612 615
613 616 The return value is a list of ``Message`` objects.
614 617 """
615 618 from pylons import session
616 619
617 620 messages = []
618 621
619 622 # Pop the 'old' pylons flash messages. They are tuples of the form
620 623 # (category, message)
621 624 for cat, msg in session.pop(self.session_key, []):
622 625 messages.append(_Message(cat, msg))
623 626
624 627 # Pop the 'new' pyramid flash messages for each category as list
625 628 # of strings.
626 629 for cat in self.categories:
627 630 for msg in session.pop_flash(queue=cat):
628 631 messages.append(_Message(cat, msg))
629 632 # Map messages from the default queue to the 'notice' category.
630 633 for msg in session.pop_flash():
631 634 messages.append(_Message('notice', msg))
632 635
633 636 session.save()
634 637 return messages
635 638
636 639 flash = Flash()
637 640
638 641 #==============================================================================
639 642 # SCM FILTERS available via h.
640 643 #==============================================================================
641 644 from rhodecode.lib.vcs.utils import author_name, author_email
642 645 from rhodecode.lib.utils2 import credentials_filter, age as _age
643 646 from rhodecode.model.db import User, ChangesetStatus
644 647
645 648 age = _age
646 649 capitalize = lambda x: x.capitalize()
647 650 email = author_email
648 651 short_id = lambda x: x[:12]
649 652 hide_credentials = lambda x: ''.join(credentials_filter(x))
650 653
651 654
652 655 def age_component(datetime_iso, value=None, time_is_local=False):
653 656 title = value or format_date(datetime_iso)
654 657
655 658 # detect if we have a timezone info, otherwise, add it
656 659 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
657 660 tzinfo = '+00:00'
658 661
659 662 if time_is_local:
660 663 tzinfo = time.strftime("+%H:%M",
661 664 time.gmtime(
662 665 (datetime.now() - datetime.utcnow()).seconds + 1
663 666 )
664 667 )
665 668
666 669 return literal(
667 670 '<time class="timeago tooltip" '
668 671 'title="{1}" datetime="{0}{2}">{1}</time>'.format(
669 672 datetime_iso, title, tzinfo))
670 673
671 674
672 675 def _shorten_commit_id(commit_id):
673 676 from rhodecode import CONFIG
674 677 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
675 678 return commit_id[:def_len]
676 679
677 680
678 def get_repo_id_from_name(repo_name):
679 repo = get_by_repo_name(repo_name)
680 return repo.repo_id
681
682
683 681 def show_id(commit):
684 682 """
685 683 Configurable function that shows ID
686 684 by default it's r123:fffeeefffeee
687 685
688 686 :param commit: commit instance
689 687 """
690 688 from rhodecode import CONFIG
691 689 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
692 690
693 691 raw_id = _shorten_commit_id(commit.raw_id)
694 692 if show_idx:
695 693 return 'r%s:%s' % (commit.idx, raw_id)
696 694 else:
697 695 return '%s' % (raw_id, )
698 696
699 697
700 698 def format_date(date):
701 699 """
702 700 use a standardized formatting for dates used in RhodeCode
703 701
704 702 :param date: date/datetime object
705 703 :return: formatted date
706 704 """
707 705
708 706 if date:
709 707 _fmt = "%a, %d %b %Y %H:%M:%S"
710 708 return safe_unicode(date.strftime(_fmt))
711 709
712 710 return u""
713 711
714 712
715 713 class _RepoChecker(object):
716 714
717 715 def __init__(self, backend_alias):
718 716 self._backend_alias = backend_alias
719 717
720 718 def __call__(self, repository):
721 719 if hasattr(repository, 'alias'):
722 720 _type = repository.alias
723 721 elif hasattr(repository, 'repo_type'):
724 722 _type = repository.repo_type
725 723 else:
726 724 _type = repository
727 725 return _type == self._backend_alias
728 726
729 727 is_git = _RepoChecker('git')
730 728 is_hg = _RepoChecker('hg')
731 729 is_svn = _RepoChecker('svn')
732 730
733 731
734 732 def get_repo_type_by_name(repo_name):
735 733 repo = Repository.get_by_repo_name(repo_name)
736 734 return repo.repo_type
737 735
738 736
739 737 def is_svn_without_proxy(repository):
740 738 from rhodecode import CONFIG
741 739 if is_svn(repository):
742 740 if not CONFIG.get('rhodecode_proxy_subversion_http_requests', False):
743 741 return True
744 742 return False
745 743
746 744
745 def discover_user(author):
746 """
747 Tries to discover RhodeCode User based on the autho string. Author string
748 is typically `FirstName LastName <email@address.com>`
749 """
750
751 # if author is already an instance use it for extraction
752 if isinstance(author, User):
753 return author
754
755 # Valid email in the attribute passed, see if they're in the system
756 _email = author_email(author)
757 if _email != '':
758 user = User.get_by_email(_email, case_insensitive=True, cache=True)
759 if user is not None:
760 return user
761
762 # Maybe it's a username, we try to extract it and fetch by username ?
763 _author = author_name(author)
764 user = User.get_by_username(_author, case_insensitive=True, cache=True)
765 if user is not None:
766 return user
767
768 return None
769
770
747 771 def email_or_none(author):
748 772 # extract email from the commit string
749 773 _email = author_email(author)
750 774 if _email != '':
751 775 # check it against RhodeCode database, and use the MAIN email for this
752 776 # user
753 777 user = User.get_by_email(_email, case_insensitive=True, cache=True)
754 778 if user is not None:
755 779 return user.email
756 780 return _email
757 781
758 782 # See if it contains a username we can get an email from
759 783 user = User.get_by_username(author_name(author), case_insensitive=True,
760 784 cache=True)
761 785 if user is not None:
762 786 return user.email
763 787
764 788 # No valid email, not a valid user in the system, none!
765 789 return None
766 790
767 791
768 def discover_user(author):
769 # if author is already an instance use it for extraction
770 if isinstance(author, User):
771 return author
772
773 # Valid email in the attribute passed, see if they're in the system
774 _email = email(author)
775 if _email != '':
776 user = User.get_by_email(_email, case_insensitive=True, cache=True)
777 if user is not None:
778 return user
779
780 # Maybe it's a username?
781 _author = author_name(author)
782 user = User.get_by_username(_author, case_insensitive=True,
783 cache=True)
784 if user is not None:
785 return user
786
787 return None
788
789
790 792 def link_to_user(author, length=0, **kwargs):
791 793 user = discover_user(author)
794 # user can be None, but if we have it already it means we can re-use it
795 # in the person() function, so we save 1 intensive-query
796 if user:
797 author = user
798
792 799 display_person = person(author, 'username_or_name_or_email')
793 800 if length:
794 801 display_person = shorter(display_person, length)
795 802
796 803 if user:
797 804 return link_to(
798 805 escape(display_person),
799 806 url('user_profile', username=user.username),
800 807 **kwargs)
801 808 else:
802 809 return escape(display_person)
803 810
804 811
805 812 def person(author, show_attr="username_and_name"):
806 # attr to return from fetched user
807 person_getter = lambda usr: getattr(usr, show_attr)
808 813 user = discover_user(author)
809 814 if user:
810 return person_getter(user)
815 return getattr(user, show_attr)
811 816 else:
812 817 _author = author_name(author)
813 818 _email = email(author)
814 819 return _author or _email
815 820
816 821
817 822 def person_by_id(id_, show_attr="username_and_name"):
818 823 # attr to return from fetched user
819 824 person_getter = lambda usr: getattr(usr, show_attr)
820 825
821 826 #maybe it's an ID ?
822 827 if str(id_).isdigit() or isinstance(id_, int):
823 828 id_ = int(id_)
824 829 user = User.get(id_)
825 830 if user is not None:
826 831 return person_getter(user)
827 832 return id_
828 833
829 834
830 def gravatar_with_user(author):
835 def gravatar_with_user(author, show_disabled=False):
831 836 from rhodecode.lib.utils import PartialRenderer
832 837 _render = PartialRenderer('base/base.html')
833 return _render('gravatar_with_user', author)
838 return _render('gravatar_with_user', author, show_disabled=show_disabled)
834 839
835 840
836 841 def desc_stylize(value):
837 842 """
838 843 converts tags from value into html equivalent
839 844
840 845 :param value:
841 846 """
842 847 if not value:
843 848 return ''
844 849
845 850 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
846 851 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
847 852 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
848 853 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
849 854 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
850 855 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
851 856 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
852 857 '<div class="metatag" tag="lang">\\2</div>', value)
853 858 value = re.sub(r'\[([a-z]+)\]',
854 '<div class="metatag" tag="\\1">\\1</div>', value)
859 '<div class="metatag" tag="\\1">\\1</div>', value)
855 860
856 861 return value
857 862
858 863
859 864 def escaped_stylize(value):
860 865 """
861 866 converts tags from value into html equivalent, but escaping its value first
862 867 """
863 868 if not value:
864 869 return ''
865 870
866 871 # Using default webhelper escape method, but has to force it as a
867 872 # plain unicode instead of a markup tag to be used in regex expressions
868 873 value = unicode(escape(safe_unicode(value)))
869 874
870 875 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
871 876 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
872 877 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
873 878 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
874 879 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
875 880 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
876 881 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
877 882 '<div class="metatag" tag="lang">\\2</div>', value)
878 883 value = re.sub(r'\[([a-z]+)\]',
879 '<div class="metatag" tag="\\1">\\1</div>', value)
884 '<div class="metatag" tag="\\1">\\1</div>', value)
880 885
881 886 return value
882 887
883 888
884 889 def bool2icon(value):
885 890 """
886 891 Returns boolean value of a given value, represented as html element with
887 892 classes that will represent icons
888 893
889 894 :param value: given value to convert to html node
890 895 """
891 896
892 897 if value: # does bool conversion
893 898 return HTML.tag('i', class_="icon-true")
894 899 else: # not true as bool
895 900 return HTML.tag('i', class_="icon-false")
896 901
897 902
898 903 #==============================================================================
899 904 # PERMS
900 905 #==============================================================================
901 906 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
902 907 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
903 908 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token
904 909
905 910
906 911 #==============================================================================
907 912 # GRAVATAR URL
908 913 #==============================================================================
909 914 class InitialsGravatar(object):
910 915 def __init__(self, email_address, first_name, last_name, size=30,
911 916 background=None, text_color='#fff'):
912 917 self.size = size
913 918 self.first_name = first_name
914 919 self.last_name = last_name
915 920 self.email_address = email_address
916 921 self.background = background or self.str2color(email_address)
917 922 self.text_color = text_color
918 923
919 924 def get_color_bank(self):
920 925 """
921 926 returns a predefined list of colors that gravatars can use.
922 927 Those are randomized distinct colors that guarantee readability and
923 928 uniqueness.
924 929
925 930 generated with: http://phrogz.net/css/distinct-colors.html
926 931 """
927 932 return [
928 933 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
929 934 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
930 935 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
931 936 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
932 937 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
933 938 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
934 939 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
935 940 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
936 941 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
937 942 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
938 943 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
939 944 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
940 945 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
941 946 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
942 947 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
943 948 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
944 949 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
945 950 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
946 951 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
947 952 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
948 953 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
949 954 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
950 955 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
951 956 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
952 957 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
953 958 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
954 959 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
955 960 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
956 961 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
957 962 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
958 963 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
959 964 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
960 965 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
961 966 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
962 967 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
963 968 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
964 969 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
965 970 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
966 971 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
967 972 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
968 973 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
969 974 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
970 975 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
971 976 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
972 977 '#4f8c46', '#368dd9', '#5c0073'
973 978 ]
974 979
975 980 def rgb_to_hex_color(self, rgb_tuple):
976 981 """
977 982 Converts an rgb_tuple passed to an hex color.
978 983
979 984 :param rgb_tuple: tuple with 3 ints represents rgb color space
980 985 """
981 986 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
982 987
983 988 def email_to_int_list(self, email_str):
984 989 """
985 990 Get every byte of the hex digest value of email and turn it to integer.
986 991 It's going to be always between 0-255
987 992 """
988 993 digest = md5_safe(email_str.lower())
989 994 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
990 995
991 996 def pick_color_bank_index(self, email_str, color_bank):
992 997 return self.email_to_int_list(email_str)[0] % len(color_bank)
993 998
994 999 def str2color(self, email_str):
995 1000 """
996 1001 Tries to map in a stable algorithm an email to color
997 1002
998 1003 :param email_str:
999 1004 """
1000 1005 color_bank = self.get_color_bank()
1001 1006 # pick position (module it's length so we always find it in the
1002 1007 # bank even if it's smaller than 256 values
1003 1008 pos = self.pick_color_bank_index(email_str, color_bank)
1004 1009 return color_bank[pos]
1005 1010
1006 1011 def normalize_email(self, email_address):
1007 1012 import unicodedata
1008 1013 # default host used to fill in the fake/missing email
1009 1014 default_host = u'localhost'
1010 1015
1011 1016 if not email_address:
1012 1017 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1013 1018
1014 1019 email_address = safe_unicode(email_address)
1015 1020
1016 1021 if u'@' not in email_address:
1017 1022 email_address = u'%s@%s' % (email_address, default_host)
1018 1023
1019 1024 if email_address.endswith(u'@'):
1020 1025 email_address = u'%s%s' % (email_address, default_host)
1021 1026
1022 1027 email_address = unicodedata.normalize('NFKD', email_address)\
1023 1028 .encode('ascii', 'ignore')
1024 1029 return email_address
1025 1030
1026 1031 def get_initials(self):
1027 1032 """
1028 1033 Returns 2 letter initials calculated based on the input.
1029 1034 The algorithm picks first given email address, and takes first letter
1030 1035 of part before @, and then the first letter of server name. In case
1031 1036 the part before @ is in a format of `somestring.somestring2` it replaces
1032 1037 the server letter with first letter of somestring2
1033 1038
1034 1039 In case function was initialized with both first and lastname, this
1035 1040 overrides the extraction from email by first letter of the first and
1036 1041 last name. We add special logic to that functionality, In case Full name
1037 1042 is compound, like Guido Von Rossum, we use last part of the last name
1038 1043 (Von Rossum) picking `R`.
1039 1044
1040 1045 Function also normalizes the non-ascii characters to they ascii
1041 1046 representation, eg Ą => A
1042 1047 """
1043 1048 import unicodedata
1044 1049 # replace non-ascii to ascii
1045 1050 first_name = unicodedata.normalize(
1046 1051 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1047 1052 last_name = unicodedata.normalize(
1048 1053 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1049 1054
1050 1055 # do NFKD encoding, and also make sure email has proper format
1051 1056 email_address = self.normalize_email(self.email_address)
1052 1057
1053 1058 # first push the email initials
1054 1059 prefix, server = email_address.split('@', 1)
1055 1060
1056 1061 # check if prefix is maybe a 'firstname.lastname' syntax
1057 1062 _dot_split = prefix.rsplit('.', 1)
1058 1063 if len(_dot_split) == 2:
1059 1064 initials = [_dot_split[0][0], _dot_split[1][0]]
1060 1065 else:
1061 1066 initials = [prefix[0], server[0]]
1062 1067
1063 1068 # then try to replace either firtname or lastname
1064 1069 fn_letter = (first_name or " ")[0].strip()
1065 1070 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1066 1071
1067 1072 if fn_letter:
1068 1073 initials[0] = fn_letter
1069 1074
1070 1075 if ln_letter:
1071 1076 initials[1] = ln_letter
1072 1077
1073 1078 return ''.join(initials).upper()
1074 1079
1075 1080 def get_img_data_by_type(self, font_family, img_type):
1076 1081 default_user = """
1077 1082 <svg xmlns="http://www.w3.org/2000/svg"
1078 1083 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1079 1084 viewBox="-15 -10 439.165 429.164"
1080 1085
1081 1086 xml:space="preserve"
1082 1087 style="background:{background};" >
1083 1088
1084 1089 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1085 1090 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1086 1091 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1087 1092 168.596,153.916,216.671,
1088 1093 204.583,216.671z" fill="{text_color}"/>
1089 1094 <path d="M407.164,374.717L360.88,
1090 1095 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1091 1096 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1092 1097 15.366-44.203,23.488-69.076,23.488c-24.877,
1093 1098 0-48.762-8.122-69.078-23.488
1094 1099 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1095 1100 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1096 1101 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1097 1102 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1098 1103 19.402-10.527 C409.699,390.129,
1099 1104 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1100 1105 </svg>""".format(
1101 1106 size=self.size,
1102 1107 background='#979797', # @grey4
1103 1108 text_color=self.text_color,
1104 1109 font_family=font_family)
1105 1110
1106 1111 return {
1107 1112 "default_user": default_user
1108 1113 }[img_type]
1109 1114
1110 1115 def get_img_data(self, svg_type=None):
1111 1116 """
1112 1117 generates the svg metadata for image
1113 1118 """
1114 1119
1115 1120 font_family = ','.join([
1116 1121 'proximanovaregular',
1117 1122 'Proxima Nova Regular',
1118 1123 'Proxima Nova',
1119 1124 'Arial',
1120 1125 'Lucida Grande',
1121 1126 'sans-serif'
1122 1127 ])
1123 1128 if svg_type:
1124 1129 return self.get_img_data_by_type(font_family, svg_type)
1125 1130
1126 1131 initials = self.get_initials()
1127 1132 img_data = """
1128 1133 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1129 1134 width="{size}" height="{size}"
1130 1135 style="width: 100%; height: 100%; background-color: {background}"
1131 1136 viewBox="0 0 {size} {size}">
1132 1137 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1133 1138 pointer-events="auto" fill="{text_color}"
1134 1139 font-family="{font_family}"
1135 1140 style="font-weight: 400; font-size: {f_size}px;">{text}
1136 1141 </text>
1137 1142 </svg>""".format(
1138 1143 size=self.size,
1139 1144 f_size=self.size/1.85, # scale the text inside the box nicely
1140 1145 background=self.background,
1141 1146 text_color=self.text_color,
1142 1147 text=initials.upper(),
1143 1148 font_family=font_family)
1144 1149
1145 1150 return img_data
1146 1151
1147 1152 def generate_svg(self, svg_type=None):
1148 1153 img_data = self.get_img_data(svg_type)
1149 1154 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1150 1155
1151 1156
1152 1157 def initials_gravatar(email_address, first_name, last_name, size=30):
1153 1158 svg_type = None
1154 1159 if email_address == User.DEFAULT_USER_EMAIL:
1155 1160 svg_type = 'default_user'
1156 1161 klass = InitialsGravatar(email_address, first_name, last_name, size)
1157 1162 return klass.generate_svg(svg_type=svg_type)
1158 1163
1159 1164
1160 1165 def gravatar_url(email_address, size=30):
1161 1166 # doh, we need to re-import those to mock it later
1162 1167 from pylons import tmpl_context as c
1163 1168
1164 1169 _use_gravatar = c.visual.use_gravatar
1165 1170 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1166 1171
1167 1172 email_address = email_address or User.DEFAULT_USER_EMAIL
1168 1173 if isinstance(email_address, unicode):
1169 1174 # hashlib crashes on unicode items
1170 1175 email_address = safe_str(email_address)
1171 1176
1172 1177 # empty email or default user
1173 1178 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1174 1179 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1175 1180
1176 1181 if _use_gravatar:
1177 1182 # TODO: Disuse pyramid thread locals. Think about another solution to
1178 1183 # get the host and schema here.
1179 1184 request = get_current_request()
1180 1185 tmpl = safe_str(_gravatar_url)
1181 1186 tmpl = tmpl.replace('{email}', email_address)\
1182 1187 .replace('{md5email}', md5_safe(email_address.lower())) \
1183 1188 .replace('{netloc}', request.host)\
1184 1189 .replace('{scheme}', request.scheme)\
1185 1190 .replace('{size}', safe_str(size))
1186 1191 return tmpl
1187 1192 else:
1188 1193 return initials_gravatar(email_address, '', '', size=size)
1189 1194
1190 1195
1191 1196 class Page(_Page):
1192 1197 """
1193 1198 Custom pager to match rendering style with paginator
1194 1199 """
1195 1200
1196 1201 def _get_pos(self, cur_page, max_page, items):
1197 1202 edge = (items / 2) + 1
1198 1203 if (cur_page <= edge):
1199 1204 radius = max(items / 2, items - cur_page)
1200 1205 elif (max_page - cur_page) < edge:
1201 1206 radius = (items - 1) - (max_page - cur_page)
1202 1207 else:
1203 1208 radius = items / 2
1204 1209
1205 1210 left = max(1, (cur_page - (radius)))
1206 1211 right = min(max_page, cur_page + (radius))
1207 1212 return left, cur_page, right
1208 1213
1209 1214 def _range(self, regexp_match):
1210 1215 """
1211 1216 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1212 1217
1213 1218 Arguments:
1214 1219
1215 1220 regexp_match
1216 1221 A "re" (regular expressions) match object containing the
1217 1222 radius of linked pages around the current page in
1218 1223 regexp_match.group(1) as a string
1219 1224
1220 1225 This function is supposed to be called as a callable in
1221 1226 re.sub.
1222 1227
1223 1228 """
1224 1229 radius = int(regexp_match.group(1))
1225 1230
1226 1231 # Compute the first and last page number within the radius
1227 1232 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1228 1233 # -> leftmost_page = 5
1229 1234 # -> rightmost_page = 9
1230 1235 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1231 1236 self.last_page,
1232 1237 (radius * 2) + 1)
1233 1238 nav_items = []
1234 1239
1235 1240 # Create a link to the first page (unless we are on the first page
1236 1241 # or there would be no need to insert '..' spacers)
1237 1242 if self.page != self.first_page and self.first_page < leftmost_page:
1238 1243 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1239 1244
1240 1245 # Insert dots if there are pages between the first page
1241 1246 # and the currently displayed page range
1242 1247 if leftmost_page - self.first_page > 1:
1243 1248 # Wrap in a SPAN tag if nolink_attr is set
1244 1249 text = '..'
1245 1250 if self.dotdot_attr:
1246 1251 text = HTML.span(c=text, **self.dotdot_attr)
1247 1252 nav_items.append(text)
1248 1253
1249 1254 for thispage in xrange(leftmost_page, rightmost_page + 1):
1250 1255 # Hilight the current page number and do not use a link
1251 1256 if thispage == self.page:
1252 1257 text = '%s' % (thispage,)
1253 1258 # Wrap in a SPAN tag if nolink_attr is set
1254 1259 if self.curpage_attr:
1255 1260 text = HTML.span(c=text, **self.curpage_attr)
1256 1261 nav_items.append(text)
1257 1262 # Otherwise create just a link to that page
1258 1263 else:
1259 1264 text = '%s' % (thispage,)
1260 1265 nav_items.append(self._pagerlink(thispage, text))
1261 1266
1262 1267 # Insert dots if there are pages between the displayed
1263 1268 # page numbers and the end of the page range
1264 1269 if self.last_page - rightmost_page > 1:
1265 1270 text = '..'
1266 1271 # Wrap in a SPAN tag if nolink_attr is set
1267 1272 if self.dotdot_attr:
1268 1273 text = HTML.span(c=text, **self.dotdot_attr)
1269 1274 nav_items.append(text)
1270 1275
1271 1276 # Create a link to the very last page (unless we are on the last
1272 1277 # page or there would be no need to insert '..' spacers)
1273 1278 if self.page != self.last_page and rightmost_page < self.last_page:
1274 1279 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1275 1280
1276 1281 ## prerender links
1277 1282 #_page_link = url.current()
1278 1283 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1279 1284 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1280 1285 return self.separator.join(nav_items)
1281 1286
1282 1287 def pager(self, format='~2~', page_param='page', partial_param='partial',
1283 1288 show_if_single_page=False, separator=' ', onclick=None,
1284 1289 symbol_first='<<', symbol_last='>>',
1285 1290 symbol_previous='<', symbol_next='>',
1286 1291 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1287 1292 curpage_attr={'class': 'pager_curpage'},
1288 1293 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1289 1294
1290 1295 self.curpage_attr = curpage_attr
1291 1296 self.separator = separator
1292 1297 self.pager_kwargs = kwargs
1293 1298 self.page_param = page_param
1294 1299 self.partial_param = partial_param
1295 1300 self.onclick = onclick
1296 1301 self.link_attr = link_attr
1297 1302 self.dotdot_attr = dotdot_attr
1298 1303
1299 1304 # Don't show navigator if there is no more than one page
1300 1305 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1301 1306 return ''
1302 1307
1303 1308 from string import Template
1304 1309 # Replace ~...~ in token format by range of pages
1305 1310 result = re.sub(r'~(\d+)~', self._range, format)
1306 1311
1307 1312 # Interpolate '%' variables
1308 1313 result = Template(result).safe_substitute({
1309 1314 'first_page': self.first_page,
1310 1315 'last_page': self.last_page,
1311 1316 'page': self.page,
1312 1317 'page_count': self.page_count,
1313 1318 'items_per_page': self.items_per_page,
1314 1319 'first_item': self.first_item,
1315 1320 'last_item': self.last_item,
1316 1321 'item_count': self.item_count,
1317 1322 'link_first': self.page > self.first_page and \
1318 1323 self._pagerlink(self.first_page, symbol_first) or '',
1319 1324 'link_last': self.page < self.last_page and \
1320 1325 self._pagerlink(self.last_page, symbol_last) or '',
1321 1326 'link_previous': self.previous_page and \
1322 1327 self._pagerlink(self.previous_page, symbol_previous) \
1323 1328 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1324 1329 'link_next': self.next_page and \
1325 1330 self._pagerlink(self.next_page, symbol_next) \
1326 1331 or HTML.span(symbol_next, class_="pg-next disabled")
1327 1332 })
1328 1333
1329 1334 return literal(result)
1330 1335
1331 1336
1332 1337 #==============================================================================
1333 1338 # REPO PAGER, PAGER FOR REPOSITORY
1334 1339 #==============================================================================
1335 1340 class RepoPage(Page):
1336 1341
1337 1342 def __init__(self, collection, page=1, items_per_page=20,
1338 1343 item_count=None, url=None, **kwargs):
1339 1344
1340 1345 """Create a "RepoPage" instance. special pager for paging
1341 1346 repository
1342 1347 """
1343 1348 self._url_generator = url
1344 1349
1345 1350 # Safe the kwargs class-wide so they can be used in the pager() method
1346 1351 self.kwargs = kwargs
1347 1352
1348 1353 # Save a reference to the collection
1349 1354 self.original_collection = collection
1350 1355
1351 1356 self.collection = collection
1352 1357
1353 1358 # The self.page is the number of the current page.
1354 1359 # The first page has the number 1!
1355 1360 try:
1356 1361 self.page = int(page) # make it int() if we get it as a string
1357 1362 except (ValueError, TypeError):
1358 1363 self.page = 1
1359 1364
1360 1365 self.items_per_page = items_per_page
1361 1366
1362 1367 # Unless the user tells us how many items the collections has
1363 1368 # we calculate that ourselves.
1364 1369 if item_count is not None:
1365 1370 self.item_count = item_count
1366 1371 else:
1367 1372 self.item_count = len(self.collection)
1368 1373
1369 1374 # Compute the number of the first and last available page
1370 1375 if self.item_count > 0:
1371 1376 self.first_page = 1
1372 1377 self.page_count = int(math.ceil(float(self.item_count) /
1373 1378 self.items_per_page))
1374 1379 self.last_page = self.first_page + self.page_count - 1
1375 1380
1376 1381 # Make sure that the requested page number is the range of
1377 1382 # valid pages
1378 1383 if self.page > self.last_page:
1379 1384 self.page = self.last_page
1380 1385 elif self.page < self.first_page:
1381 1386 self.page = self.first_page
1382 1387
1383 1388 # Note: the number of items on this page can be less than
1384 1389 # items_per_page if the last page is not full
1385 1390 self.first_item = max(0, (self.item_count) - (self.page *
1386 1391 items_per_page))
1387 1392 self.last_item = ((self.item_count - 1) - items_per_page *
1388 1393 (self.page - 1))
1389 1394
1390 1395 self.items = list(self.collection[self.first_item:self.last_item + 1])
1391 1396
1392 1397 # Links to previous and next page
1393 1398 if self.page > self.first_page:
1394 1399 self.previous_page = self.page - 1
1395 1400 else:
1396 1401 self.previous_page = None
1397 1402
1398 1403 if self.page < self.last_page:
1399 1404 self.next_page = self.page + 1
1400 1405 else:
1401 1406 self.next_page = None
1402 1407
1403 1408 # No items available
1404 1409 else:
1405 1410 self.first_page = None
1406 1411 self.page_count = 0
1407 1412 self.last_page = None
1408 1413 self.first_item = None
1409 1414 self.last_item = None
1410 1415 self.previous_page = None
1411 1416 self.next_page = None
1412 1417 self.items = []
1413 1418
1414 1419 # This is a subclass of the 'list' type. Initialise the list now.
1415 1420 list.__init__(self, reversed(self.items))
1416 1421
1417 1422
1418 1423 def changed_tooltip(nodes):
1419 1424 """
1420 1425 Generates a html string for changed nodes in commit page.
1421 1426 It limits the output to 30 entries
1422 1427
1423 1428 :param nodes: LazyNodesGenerator
1424 1429 """
1425 1430 if nodes:
1426 1431 pref = ': <br/> '
1427 1432 suf = ''
1428 1433 if len(nodes) > 30:
1429 1434 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1430 1435 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1431 1436 for x in nodes[:30]]) + suf)
1432 1437 else:
1433 1438 return ': ' + _('No Files')
1434 1439
1435 1440
1436 1441 def breadcrumb_repo_link(repo):
1437 1442 """
1438 1443 Makes a breadcrumbs path link to repo
1439 1444
1440 1445 ex::
1441 1446 group >> subgroup >> repo
1442 1447
1443 1448 :param repo: a Repository instance
1444 1449 """
1445 1450
1446 1451 path = [
1447 1452 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1448 1453 for group in repo.groups_with_parents
1449 1454 ] + [
1450 1455 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1451 1456 ]
1452 1457
1453 1458 return literal(' &raquo; '.join(path))
1454 1459
1455 1460
1456 1461 def format_byte_size_binary(file_size):
1457 1462 """
1458 1463 Formats file/folder sizes to standard.
1459 1464 """
1460 1465 formatted_size = format_byte_size(file_size, binary=True)
1461 1466 return formatted_size
1462 1467
1463 1468
1464 1469 def fancy_file_stats(stats):
1465 1470 """
1466 1471 Displays a fancy two colored bar for number of added/deleted
1467 1472 lines of code on file
1468 1473
1469 1474 :param stats: two element list of added/deleted lines of code
1470 1475 """
1471 1476 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1472 1477 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1473 1478
1474 1479 def cgen(l_type, a_v, d_v):
1475 1480 mapping = {'tr': 'top-right-rounded-corner-mid',
1476 1481 'tl': 'top-left-rounded-corner-mid',
1477 1482 'br': 'bottom-right-rounded-corner-mid',
1478 1483 'bl': 'bottom-left-rounded-corner-mid'}
1479 1484 map_getter = lambda x: mapping[x]
1480 1485
1481 1486 if l_type == 'a' and d_v:
1482 1487 #case when added and deleted are present
1483 1488 return ' '.join(map(map_getter, ['tl', 'bl']))
1484 1489
1485 1490 if l_type == 'a' and not d_v:
1486 1491 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1487 1492
1488 1493 if l_type == 'd' and a_v:
1489 1494 return ' '.join(map(map_getter, ['tr', 'br']))
1490 1495
1491 1496 if l_type == 'd' and not a_v:
1492 1497 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1493 1498
1494 1499 a, d = stats['added'], stats['deleted']
1495 1500 width = 100
1496 1501
1497 1502 if stats['binary']: # binary operations like chmod/rename etc
1498 1503 lbl = []
1499 1504 bin_op = 0 # undefined
1500 1505
1501 1506 # prefix with bin for binary files
1502 1507 if BIN_FILENODE in stats['ops']:
1503 1508 lbl += ['bin']
1504 1509
1505 1510 if NEW_FILENODE in stats['ops']:
1506 1511 lbl += [_('new file')]
1507 1512 bin_op = NEW_FILENODE
1508 1513 elif MOD_FILENODE in stats['ops']:
1509 1514 lbl += [_('mod')]
1510 1515 bin_op = MOD_FILENODE
1511 1516 elif DEL_FILENODE in stats['ops']:
1512 1517 lbl += [_('del')]
1513 1518 bin_op = DEL_FILENODE
1514 1519 elif RENAMED_FILENODE in stats['ops']:
1515 1520 lbl += [_('rename')]
1516 1521 bin_op = RENAMED_FILENODE
1517 1522
1518 1523 # chmod can go with other operations, so we add a + to lbl if needed
1519 1524 if CHMOD_FILENODE in stats['ops']:
1520 1525 lbl += [_('chmod')]
1521 1526 if bin_op == 0:
1522 1527 bin_op = CHMOD_FILENODE
1523 1528
1524 1529 lbl = '+'.join(lbl)
1525 1530 b_a = '<div class="bin bin%s %s" style="width:100%%">%s</div>' \
1526 1531 % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1527 1532 b_d = '<div class="bin bin1" style="width:0%%"></div>'
1528 1533 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1529 1534
1530 1535 t = stats['added'] + stats['deleted']
1531 1536 unit = float(width) / (t or 1)
1532 1537
1533 1538 # needs > 9% of width to be visible or 0 to be hidden
1534 1539 a_p = max(9, unit * a) if a > 0 else 0
1535 1540 d_p = max(9, unit * d) if d > 0 else 0
1536 1541 p_sum = a_p + d_p
1537 1542
1538 1543 if p_sum > width:
1539 1544 #adjust the percentage to be == 100% since we adjusted to 9
1540 1545 if a_p > d_p:
1541 1546 a_p = a_p - (p_sum - width)
1542 1547 else:
1543 1548 d_p = d_p - (p_sum - width)
1544 1549
1545 1550 a_v = a if a > 0 else ''
1546 1551 d_v = d if d > 0 else ''
1547 1552
1548 1553 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1549 1554 cgen('a', a_v, d_v), a_p, a_v
1550 1555 )
1551 1556 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1552 1557 cgen('d', a_v, d_v), d_p, d_v
1553 1558 )
1554 1559 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1555 1560
1556 1561
1557 1562 def urlify_text(text_, safe=True):
1558 1563 """
1559 1564 Extrac urls from text and make html links out of them
1560 1565
1561 1566 :param text_:
1562 1567 """
1563 1568
1564 1569 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1565 1570 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1566 1571
1567 1572 def url_func(match_obj):
1568 1573 url_full = match_obj.groups()[0]
1569 1574 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1570 1575 _newtext = url_pat.sub(url_func, text_)
1571 1576 if safe:
1572 1577 return literal(_newtext)
1573 1578 return _newtext
1574 1579
1575 1580
1576 1581 def urlify_commits(text_, repository):
1577 1582 """
1578 1583 Extract commit ids from text and make link from them
1579 1584
1580 1585 :param text_:
1581 1586 :param repository: repo name to build the URL with
1582 1587 """
1583 1588 from pylons import url # doh, we need to re-import url to mock it later
1584 1589 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1585 1590
1586 1591 def url_func(match_obj):
1587 1592 commit_id = match_obj.groups()[1]
1588 1593 pref = match_obj.groups()[0]
1589 1594 suf = match_obj.groups()[2]
1590 1595
1591 1596 tmpl = (
1592 1597 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1593 1598 '%(commit_id)s</a>%(suf)s'
1594 1599 )
1595 1600 return tmpl % {
1596 1601 'pref': pref,
1597 1602 'cls': 'revision-link',
1598 1603 'url': url('changeset_home', repo_name=repository,
1599 1604 revision=commit_id),
1600 1605 'commit_id': commit_id,
1601 1606 'suf': suf
1602 1607 }
1603 1608
1604 1609 newtext = URL_PAT.sub(url_func, text_)
1605 1610
1606 1611 return newtext
1607 1612
1608 1613
1609 1614 def _process_url_func(match_obj, repo_name, uid, entry):
1610 1615 pref = ''
1611 1616 if match_obj.group().startswith(' '):
1612 1617 pref = ' '
1613 1618
1614 1619 issue_id = ''.join(match_obj.groups())
1615 1620 tmpl = (
1616 1621 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1617 1622 '%(issue-prefix)s%(id-repr)s'
1618 1623 '</a>')
1619 1624
1620 1625 (repo_name_cleaned,
1621 1626 parent_group_name) = RepoGroupModel().\
1622 1627 _get_group_name_and_parent(repo_name)
1623 1628
1624 1629 # variables replacement
1625 1630 named_vars = {
1626 1631 'id': issue_id,
1627 1632 'repo': repo_name,
1628 1633 'repo_name': repo_name_cleaned,
1629 1634 'group_name': parent_group_name
1630 1635 }
1631 1636 # named regex variables
1632 1637 named_vars.update(match_obj.groupdict())
1633 1638 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1634 1639
1635 1640 return tmpl % {
1636 1641 'pref': pref,
1637 1642 'cls': 'issue-tracker-link',
1638 1643 'url': _url,
1639 1644 'id-repr': issue_id,
1640 1645 'issue-prefix': entry['pref'],
1641 1646 'serv': entry['url'],
1642 1647 }
1643 1648
1644 1649
1645 1650 def process_patterns(text_string, repo_name, config):
1646 1651 repo = None
1647 1652 if repo_name:
1648 1653 # Retrieving repo_name to avoid invalid repo_name to explode on
1649 1654 # IssueTrackerSettingsModel but still passing invalid name further down
1650 repo = Repository.get_by_repo_name(repo_name)
1655 repo = Repository.get_by_repo_name(repo_name, cache=True)
1651 1656
1652 1657 settings_model = IssueTrackerSettingsModel(repo=repo)
1653 active_entries = settings_model.get_settings()
1658 active_entries = settings_model.get_settings(cache=True)
1654 1659
1655 1660 newtext = text_string
1656 1661 for uid, entry in active_entries.items():
1657 1662 url_func = partial(
1658 1663 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1659 1664
1660 1665 log.debug('found issue tracker entry with uid %s' % (uid,))
1661 1666
1662 1667 if not (entry['pat'] and entry['url']):
1663 1668 log.debug('skipping due to missing data')
1664 1669 continue
1665 1670
1666 1671 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1667 1672 % (uid, entry['pat'], entry['url'], entry['pref']))
1668 1673
1669 1674 try:
1670 1675 pattern = re.compile(r'%s' % entry['pat'])
1671 1676 except re.error:
1672 1677 log.exception(
1673 1678 'issue tracker pattern: `%s` failed to compile',
1674 1679 entry['pat'])
1675 1680 continue
1676 1681
1677 1682 newtext = pattern.sub(url_func, newtext)
1678 1683 log.debug('processed prefix:uid `%s`' % (uid,))
1679 1684
1680 1685 return newtext
1681 1686
1682 1687
1683 1688 def urlify_commit_message(commit_text, repository=None):
1684 1689 """
1685 1690 Parses given text message and makes proper links.
1686 1691 issues are linked to given issue-server, and rest is a commit link
1687 1692
1688 1693 :param commit_text:
1689 1694 :param repository:
1690 1695 """
1691 1696 from pylons import url # doh, we need to re-import url to mock it later
1692 1697 from rhodecode import CONFIG
1693 1698
1694 1699 def escaper(string):
1695 1700 return string.replace('<', '&lt;').replace('>', '&gt;')
1696 1701
1697 1702 newtext = escaper(commit_text)
1698 1703 # urlify commits - extract commit ids and make link out of them, if we have
1699 1704 # the scope of repository present.
1700 1705 if repository:
1701 1706 newtext = urlify_commits(newtext, repository)
1702 1707
1703 1708 # extract http/https links and make them real urls
1704 1709 newtext = urlify_text(newtext, safe=False)
1705 1710
1706 1711 # process issue tracker patterns
1707 1712 newtext = process_patterns(newtext, repository or '', CONFIG)
1708 1713
1709 1714 return literal(newtext)
1710 1715
1711 1716
1712 1717 def rst(source, mentions=False):
1713 1718 return literal('<div class="rst-block">%s</div>' %
1714 1719 MarkupRenderer.rst(source, mentions=mentions))
1715 1720
1716 1721
1717 1722 def markdown(source, mentions=False):
1718 1723 return literal('<div class="markdown-block">%s</div>' %
1719 1724 MarkupRenderer.markdown(source, flavored=False,
1720 1725 mentions=mentions))
1721 1726
1722 1727 def renderer_from_filename(filename, exclude=None):
1723 1728 from rhodecode.config.conf import MARKDOWN_EXTS, RST_EXTS
1724 1729
1725 1730 def _filter(elements):
1726 1731 if isinstance(exclude, (list, tuple)):
1727 1732 return [x for x in elements if x not in exclude]
1728 1733 return elements
1729 1734
1730 1735 if filename.endswith(tuple(_filter([x[0] for x in MARKDOWN_EXTS if x[0]]))):
1731 1736 return 'markdown'
1732 1737 if filename.endswith(tuple(_filter([x[0] for x in RST_EXTS if x[0]]))):
1733 1738 return 'rst'
1734 1739
1735 1740
1736 1741 def render(source, renderer='rst', mentions=False):
1737 1742 if renderer == 'rst':
1738 1743 return rst(source, mentions=mentions)
1739 1744 if renderer == 'markdown':
1740 1745 return markdown(source, mentions=mentions)
1741 1746
1742 1747
1743 1748 def commit_status(repo, commit_id):
1744 1749 return ChangesetStatusModel().get_status(repo, commit_id)
1745 1750
1746 1751
1747 1752 def commit_status_lbl(commit_status):
1748 1753 return dict(ChangesetStatus.STATUSES).get(commit_status)
1749 1754
1750 1755
1751 1756 def commit_time(repo_name, commit_id):
1752 1757 repo = Repository.get_by_repo_name(repo_name)
1753 1758 commit = repo.get_commit(commit_id=commit_id)
1754 1759 return commit.date
1755 1760
1756 1761
1757 1762 def get_permission_name(key):
1758 1763 return dict(Permission.PERMS).get(key)
1759 1764
1760 1765
1761 1766 def journal_filter_help():
1762 1767 return _(
1763 1768 'Example filter terms:\n' +
1764 1769 ' repository:vcs\n' +
1765 1770 ' username:marcin\n' +
1766 1771 ' action:*push*\n' +
1767 1772 ' ip:127.0.0.1\n' +
1768 1773 ' date:20120101\n' +
1769 1774 ' date:[20120101100000 TO 20120102]\n' +
1770 1775 '\n' +
1771 1776 'Generate wildcards using \'*\' character:\n' +
1772 1777 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1773 1778 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1774 1779 '\n' +
1775 1780 'Optional AND / OR operators in queries\n' +
1776 1781 ' "repository:vcs OR repository:test"\n' +
1777 1782 ' "username:test AND repository:test*"\n'
1778 1783 )
1779 1784
1780 1785
1781 1786 def not_mapped_error(repo_name):
1782 1787 flash(_('%s repository is not mapped to db perhaps'
1783 1788 ' it was created or renamed from the filesystem'
1784 1789 ' please run the application again'
1785 1790 ' in order to rescan repositories') % repo_name, category='error')
1786 1791
1787 1792
1788 1793 def ip_range(ip_addr):
1789 1794 from rhodecode.model.db import UserIpMap
1790 1795 s, e = UserIpMap._get_ip_range(ip_addr)
1791 1796 return '%s - %s' % (s, e)
1792 1797
1793 1798
1794 1799 def form(url, method='post', needs_csrf_token=True, **attrs):
1795 1800 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1796 1801 if method.lower() != 'get' and needs_csrf_token:
1797 1802 raise Exception(
1798 1803 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1799 1804 'CSRF token. If the endpoint does not require such token you can ' +
1800 1805 'explicitly set the parameter needs_csrf_token to false.')
1801 1806
1802 1807 return wh_form(url, method=method, **attrs)
1803 1808
1804 1809
1805 1810 def secure_form(url, method="POST", multipart=False, **attrs):
1806 1811 """Start a form tag that points the action to an url. This
1807 1812 form tag will also include the hidden field containing
1808 1813 the auth token.
1809 1814
1810 1815 The url options should be given either as a string, or as a
1811 1816 ``url()`` function. The method for the form defaults to POST.
1812 1817
1813 1818 Options:
1814 1819
1815 1820 ``multipart``
1816 1821 If set to True, the enctype is set to "multipart/form-data".
1817 1822 ``method``
1818 1823 The method to use when submitting the form, usually either
1819 1824 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1820 1825 hidden input with name _method is added to simulate the verb
1821 1826 over POST.
1822 1827
1823 1828 """
1824 1829 from webhelpers.pylonslib.secure_form import insecure_form
1825 1830 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1826 1831 form = insecure_form(url, method, multipart, **attrs)
1827 1832 token = HTML.div(hidden(csrf_token_key, get_csrf_token()), style="display: none;")
1828 1833 return literal("%s\n%s" % (form, token))
1829 1834
1830 1835 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1831 1836 select_html = select(name, selected, options, **attrs)
1832 1837 select2 = """
1833 1838 <script>
1834 1839 $(document).ready(function() {
1835 1840 $('#%s').select2({
1836 1841 containerCssClass: 'drop-menu',
1837 1842 dropdownCssClass: 'drop-menu-dropdown',
1838 1843 dropdownAutoWidth: true%s
1839 1844 });
1840 1845 });
1841 1846 </script>
1842 1847 """
1843 1848 filter_option = """,
1844 1849 minimumResultsForSearch: -1
1845 1850 """
1846 1851 input_id = attrs.get('id') or name
1847 1852 filter_enabled = "" if enable_filter else filter_option
1848 1853 select_script = literal(select2 % (input_id, filter_enabled))
1849 1854
1850 1855 return literal(select_html+select_script)
1851 1856
1852 1857
1853 1858 def get_visual_attr(tmpl_context_var, attr_name):
1854 1859 """
1855 1860 A safe way to get a variable from visual variable of template context
1856 1861
1857 1862 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1858 1863 :param attr_name: name of the attribute we fetch from the c.visual
1859 1864 """
1860 1865 visual = getattr(tmpl_context_var, 'visual', None)
1861 1866 if not visual:
1862 1867 return
1863 1868 else:
1864 1869 return getattr(visual, attr_name, None)
1865 1870
1866 1871
1867 1872 def get_last_path_part(file_node):
1868 1873 if not file_node.path:
1869 1874 return u''
1870 1875
1871 1876 path = safe_unicode(file_node.path.split('/')[-1])
1872 1877 return u'../' + path
1873 1878
1874 1879
1875 1880 def route_path(*args, **kwds):
1876 1881 """
1877 1882 Wrapper around pyramids `route_path` function. It is used to generate
1878 1883 URLs from within pylons views or templates. This will be removed when
1879 1884 pyramid migration if finished.
1880 1885 """
1881 1886 req = get_current_request()
1882 1887 return req.route_path(*args, **kwds)
1883 1888
1884 1889
1885 1890 def resource_path(*args, **kwds):
1886 1891 """
1887 1892 Wrapper around pyramids `route_path` function. It is used to generate
1888 1893 URLs from within pylons views or templates. This will be removed when
1889 1894 pyramid migration if finished.
1890 1895 """
1891 1896 req = get_current_request()
1892 1897 return req.resource_path(*args, **kwds)
@@ -1,70 +1,76 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2015-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Disable VCS pages when VCS Server is not available
23 23 """
24 24
25 25 import logging
26 26 import re
27
27 from pyramid.httpexceptions import HTTPBadGateway
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31
32 class VCSServerUnavailable(HTTPBadGateway):
33 """ HTTP Exception class for when VCS Server is unavailable """
34 code = 502
35 title = 'VCS Server Required'
36 explanation = 'A VCS Server is required for this action. There is currently no VCS Server configured.'
37
32 38 class DisableVCSPagesWrapper(object):
33 39 """
34 Wrapper to disable all pages that require VCS Server to be running,
35 avoiding that errors explode to the user.
40 Pyramid view wrapper to disable all pages that require VCS Server to be
41 running, avoiding that errors explode to the user.
36 42
37 43 This Wrapper should be enabled only in case VCS Server is not available
38 44 for the instance.
39 45 """
40 46
41 47 VCS_NOT_REQUIRED = [
42 48 '^/$',
43 49 ('/_admin(?!/settings/mapping)(?!/my_account/repos)'
44 50 '(?!/create_repository)(?!/gists)(?!/notifications/)'
45 51 ),
46 52 ]
47 53 _REGEX_VCS_NOT_REQUIRED = [re.compile(path) for path in VCS_NOT_REQUIRED]
48 54
49 55 def _check_vcs_requirement(self, path_info):
50 56 """
51 57 Tries to match the current path to one of the safe URLs to be rendered.
52 58 Displays an error message in case
53 59 """
54 60 for regex in self._REGEX_VCS_NOT_REQUIRED:
55 61 safe_url = regex.match(path_info)
56 62 if safe_url:
57 63 return True
58 64
59 65 # Url is not safe to be rendered without VCS Server
60 66 log.debug('accessing: `%s` with VCS Server disabled', path_info)
61 67 return False
62 68
63 def __init__(self, app):
64 self.application = app
69 def __init__(self, handler):
70 self.handler = handler
65 71
66 def __call__(self, environ, start_response):
67 if not self._check_vcs_requirement(environ['PATH_INFO']):
68 environ['PATH_INFO'] = '/error/vcs_unavailable'
72 def __call__(self, context, request):
73 if not self._check_vcs_requirement(request.path):
74 raise VCSServerUnavailable('VCS Server is not available')
69 75
70 return self.application(environ, start_response)
76 return self.handler(context, request)
@@ -1,982 +1,990 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Utilities library for RhodeCode
23 23 """
24 24
25 25 import datetime
26 26 import decorator
27 27 import json
28 28 import logging
29 29 import os
30 30 import re
31 31 import shutil
32 32 import tempfile
33 33 import traceback
34 34 import tarfile
35 35 import warnings
36 from os.path import abspath
37 from os.path import dirname as dn, join as jn
36 from os.path import join as jn
38 37
39 38 import paste
40 39 import pkg_resources
41 40 from paste.script.command import Command, BadCommand
42 41 from webhelpers.text import collapse, remove_formatting, strip_tags
43 42 from mako import exceptions
43 from pyramid.threadlocal import get_current_registry
44 44
45 45 from rhodecode.lib.fakemod import create_module
46 46 from rhodecode.lib.vcs.backends.base import Config
47 47 from rhodecode.lib.vcs.exceptions import VCSError
48 48 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
49 49 from rhodecode.lib.utils2 import (
50 50 safe_str, safe_unicode, get_current_rhodecode_user, md5)
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.db import (
53 53 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 54 from rhodecode.model.meta import Session
55 from rhodecode.model.repo_group import RepoGroupModel
56 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
55
57 56
58 57 log = logging.getLogger(__name__)
59 58
60 59 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 60
62 61 _license_cache = None
63 62
64 63
65 64 def recursive_replace(str_, replace=' '):
66 65 """
67 66 Recursive replace of given sign to just one instance
68 67
69 68 :param str_: given string
70 69 :param replace: char to find and replace multiple instances
71 70
72 71 Examples::
73 72 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 73 'Mighty-Mighty-Bo-sstones'
75 74 """
76 75
77 76 if str_.find(replace * 2) == -1:
78 77 return str_
79 78 else:
80 79 str_ = str_.replace(replace * 2, replace)
81 80 return recursive_replace(str_, replace)
82 81
83 82
84 83 def repo_name_slug(value):
85 84 """
86 85 Return slug of name of repository
87 86 This function is called on each creation/modification
88 87 of repository to prevent bad names in repo
89 88 """
90 89
91 90 slug = remove_formatting(value)
92 91 slug = strip_tags(slug)
93 92
94 93 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 94 slug = slug.replace(c, '-')
96 95 slug = recursive_replace(slug, '-')
97 96 slug = collapse(slug, '-')
98 97 return slug
99 98
100 99
101 100 #==============================================================================
102 101 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
103 102 #==============================================================================
104 103 def get_repo_slug(request):
105 104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
106 105 if _repo:
107 106 _repo = _repo.rstrip('/')
108 107 return _repo
109 108
110 109
111 110 def get_repo_group_slug(request):
112 111 _group = request.environ['pylons.routes_dict'].get('group_name')
113 112 if _group:
114 113 _group = _group.rstrip('/')
115 114 return _group
116 115
117 116
118 117 def get_user_group_slug(request):
119 118 _group = request.environ['pylons.routes_dict'].get('user_group_id')
120 119 try:
121 120 _group = UserGroup.get(_group)
122 121 if _group:
123 122 _group = _group.users_group_name
124 123 except Exception:
125 124 log.debug(traceback.format_exc())
126 125 #catch all failures here
127 126 pass
128 127
129 128 return _group
130 129
131 130
132 131 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
133 132 """
134 133 Action logger for various actions made by users
135 134
136 135 :param user: user that made this action, can be a unique username string or
137 136 object containing user_id attribute
138 137 :param action: action to log, should be on of predefined unique actions for
139 138 easy translations
140 139 :param repo: string name of repository or object containing repo_id,
141 140 that action was made on
142 141 :param ipaddr: optional ip address from what the action was made
143 142 :param sa: optional sqlalchemy session
144 143
145 144 """
146 145
147 146 if not sa:
148 147 sa = meta.Session()
149 148 # if we don't get explicit IP address try to get one from registered user
150 149 # in tmpl context var
151 150 if not ipaddr:
152 151 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
153 152
154 153 try:
155 154 if getattr(user, 'user_id', None):
156 155 user_obj = User.get(user.user_id)
157 156 elif isinstance(user, basestring):
158 157 user_obj = User.get_by_username(user)
159 158 else:
160 159 raise Exception('You have to provide a user object or a username')
161 160
162 161 if getattr(repo, 'repo_id', None):
163 162 repo_obj = Repository.get(repo.repo_id)
164 163 repo_name = repo_obj.repo_name
165 164 elif isinstance(repo, basestring):
166 165 repo_name = repo.lstrip('/')
167 166 repo_obj = Repository.get_by_repo_name(repo_name)
168 167 else:
169 168 repo_obj = None
170 169 repo_name = ''
171 170
172 171 user_log = UserLog()
173 172 user_log.user_id = user_obj.user_id
174 173 user_log.username = user_obj.username
175 174 action = safe_unicode(action)
176 175 user_log.action = action[:1200000]
177 176
178 177 user_log.repository = repo_obj
179 178 user_log.repository_name = repo_name
180 179
181 180 user_log.action_date = datetime.datetime.now()
182 181 user_log.user_ip = ipaddr
183 182 sa.add(user_log)
184 183
185 184 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
186 185 action, safe_unicode(repo), user_obj, ipaddr)
187 186 if commit:
188 187 sa.commit()
189 188 except Exception:
190 189 log.error(traceback.format_exc())
191 190 raise
192 191
193 192
194 193 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
195 194 """
196 195 Scans given path for repos and return (name,(type,path)) tuple
197 196
198 197 :param path: path to scan for repositories
199 198 :param recursive: recursive search and return names with subdirs in front
200 199 """
201 200
202 201 # remove ending slash for better results
203 202 path = path.rstrip(os.sep)
204 203 log.debug('now scanning in %s location recursive:%s...', path, recursive)
205 204
206 205 def _get_repos(p):
207 206 dirpaths = _get_dirpaths(p)
208 207 if not _is_dir_writable(p):
209 208 log.warning('repo path without write access: %s', p)
210 209
211 210 for dirpath in dirpaths:
212 211 if os.path.isfile(os.path.join(p, dirpath)):
213 212 continue
214 213 cur_path = os.path.join(p, dirpath)
215 214
216 215 # skip removed repos
217 216 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
218 217 continue
219 218
220 219 #skip .<somethin> dirs
221 220 if dirpath.startswith('.'):
222 221 continue
223 222
224 223 try:
225 224 scm_info = get_scm(cur_path)
226 225 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
227 226 except VCSError:
228 227 if not recursive:
229 228 continue
230 229 #check if this dir containts other repos for recursive scan
231 230 rec_path = os.path.join(p, dirpath)
232 231 if os.path.isdir(rec_path):
233 232 for inner_scm in _get_repos(rec_path):
234 233 yield inner_scm
235 234
236 235 return _get_repos(path)
237 236
238 237
239 238 def _get_dirpaths(p):
240 239 try:
241 240 # OS-independable way of checking if we have at least read-only
242 241 # access or not.
243 242 dirpaths = os.listdir(p)
244 243 except OSError:
245 244 log.warning('ignoring repo path without read access: %s', p)
246 245 return []
247 246
248 247 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
249 248 # decode paths and suddenly returns unicode objects itself. The items it
250 249 # cannot decode are returned as strings and cause issues.
251 250 #
252 251 # Those paths are ignored here until a solid solution for path handling has
253 252 # been built.
254 253 expected_type = type(p)
255 254
256 255 def _has_correct_type(item):
257 256 if type(item) is not expected_type:
258 257 log.error(
259 258 u"Ignoring path %s since it cannot be decoded into unicode.",
260 259 # Using "repr" to make sure that we see the byte value in case
261 260 # of support.
262 261 repr(item))
263 262 return False
264 263 return True
265 264
266 265 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
267 266
268 267 return dirpaths
269 268
270 269
271 270 def _is_dir_writable(path):
272 271 """
273 272 Probe if `path` is writable.
274 273
275 274 Due to trouble on Cygwin / Windows, this is actually probing if it is
276 275 possible to create a file inside of `path`, stat does not produce reliable
277 276 results in this case.
278 277 """
279 278 try:
280 279 with tempfile.TemporaryFile(dir=path):
281 280 pass
282 281 except OSError:
283 282 return False
284 283 return True
285 284
286 285
287 286 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
288 287 """
289 288 Returns True if given path is a valid repository False otherwise.
290 289 If expect_scm param is given also, compare if given scm is the same
291 290 as expected from scm parameter. If explicit_scm is given don't try to
292 291 detect the scm, just use the given one to check if repo is valid
293 292
294 293 :param repo_name:
295 294 :param base_path:
296 295 :param expect_scm:
297 296 :param explicit_scm:
298 297
299 298 :return True: if given path is a valid repository
300 299 """
301 300 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
302 301 log.debug('Checking if `%s` is a valid path for repository', repo_name)
303 302
304 303 try:
305 304 if explicit_scm:
306 305 detected_scms = [get_scm_backend(explicit_scm)]
307 306 else:
308 307 detected_scms = get_scm(full_path)
309 308
310 309 if expect_scm:
311 310 return detected_scms[0] == expect_scm
312 311 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
313 312 return True
314 313 except VCSError:
315 314 log.debug('path: %s is not a valid repo !', full_path)
316 315 return False
317 316
318 317
319 318 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
320 319 """
321 320 Returns True if given path is a repository group, False otherwise
322 321
323 322 :param repo_name:
324 323 :param base_path:
325 324 """
326 325 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
327 326 log.debug('Checking if `%s` is a valid path for repository group',
328 327 repo_group_name)
329 328
330 329 # check if it's not a repo
331 330 if is_valid_repo(repo_group_name, base_path):
332 331 log.debug('Repo called %s exist, it is not a valid '
333 332 'repo group' % repo_group_name)
334 333 return False
335 334
336 335 try:
337 336 # we need to check bare git repos at higher level
338 337 # since we might match branches/hooks/info/objects or possible
339 338 # other things inside bare git repo
340 339 scm_ = get_scm(os.path.dirname(full_path))
341 340 log.debug('path: %s is a vcs object:%s, not valid '
342 341 'repo group' % (full_path, scm_))
343 342 return False
344 343 except VCSError:
345 344 pass
346 345
347 346 # check if it's a valid path
348 347 if skip_path_check or os.path.isdir(full_path):
349 348 log.debug('path: %s is a valid repo group !', full_path)
350 349 return True
351 350
352 351 log.debug('path: %s is not a valid repo group !', full_path)
353 352 return False
354 353
355 354
356 355 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
357 356 while True:
358 357 ok = raw_input(prompt)
359 358 if ok in ('y', 'ye', 'yes'):
360 359 return True
361 360 if ok in ('n', 'no', 'nop', 'nope'):
362 361 return False
363 362 retries = retries - 1
364 363 if retries < 0:
365 364 raise IOError
366 365 print complaint
367 366
368 367 # propagated from mercurial documentation
369 368 ui_sections = [
370 369 'alias', 'auth',
371 370 'decode/encode', 'defaults',
372 371 'diff', 'email',
373 372 'extensions', 'format',
374 373 'merge-patterns', 'merge-tools',
375 374 'hooks', 'http_proxy',
376 375 'smtp', 'patch',
377 376 'paths', 'profiling',
378 377 'server', 'trusted',
379 378 'ui', 'web', ]
380 379
381 380
382 381 def config_data_from_db(clear_session=True, repo=None):
383 382 """
384 383 Read the configuration data from the database and return configuration
385 384 tuples.
386 385 """
386 from rhodecode.model.settings import VcsSettingsModel
387
387 388 config = []
388 389
389 390 sa = meta.Session()
390 391 settings_model = VcsSettingsModel(repo=repo, sa=sa)
391 392
392 393 ui_settings = settings_model.get_ui_settings()
393 394
394 395 for setting in ui_settings:
395 396 if setting.active:
396 397 log.debug(
397 398 'settings ui from db: [%s] %s=%s',
398 399 setting.section, setting.key, setting.value)
399 400 config.append((
400 401 safe_str(setting.section), safe_str(setting.key),
401 402 safe_str(setting.value)))
402 403 if setting.key == 'push_ssl':
403 404 # force set push_ssl requirement to False, rhodecode
404 405 # handles that
405 406 config.append((
406 407 safe_str(setting.section), safe_str(setting.key), False))
407 408 if clear_session:
408 409 meta.Session.remove()
409 410
410 411 # TODO: mikhail: probably it makes no sense to re-read hooks information.
411 412 # It's already there and activated/deactivated
412 413 skip_entries = []
413 414 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
414 415 if 'pull' not in enabled_hook_classes:
415 416 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
416 417 if 'push' not in enabled_hook_classes:
417 418 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
418 419
419 420 config = [entry for entry in config if entry[:2] not in skip_entries]
420 421
421 422 return config
422 423
423 424
424 425 def make_db_config(clear_session=True, repo=None):
425 426 """
426 427 Create a :class:`Config` instance based on the values in the database.
427 428 """
428 429 config = Config()
429 430 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
430 431 for section, option, value in config_data:
431 432 config.set(section, option, value)
432 433 return config
433 434
434 435
435 436 def get_enabled_hook_classes(ui_settings):
436 437 """
437 438 Return the enabled hook classes.
438 439
439 440 :param ui_settings: List of ui_settings as returned
440 441 by :meth:`VcsSettingsModel.get_ui_settings`
441 442
442 443 :return: a list with the enabled hook classes. The order is not guaranteed.
443 444 :rtype: list
444 445 """
445 446 enabled_hooks = []
446 447 active_hook_keys = [
447 448 key for section, key, value, active in ui_settings
448 449 if section == 'hooks' and active]
449 450
450 451 hook_names = {
451 452 RhodeCodeUi.HOOK_PUSH: 'push',
452 453 RhodeCodeUi.HOOK_PULL: 'pull',
453 454 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
454 455 }
455 456
456 457 for key in active_hook_keys:
457 458 hook = hook_names.get(key)
458 459 if hook:
459 460 enabled_hooks.append(hook)
460 461
461 462 return enabled_hooks
462 463
463 464
464 465 def set_rhodecode_config(config):
465 466 """
466 467 Updates pylons config with new settings from database
467 468
468 469 :param config:
469 470 """
471 from rhodecode.model.settings import SettingsModel
470 472 app_settings = SettingsModel().get_all_settings()
471 473
472 474 for k, v in app_settings.items():
473 475 config[k] = v
474 476
475 477
476 478 def map_groups(path):
477 479 """
478 480 Given a full path to a repository, create all nested groups that this
479 481 repo is inside. This function creates parent-child relationships between
480 482 groups and creates default perms for all new groups.
481 483
482 484 :param paths: full path to repository
483 485 """
486 from rhodecode.model.repo_group import RepoGroupModel
484 487 sa = meta.Session()
485 488 groups = path.split(Repository.NAME_SEP)
486 489 parent = None
487 490 group = None
488 491
489 492 # last element is repo in nested groups structure
490 493 groups = groups[:-1]
491 494 rgm = RepoGroupModel(sa)
492 owner = User.get_first_admin()
495 owner = User.get_first_super_admin()
493 496 for lvl, group_name in enumerate(groups):
494 497 group_name = '/'.join(groups[:lvl] + [group_name])
495 498 group = RepoGroup.get_by_group_name(group_name)
496 499 desc = '%s group' % group_name
497 500
498 501 # skip folders that are now removed repos
499 502 if REMOVED_REPO_PAT.match(group_name):
500 503 break
501 504
502 505 if group is None:
503 506 log.debug('creating group level: %s group_name: %s',
504 507 lvl, group_name)
505 508 group = RepoGroup(group_name, parent)
506 509 group.group_description = desc
507 510 group.user = owner
508 511 sa.add(group)
509 512 perm_obj = rgm._create_default_perms(group)
510 513 sa.add(perm_obj)
511 514 sa.flush()
512 515
513 516 parent = group
514 517 return group
515 518
516 519
517 520 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
518 521 """
519 522 maps all repos given in initial_repo_list, non existing repositories
520 523 are created, if remove_obsolete is True it also checks for db entries
521 524 that are not in initial_repo_list and removes them.
522 525
523 526 :param initial_repo_list: list of repositories found by scanning methods
524 527 :param remove_obsolete: check for obsolete entries in database
525 528 """
526 529 from rhodecode.model.repo import RepoModel
527 530 from rhodecode.model.scm import ScmModel
531 from rhodecode.model.repo_group import RepoGroupModel
532 from rhodecode.model.settings import SettingsModel
533
528 534 sa = meta.Session()
529 535 repo_model = RepoModel()
530 user = User.get_first_admin()
536 user = User.get_first_super_admin()
531 537 added = []
532 538
533 539 # creation defaults
534 540 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
535 541 enable_statistics = defs.get('repo_enable_statistics')
536 542 enable_locking = defs.get('repo_enable_locking')
537 543 enable_downloads = defs.get('repo_enable_downloads')
538 544 private = defs.get('repo_private')
539 545
540 546 for name, repo in initial_repo_list.items():
541 547 group = map_groups(name)
542 548 unicode_name = safe_unicode(name)
543 549 db_repo = repo_model.get_by_repo_name(unicode_name)
544 550 # found repo that is on filesystem not in RhodeCode database
545 551 if not db_repo:
546 552 log.info('repository %s not found, creating now', name)
547 553 added.append(name)
548 554 desc = (repo.description
549 555 if repo.description != 'unknown'
550 556 else '%s repository' % name)
551 557
552 558 db_repo = repo_model._create_repo(
553 559 repo_name=name,
554 560 repo_type=repo.alias,
555 561 description=desc,
556 562 repo_group=getattr(group, 'group_id', None),
557 563 owner=user,
558 564 enable_locking=enable_locking,
559 565 enable_downloads=enable_downloads,
560 566 enable_statistics=enable_statistics,
561 567 private=private,
562 568 state=Repository.STATE_CREATED
563 569 )
564 570 sa.commit()
565 571 # we added that repo just now, and make sure we updated server info
566 572 if db_repo.repo_type == 'git':
567 573 git_repo = db_repo.scm_instance()
568 574 # update repository server-info
569 575 log.debug('Running update server info')
570 576 git_repo._update_server_info()
571 577
572 578 db_repo.update_commit_cache()
573 579
574 580 config = db_repo._config
575 581 config.set('extensions', 'largefiles', '')
576 582 ScmModel().install_hooks(
577 583 db_repo.scm_instance(config=config),
578 584 repo_type=db_repo.repo_type)
579 585
580 586 removed = []
581 587 if remove_obsolete:
582 588 # remove from database those repositories that are not in the filesystem
583 589 for repo in sa.query(Repository).all():
584 590 if repo.repo_name not in initial_repo_list.keys():
585 591 log.debug("Removing non-existing repository found in db `%s`",
586 592 repo.repo_name)
587 593 try:
588 594 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
589 595 sa.commit()
590 596 removed.append(repo.repo_name)
591 597 except Exception:
592 598 # don't hold further removals on error
593 599 log.error(traceback.format_exc())
594 600 sa.rollback()
595 601
596 602 def splitter(full_repo_name):
597 603 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
598 604 gr_name = None
599 605 if len(_parts) == 2:
600 606 gr_name = _parts[0]
601 607 return gr_name
602 608
603 609 initial_repo_group_list = [splitter(x) for x in
604 610 initial_repo_list.keys() if splitter(x)]
605 611
606 612 # remove from database those repository groups that are not in the
607 613 # filesystem due to parent child relationships we need to delete them
608 614 # in a specific order of most nested first
609 615 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
610 616 nested_sort = lambda gr: len(gr.split('/'))
611 617 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
612 618 if group_name not in initial_repo_group_list:
613 619 repo_group = RepoGroup.get_by_group_name(group_name)
614 620 if (repo_group.children.all() or
615 621 not RepoGroupModel().check_exist_filesystem(
616 622 group_name=group_name, exc_on_failure=False)):
617 623 continue
618 624
619 625 log.info(
620 626 'Removing non-existing repository group found in db `%s`',
621 627 group_name)
622 628 try:
623 629 RepoGroupModel(sa).delete(group_name, fs_remove=False)
624 630 sa.commit()
625 631 removed.append(group_name)
626 632 except Exception:
627 633 # don't hold further removals on error
628 634 log.exception(
629 635 'Unable to remove repository group `%s`',
630 636 group_name)
631 637 sa.rollback()
632 638 raise
633 639
634 640 return added, removed
635 641
636 642
637 643 def get_default_cache_settings(settings):
638 644 cache_settings = {}
639 645 for key in settings.keys():
640 646 for prefix in ['beaker.cache.', 'cache.']:
641 647 if key.startswith(prefix):
642 648 name = key.split(prefix)[1].strip()
643 649 cache_settings[name] = settings[key].strip()
644 650 return cache_settings
645 651
646 652
647 653 # set cache regions for beaker so celery can utilise it
648 654 def add_cache(settings):
649 655 from rhodecode.lib import caches
650 656 cache_settings = {'regions': None}
651 657 # main cache settings used as default ...
652 658 cache_settings.update(get_default_cache_settings(settings))
653 659
654 660 if cache_settings['regions']:
655 661 for region in cache_settings['regions'].split(','):
656 662 region = region.strip()
657 663 region_settings = {}
658 664 for key, value in cache_settings.items():
659 665 if key.startswith(region):
660 666 region_settings[key.split('.')[1]] = value
661 667
662 668 caches.configure_cache_region(
663 669 region, region_settings, cache_settings)
664 670
665 671
666 672 def load_rcextensions(root_path):
667 673 import rhodecode
668 674 from rhodecode.config import conf
669 675
670 676 path = os.path.join(root_path, 'rcextensions', '__init__.py')
671 677 if os.path.isfile(path):
672 678 rcext = create_module('rc', path)
673 679 EXT = rhodecode.EXTENSIONS = rcext
674 680 log.debug('Found rcextensions now loading %s...', rcext)
675 681
676 682 # Additional mappings that are not present in the pygments lexers
677 683 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
678 684
679 685 # auto check if the module is not missing any data, set to default if is
680 686 # this will help autoupdate new feature of rcext module
681 687 #from rhodecode.config import rcextensions
682 688 #for k in dir(rcextensions):
683 689 # if not k.startswith('_') and not hasattr(EXT, k):
684 690 # setattr(EXT, k, getattr(rcextensions, k))
685 691
686 692
687 693 def get_custom_lexer(extension):
688 694 """
689 695 returns a custom lexer if it is defined in rcextensions module, or None
690 696 if there's no custom lexer defined
691 697 """
692 698 import rhodecode
693 699 from pygments import lexers
694 700 # check if we didn't define this extension as other lexer
695 701 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
696 702 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
697 703 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
698 704 return lexers.get_lexer_by_name(_lexer_name)
699 705
700 706
701 707 #==============================================================================
702 708 # TEST FUNCTIONS AND CREATORS
703 709 #==============================================================================
704 def create_test_index(repo_location, config, full_index):
710 def create_test_index(repo_location, config):
705 711 """
706 Makes default test index
707
708 :param config: test config
709 :param full_index:
710 # start test server:
711 rcserver --with-vcsserver test.ini
712 Makes default test index.
713 """
714 import rc_testdata
712 715
713 # build index and store it in /tmp/rc/index:
714 rhodecode-index --force --api-host=http://vps1.dev:5000 --api-key=xxx --engine-location=/tmp/rc/index
715
716 # package and move new packages
717 tar -zcvf vcs_search_index.tar.gz -C /tmp/rc index
718 mv vcs_search_index.tar.gz rhodecode/tests/fixtures/
719
720 """
721 cur_dir = dn(dn(abspath(__file__)))
722 with tarfile.open(jn(cur_dir, 'tests', 'fixtures',
723 'vcs_search_index.tar.gz')) as tar:
724 tar.extractall(os.path.dirname(config['search.location']))
716 rc_testdata.extract_search_index(
717 'vcs_search_index', os.path.dirname(config['search.location']))
725 718
726 719
727 def create_test_env(repos_test_path, config):
720 def create_test_directory(test_path):
721 """
722 Create test directory if it doesn't exist.
728 723 """
729 Makes a fresh database and
730 installs test repository into tmp dir
724 if not os.path.isdir(test_path):
725 log.debug('Creating testdir %s', test_path)
726 os.makedirs(test_path)
727
728
729 def create_test_database(test_path, config):
730 """
731 Makes a fresh database.
731 732 """
732 733 from rhodecode.lib.db_manage import DbManage
733 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO, TESTS_TMP_PATH
734 734
735 735 # PART ONE create db
736 736 dbconf = config['sqlalchemy.db1.url']
737 737 log.debug('making test db %s', dbconf)
738 738
739 # create test dir if it doesn't exist
740 if not os.path.isdir(repos_test_path):
741 log.debug('Creating testdir %s', repos_test_path)
742 os.makedirs(repos_test_path)
743
744 739 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
745 740 tests=True, cli_args={'force_ask': True})
746 741 dbmanage.create_tables(override=True)
747 742 dbmanage.set_db_version()
748 743 # for tests dynamically set new root paths based on generated content
749 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
744 dbmanage.create_settings(dbmanage.config_prompt(test_path))
750 745 dbmanage.create_default_user()
751 746 dbmanage.create_test_admin_and_users()
752 747 dbmanage.create_permissions()
753 748 dbmanage.populate_default_permissions()
754 749 Session().commit()
755 # PART TWO make test repo
750
751
752 def create_test_repositories(test_path, config):
753 """
754 Creates test repositories in the temporary directory. Repositories are
755 extracted from archives within the rc_testdata package.
756 """
757 import rc_testdata
758 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
759
756 760 log.debug('making test vcs repositories')
757 761
758 762 idx_path = config['search.location']
759 763 data_path = config['cache_dir']
760 764
761 765 # clean index and data
762 766 if idx_path and os.path.exists(idx_path):
763 767 log.debug('remove %s', idx_path)
764 768 shutil.rmtree(idx_path)
765 769
766 770 if data_path and os.path.exists(data_path):
767 771 log.debug('remove %s', data_path)
768 772 shutil.rmtree(data_path)
769 773
770 # CREATE DEFAULT TEST REPOS
771 cur_dir = dn(dn(abspath(__file__)))
772 with tarfile.open(jn(cur_dir, 'tests', 'fixtures',
773 'vcs_test_hg.tar.gz')) as tar:
774 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
775
776 cur_dir = dn(dn(abspath(__file__)))
777 with tarfile.open(jn(cur_dir, 'tests', 'fixtures',
778 'vcs_test_git.tar.gz')) as tar:
779 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
774 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
775 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
780 776
781 777 # Note: Subversion is in the process of being integrated with the system,
782 778 # until we have a properly packed version of the test svn repository, this
783 779 # tries to copy over the repo from a package "rc_testdata"
784 import rc_testdata
785 780 svn_repo_path = rc_testdata.get_svn_repo_archive()
786 781 with tarfile.open(svn_repo_path) as tar:
787 tar.extractall(jn(TESTS_TMP_PATH, SVN_REPO))
782 tar.extractall(jn(test_path, SVN_REPO))
788 783
789 784
790 785 #==============================================================================
791 786 # PASTER COMMANDS
792 787 #==============================================================================
793 788 class BasePasterCommand(Command):
794 789 """
795 790 Abstract Base Class for paster commands.
796 791
797 792 The celery commands are somewhat aggressive about loading
798 793 celery.conf, and since our module sets the `CELERY_LOADER`
799 794 environment variable to our loader, we have to bootstrap a bit and
800 795 make sure we've had a chance to load the pylons config off of the
801 796 command line, otherwise everything fails.
802 797 """
803 798 min_args = 1
804 799 min_args_error = "Please provide a paster config file as an argument."
805 800 takes_config_file = 1
806 801 requires_config_file = True
807 802
808 803 def notify_msg(self, msg, log=False):
809 804 """Make a notification to user, additionally if logger is passed
810 805 it logs this action using given logger
811 806
812 807 :param msg: message that will be printed to user
813 808 :param log: logging instance, to use to additionally log this message
814 809
815 810 """
816 811 if log and isinstance(log, logging):
817 812 log(msg)
818 813
819 814 def run(self, args):
820 815 """
821 816 Overrides Command.run
822 817
823 818 Checks for a config file argument and loads it.
824 819 """
825 820 if len(args) < self.min_args:
826 821 raise BadCommand(
827 822 self.min_args_error % {'min_args': self.min_args,
828 823 'actual_args': len(args)})
829 824
830 825 # Decrement because we're going to lob off the first argument.
831 826 # @@ This is hacky
832 827 self.min_args -= 1
833 828 self.bootstrap_config(args[0])
834 829 self.update_parser()
835 830 return super(BasePasterCommand, self).run(args[1:])
836 831
837 832 def update_parser(self):
838 833 """
839 834 Abstract method. Allows for the class' parser to be updated
840 835 before the superclass' `run` method is called. Necessary to
841 836 allow options/arguments to be passed through to the underlying
842 837 celery command.
843 838 """
844 839 raise NotImplementedError("Abstract Method.")
845 840
846 841 def bootstrap_config(self, conf):
847 842 """
848 843 Loads the pylons configuration.
849 844 """
850 845 from pylons import config as pylonsconfig
851 846
852 847 self.path_to_ini_file = os.path.realpath(conf)
853 848 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
854 849 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
855 850
856 851 def _init_session(self):
857 852 """
858 853 Inits SqlAlchemy Session
859 854 """
860 855 logging.config.fileConfig(self.path_to_ini_file)
861 856 from pylons import config
862 857 from rhodecode.config.utils import initialize_database
863 858
864 859 # get to remove repos !!
865 860 add_cache(config)
866 861 initialize_database(config)
867 862
868 863
869 864 @decorator.decorator
870 865 def jsonify(func, *args, **kwargs):
871 866 """Action decorator that formats output for JSON
872 867
873 868 Given a function that will return content, this decorator will turn
874 869 the result into JSON, with a content-type of 'application/json' and
875 870 output it.
876 871
877 872 """
878 873 from pylons.decorators.util import get_pylons
879 874 from rhodecode.lib.ext_json import json
880 875 pylons = get_pylons(args)
881 876 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
882 877 data = func(*args, **kwargs)
883 878 if isinstance(data, (list, tuple)):
884 879 msg = "JSON responses with Array envelopes are susceptible to " \
885 880 "cross-site data leak attacks, see " \
886 881 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
887 882 warnings.warn(msg, Warning, 2)
888 883 log.warning(msg)
889 884 log.debug("Returning JSON wrapped action output")
890 885 return json.dumps(data, encoding='utf-8')
891 886
892 887
893 888 class PartialRenderer(object):
894 889 """
895 890 Partial renderer used to render chunks of html used in datagrids
896 891 use like::
897 892
898 893 _render = PartialRenderer('data_table/_dt_elements.html')
899 894 _render('quick_menu', args, kwargs)
900 895 PartialRenderer.h,
901 896 c,
902 897 _,
903 898 ungettext
904 899 are the template stuff initialized inside and can be re-used later
905 900
906 901 :param tmpl_name: template path relate to /templates/ dir
907 902 """
908 903
909 904 def __init__(self, tmpl_name):
910 905 import rhodecode
911 906 from pylons import request, tmpl_context as c
912 907 from pylons.i18n.translation import _, ungettext
913 908 from rhodecode.lib import helpers as h
914 909
915 910 self.tmpl_name = tmpl_name
916 911 self.rhodecode = rhodecode
917 912 self.c = c
918 913 self._ = _
919 914 self.ungettext = ungettext
920 915 self.h = h
921 916 self.request = request
922 917
923 918 def _mako_lookup(self):
924 919 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
925 920 return _tmpl_lookup.get_template(self.tmpl_name)
926 921
927 922 def _update_kwargs_for_render(self, kwargs):
928 923 """
929 924 Inject params required for Mako rendering
930 925 """
931 926 _kwargs = {
932 927 '_': self._,
933 928 'h': self.h,
934 929 'c': self.c,
935 930 'request': self.request,
936 931 'ungettext': self.ungettext,
937 932 }
938 933 _kwargs.update(kwargs)
939 934 return _kwargs
940 935
941 936 def _render_with_exc(self, render_func, args, kwargs):
942 937 try:
943 938 return render_func.render(*args, **kwargs)
944 939 except:
945 940 log.error(exceptions.text_error_template().render())
946 941 raise
947 942
948 943 def _get_template(self, template_obj, def_name):
949 944 if def_name:
950 945 tmpl = template_obj.get_def(def_name)
951 946 else:
952 947 tmpl = template_obj
953 948 return tmpl
954 949
955 950 def render(self, def_name, *args, **kwargs):
956 951 lookup_obj = self._mako_lookup()
957 952 tmpl = self._get_template(lookup_obj, def_name=def_name)
958 953 kwargs = self._update_kwargs_for_render(kwargs)
959 954 return self._render_with_exc(tmpl, args, kwargs)
960 955
961 956 def __call__(self, tmpl, *args, **kwargs):
962 957 return self.render(tmpl, *args, **kwargs)
963 958
964 959
965 960 def password_changed(auth_user, session):
966 961 if auth_user.username == User.DEFAULT_USER:
967 962 return False
968 963 password_hash = md5(auth_user.password) if auth_user.password else None
969 964 rhodecode_user = session.get('rhodecode_user', {})
970 965 session_password_hash = rhodecode_user.get('password', '')
971 966 return password_hash != session_password_hash
972 967
973 968
974 969 def read_opensource_licenses():
975 970 global _license_cache
976 971
977 972 if not _license_cache:
978 973 licenses = pkg_resources.resource_string(
979 'rhodecode.config', 'licenses.json')
974 'rhodecode', 'config/licenses.json')
980 975 _license_cache = json.loads(licenses)
981 976
982 977 return _license_cache
978
979
980 def get_registry(request):
981 """
982 Utility to get the pyramid registry from a request. During migration to
983 pyramid we sometimes want to use the pyramid registry from pylons context.
984 Therefore this utility returns `request.registry` for pyramid requests and
985 uses `get_current_registry()` for pylons requests.
986 """
987 try:
988 return request.registry
989 except AttributeError:
990 return get_current_registry()
@@ -1,853 +1,860 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Some simple helper functions
24 24 """
25 25
26 26
27 27 import collections
28 28 import datetime
29 29 import dateutil.relativedelta
30 30 import hashlib
31 31 import logging
32 32 import re
33 33 import sys
34 34 import time
35 35 import threading
36 36 import urllib
37 37 import urlobject
38 38 import uuid
39 39
40 40 import pygments.lexers
41 41 import sqlalchemy
42 42 import sqlalchemy.engine.url
43 43 import webob
44 44
45 45 import rhodecode
46 46
47 47
48 48 def md5(s):
49 49 return hashlib.md5(s).hexdigest()
50 50
51 51
52 52 def md5_safe(s):
53 53 return md5(safe_str(s))
54 54
55 55
56 56 def __get_lem():
57 57 """
58 58 Get language extension map based on what's inside pygments lexers
59 59 """
60 60 d = collections.defaultdict(lambda: [])
61 61
62 62 def __clean(s):
63 63 s = s.lstrip('*')
64 64 s = s.lstrip('.')
65 65
66 66 if s.find('[') != -1:
67 67 exts = []
68 68 start, stop = s.find('['), s.find(']')
69 69
70 70 for suffix in s[start + 1:stop]:
71 71 exts.append(s[:s.find('[')] + suffix)
72 72 return [e.lower() for e in exts]
73 73 else:
74 74 return [s.lower()]
75 75
76 76 for lx, t in sorted(pygments.lexers.LEXERS.items()):
77 77 m = map(__clean, t[-2])
78 78 if m:
79 79 m = reduce(lambda x, y: x + y, m)
80 80 for ext in m:
81 81 desc = lx.replace('Lexer', '')
82 82 d[ext].append(desc)
83 83
84 84 return dict(d)
85 85
86 86
87 87 def str2bool(_str):
88 88 """
89 89 returs True/False value from given string, it tries to translate the
90 90 string into boolean
91 91
92 92 :param _str: string value to translate into boolean
93 93 :rtype: boolean
94 94 :returns: boolean from given string
95 95 """
96 96 if _str is None:
97 97 return False
98 98 if _str in (True, False):
99 99 return _str
100 100 _str = str(_str).strip().lower()
101 101 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
102 102
103 103
104 104 def aslist(obj, sep=None, strip=True):
105 105 """
106 106 Returns given string separated by sep as list
107 107
108 108 :param obj:
109 109 :param sep:
110 110 :param strip:
111 111 """
112 112 if isinstance(obj, (basestring)):
113 113 lst = obj.split(sep)
114 114 if strip:
115 115 lst = [v.strip() for v in lst]
116 116 return lst
117 117 elif isinstance(obj, (list, tuple)):
118 118 return obj
119 119 elif obj is None:
120 120 return []
121 121 else:
122 122 return [obj]
123 123
124 124
125 125 def convert_line_endings(line, mode):
126 126 """
127 127 Converts a given line "line end" accordingly to given mode
128 128
129 129 Available modes are::
130 130 0 - Unix
131 131 1 - Mac
132 132 2 - DOS
133 133
134 134 :param line: given line to convert
135 135 :param mode: mode to convert to
136 136 :rtype: str
137 137 :return: converted line according to mode
138 138 """
139 139 if mode == 0:
140 140 line = line.replace('\r\n', '\n')
141 141 line = line.replace('\r', '\n')
142 142 elif mode == 1:
143 143 line = line.replace('\r\n', '\r')
144 144 line = line.replace('\n', '\r')
145 145 elif mode == 2:
146 146 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
147 147 return line
148 148
149 149
150 150 def detect_mode(line, default):
151 151 """
152 152 Detects line break for given line, if line break couldn't be found
153 153 given default value is returned
154 154
155 155 :param line: str line
156 156 :param default: default
157 157 :rtype: int
158 158 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
159 159 """
160 160 if line.endswith('\r\n'):
161 161 return 2
162 162 elif line.endswith('\n'):
163 163 return 0
164 164 elif line.endswith('\r'):
165 165 return 1
166 166 else:
167 167 return default
168 168
169 169
170 170 def safe_int(val, default=None):
171 171 """
172 172 Returns int() of val if val is not convertable to int use default
173 173 instead
174 174
175 175 :param val:
176 176 :param default:
177 177 """
178 178
179 179 try:
180 180 val = int(val)
181 181 except (ValueError, TypeError):
182 182 val = default
183 183
184 184 return val
185 185
186 186
187 187 def safe_unicode(str_, from_encoding=None):
188 188 """
189 189 safe unicode function. Does few trick to turn str_ into unicode
190 190
191 191 In case of UnicodeDecode error, we try to return it with encoding detected
192 192 by chardet library if it fails fallback to unicode with errors replaced
193 193
194 194 :param str_: string to decode
195 195 :rtype: unicode
196 196 :returns: unicode object
197 197 """
198 198 if isinstance(str_, unicode):
199 199 return str_
200 200
201 201 if not from_encoding:
202 202 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
203 203 'utf8'), sep=',')
204 204 from_encoding = DEFAULT_ENCODINGS
205 205
206 206 if not isinstance(from_encoding, (list, tuple)):
207 207 from_encoding = [from_encoding]
208 208
209 209 try:
210 210 return unicode(str_)
211 211 except UnicodeDecodeError:
212 212 pass
213 213
214 214 for enc in from_encoding:
215 215 try:
216 216 return unicode(str_, enc)
217 217 except UnicodeDecodeError:
218 218 pass
219 219
220 220 try:
221 221 import chardet
222 222 encoding = chardet.detect(str_)['encoding']
223 223 if encoding is None:
224 224 raise Exception()
225 225 return str_.decode(encoding)
226 226 except (ImportError, UnicodeDecodeError, Exception):
227 227 return unicode(str_, from_encoding[0], 'replace')
228 228
229 229
230 230 def safe_str(unicode_, to_encoding=None):
231 231 """
232 232 safe str function. Does few trick to turn unicode_ into string
233 233
234 234 In case of UnicodeEncodeError, we try to return it with encoding detected
235 235 by chardet library if it fails fallback to string with errors replaced
236 236
237 237 :param unicode_: unicode to encode
238 238 :rtype: str
239 239 :returns: str object
240 240 """
241 241
242 242 # if it's not basestr cast to str
243 243 if not isinstance(unicode_, basestring):
244 244 return str(unicode_)
245 245
246 246 if isinstance(unicode_, str):
247 247 return unicode_
248 248
249 249 if not to_encoding:
250 250 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
251 251 'utf8'), sep=',')
252 252 to_encoding = DEFAULT_ENCODINGS
253 253
254 254 if not isinstance(to_encoding, (list, tuple)):
255 255 to_encoding = [to_encoding]
256 256
257 257 for enc in to_encoding:
258 258 try:
259 259 return unicode_.encode(enc)
260 260 except UnicodeEncodeError:
261 261 pass
262 262
263 263 try:
264 264 import chardet
265 265 encoding = chardet.detect(unicode_)['encoding']
266 266 if encoding is None:
267 267 raise UnicodeEncodeError()
268 268
269 269 return unicode_.encode(encoding)
270 270 except (ImportError, UnicodeEncodeError):
271 271 return unicode_.encode(to_encoding[0], 'replace')
272 272
273 273
274 274 def remove_suffix(s, suffix):
275 275 if s.endswith(suffix):
276 276 s = s[:-1 * len(suffix)]
277 277 return s
278 278
279 279
280 280 def remove_prefix(s, prefix):
281 281 if s.startswith(prefix):
282 282 s = s[len(prefix):]
283 283 return s
284 284
285 285
286 286 def find_calling_context(ignore_modules=None):
287 287 """
288 288 Look through the calling stack and return the frame which called
289 289 this function and is part of core module ( ie. rhodecode.* )
290 290
291 291 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
292 292 """
293 293
294 294 ignore_modules = ignore_modules or []
295 295
296 296 f = sys._getframe(2)
297 297 while f.f_back is not None:
298 298 name = f.f_globals.get('__name__')
299 299 if name and name.startswith(__name__.split('.')[0]):
300 300 if name not in ignore_modules:
301 301 return f
302 302 f = f.f_back
303 303 return None
304 304
305 305
306 306 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
307 307 """Custom engine_from_config functions."""
308 308 log = logging.getLogger('sqlalchemy.engine')
309 309 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
310 310
311 311 def color_sql(sql):
312 312 color_seq = '\033[1;33m' # This is yellow: code 33
313 313 normal = '\x1b[0m'
314 314 return ''.join([color_seq, sql, normal])
315 315
316 316 if configuration['debug']:
317 317 # attach events only for debug configuration
318 318
319 319 def before_cursor_execute(conn, cursor, statement,
320 320 parameters, context, executemany):
321 321 setattr(conn, 'query_start_time', time.time())
322 322 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
323 323 calling_context = find_calling_context(ignore_modules=[
324 'rhodecode.lib.caching_query'
324 'rhodecode.lib.caching_query',
325 'rhodecode.model.settings',
325 326 ])
326 327 if calling_context:
327 328 log.info(color_sql('call context %s:%s' % (
328 329 calling_context.f_code.co_filename,
329 330 calling_context.f_lineno,
330 331 )))
331 332
332 333 def after_cursor_execute(conn, cursor, statement,
333 334 parameters, context, executemany):
334 335 delattr(conn, 'query_start_time')
335 336
336 337 sqlalchemy.event.listen(engine, "before_cursor_execute",
337 338 before_cursor_execute)
338 339 sqlalchemy.event.listen(engine, "after_cursor_execute",
339 340 after_cursor_execute)
340 341
341 342 return engine
342 343
343 344
345 def get_encryption_key(config):
346 secret = config.get('rhodecode.encrypted_values.secret')
347 default = config['beaker.session.secret']
348 return secret or default
349
350
344 351 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
345 352 short_format=False):
346 353 """
347 354 Turns a datetime into an age string.
348 355 If show_short_version is True, this generates a shorter string with
349 356 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
350 357
351 358 * IMPORTANT*
352 359 Code of this function is written in special way so it's easier to
353 360 backport it to javascript. If you mean to update it, please also update
354 361 `jquery.timeago-extension.js` file
355 362
356 363 :param prevdate: datetime object
357 364 :param now: get current time, if not define we use
358 365 `datetime.datetime.now()`
359 366 :param show_short_version: if it should approximate the date and
360 367 return a shorter string
361 368 :param show_suffix:
362 369 :param short_format: show short format, eg 2D instead of 2 days
363 370 :rtype: unicode
364 371 :returns: unicode words describing age
365 372 """
366 373 from pylons.i18n.translation import _, ungettext
367 374
368 375 def _get_relative_delta(now, prevdate):
369 376 base = dateutil.relativedelta.relativedelta(now, prevdate)
370 377 return {
371 378 'year': base.years,
372 379 'month': base.months,
373 380 'day': base.days,
374 381 'hour': base.hours,
375 382 'minute': base.minutes,
376 383 'second': base.seconds,
377 384 }
378 385
379 386 def _is_leap_year(year):
380 387 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
381 388
382 389 def get_month(prevdate):
383 390 return prevdate.month
384 391
385 392 def get_year(prevdate):
386 393 return prevdate.year
387 394
388 395 now = now or datetime.datetime.now()
389 396 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
390 397 deltas = {}
391 398 future = False
392 399
393 400 if prevdate > now:
394 401 now_old = now
395 402 now = prevdate
396 403 prevdate = now_old
397 404 future = True
398 405 if future:
399 406 prevdate = prevdate.replace(microsecond=0)
400 407 # Get date parts deltas
401 408 for part in order:
402 409 rel_delta = _get_relative_delta(now, prevdate)
403 410 deltas[part] = rel_delta[part]
404 411
405 412 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
406 413 # not 1 hour, -59 minutes and -59 seconds)
407 414 offsets = [[5, 60], [4, 60], [3, 24]]
408 415 for element in offsets: # seconds, minutes, hours
409 416 num = element[0]
410 417 length = element[1]
411 418
412 419 part = order[num]
413 420 carry_part = order[num - 1]
414 421
415 422 if deltas[part] < 0:
416 423 deltas[part] += length
417 424 deltas[carry_part] -= 1
418 425
419 426 # Same thing for days except that the increment depends on the (variable)
420 427 # number of days in the month
421 428 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
422 429 if deltas['day'] < 0:
423 430 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
424 431 deltas['day'] += 29
425 432 else:
426 433 deltas['day'] += month_lengths[get_month(prevdate) - 1]
427 434
428 435 deltas['month'] -= 1
429 436
430 437 if deltas['month'] < 0:
431 438 deltas['month'] += 12
432 439 deltas['year'] -= 1
433 440
434 441 # Format the result
435 442 if short_format:
436 443 fmt_funcs = {
437 444 'year': lambda d: u'%dy' % d,
438 445 'month': lambda d: u'%dm' % d,
439 446 'day': lambda d: u'%dd' % d,
440 447 'hour': lambda d: u'%dh' % d,
441 448 'minute': lambda d: u'%dmin' % d,
442 449 'second': lambda d: u'%dsec' % d,
443 450 }
444 451 else:
445 452 fmt_funcs = {
446 453 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
447 454 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
448 455 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
449 456 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
450 457 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
451 458 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
452 459 }
453 460
454 461 i = 0
455 462 for part in order:
456 463 value = deltas[part]
457 464 if value != 0:
458 465
459 466 if i < 5:
460 467 sub_part = order[i + 1]
461 468 sub_value = deltas[sub_part]
462 469 else:
463 470 sub_value = 0
464 471
465 472 if sub_value == 0 or show_short_version:
466 473 _val = fmt_funcs[part](value)
467 474 if future:
468 475 if show_suffix:
469 476 return _(u'in %s') % _val
470 477 else:
471 478 return _val
472 479
473 480 else:
474 481 if show_suffix:
475 482 return _(u'%s ago') % _val
476 483 else:
477 484 return _val
478 485
479 486 val = fmt_funcs[part](value)
480 487 val_detail = fmt_funcs[sub_part](sub_value)
481 488
482 489 if short_format:
483 490 datetime_tmpl = u'%s, %s'
484 491 if show_suffix:
485 492 datetime_tmpl = _(u'%s, %s ago')
486 493 if future:
487 494 datetime_tmpl = _(u'in %s, %s')
488 495 else:
489 496 datetime_tmpl = _(u'%s and %s')
490 497 if show_suffix:
491 498 datetime_tmpl = _(u'%s and %s ago')
492 499 if future:
493 500 datetime_tmpl = _(u'in %s and %s')
494 501
495 502 return datetime_tmpl % (val, val_detail)
496 503 i += 1
497 504 return _(u'just now')
498 505
499 506
500 507 def uri_filter(uri):
501 508 """
502 509 Removes user:password from given url string
503 510
504 511 :param uri:
505 512 :rtype: unicode
506 513 :returns: filtered list of strings
507 514 """
508 515 if not uri:
509 516 return ''
510 517
511 518 proto = ''
512 519
513 520 for pat in ('https://', 'http://'):
514 521 if uri.startswith(pat):
515 522 uri = uri[len(pat):]
516 523 proto = pat
517 524 break
518 525
519 526 # remove passwords and username
520 527 uri = uri[uri.find('@') + 1:]
521 528
522 529 # get the port
523 530 cred_pos = uri.find(':')
524 531 if cred_pos == -1:
525 532 host, port = uri, None
526 533 else:
527 534 host, port = uri[:cred_pos], uri[cred_pos + 1:]
528 535
529 536 return filter(None, [proto, host, port])
530 537
531 538
532 539 def credentials_filter(uri):
533 540 """
534 541 Returns a url with removed credentials
535 542
536 543 :param uri:
537 544 """
538 545
539 546 uri = uri_filter(uri)
540 547 # check if we have port
541 548 if len(uri) > 2 and uri[2]:
542 549 uri[2] = ':' + uri[2]
543 550
544 551 return ''.join(uri)
545 552
546 553
547 554 def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
548 555 parsed_url = urlobject.URLObject(qualifed_home_url)
549 556 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
550 557 args = {
551 558 'scheme': parsed_url.scheme,
552 559 'user': '',
553 560 # path if we use proxy-prefix
554 561 'netloc': parsed_url.netloc+decoded_path,
555 562 'prefix': decoded_path,
556 563 'repo': repo_name,
557 564 'repoid': str(repo_id)
558 565 }
559 566 args.update(override)
560 567 args['user'] = urllib.quote(safe_str(args['user']))
561 568
562 569 for k, v in args.items():
563 570 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
564 571
565 572 # remove leading @ sign if it's present. Case of empty user
566 573 url_obj = urlobject.URLObject(uri_tmpl)
567 574 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
568 575
569 576 return safe_unicode(url)
570 577
571 578
572 579 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
573 580 """
574 581 Safe version of get_commit if this commit doesn't exists for a
575 582 repository it returns a Dummy one instead
576 583
577 584 :param repo: repository instance
578 585 :param commit_id: commit id as str
579 586 :param pre_load: optional list of commit attributes to load
580 587 """
581 588 # TODO(skreft): remove these circular imports
582 589 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
583 590 from rhodecode.lib.vcs.exceptions import RepositoryError
584 591 if not isinstance(repo, BaseRepository):
585 592 raise Exception('You must pass an Repository '
586 593 'object as first argument got %s', type(repo))
587 594
588 595 try:
589 596 commit = repo.get_commit(
590 597 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
591 598 except (RepositoryError, LookupError):
592 599 commit = EmptyCommit()
593 600 return commit
594 601
595 602
596 603 def datetime_to_time(dt):
597 604 if dt:
598 605 return time.mktime(dt.timetuple())
599 606
600 607
601 608 def time_to_datetime(tm):
602 609 if tm:
603 610 if isinstance(tm, basestring):
604 611 try:
605 612 tm = float(tm)
606 613 except ValueError:
607 614 return
608 615 return datetime.datetime.fromtimestamp(tm)
609 616
610 617
611 618 def time_to_utcdatetime(tm):
612 619 if tm:
613 620 if isinstance(tm, basestring):
614 621 try:
615 622 tm = float(tm)
616 623 except ValueError:
617 624 return
618 625 return datetime.datetime.utcfromtimestamp(tm)
619 626
620 627
621 628 MENTIONS_REGEX = re.compile(
622 629 # ^@ or @ without any special chars in front
623 630 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
624 631 # main body starts with letter, then can be . - _
625 632 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
626 633 re.VERBOSE | re.MULTILINE)
627 634
628 635
629 636 def extract_mentioned_users(s):
630 637 """
631 638 Returns unique usernames from given string s that have @mention
632 639
633 640 :param s: string to get mentions
634 641 """
635 642 usrs = set()
636 643 for username in MENTIONS_REGEX.findall(s):
637 644 usrs.add(username)
638 645
639 646 return sorted(list(usrs), key=lambda k: k.lower())
640 647
641 648
642 649 class AttributeDict(dict):
643 650 def __getattr__(self, attr):
644 651 return self.get(attr, None)
645 652 __setattr__ = dict.__setitem__
646 653 __delattr__ = dict.__delitem__
647 654
648 655
649 656 def fix_PATH(os_=None):
650 657 """
651 658 Get current active python path, and append it to PATH variable to fix
652 659 issues of subprocess calls and different python versions
653 660 """
654 661 if os_ is None:
655 662 import os
656 663 else:
657 664 os = os_
658 665
659 666 cur_path = os.path.split(sys.executable)[0]
660 667 if not os.environ['PATH'].startswith(cur_path):
661 668 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
662 669
663 670
664 671 def obfuscate_url_pw(engine):
665 672 _url = engine or ''
666 673 try:
667 674 _url = sqlalchemy.engine.url.make_url(engine)
668 675 if _url.password:
669 676 _url.password = 'XXXXX'
670 677 except Exception:
671 678 pass
672 679 return unicode(_url)
673 680
674 681
675 682 def get_server_url(environ):
676 683 req = webob.Request(environ)
677 684 return req.host_url + req.script_name
678 685
679 686
680 687 def unique_id(hexlen=32):
681 688 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
682 689 return suuid(truncate_to=hexlen, alphabet=alphabet)
683 690
684 691
685 692 def suuid(url=None, truncate_to=22, alphabet=None):
686 693 """
687 694 Generate and return a short URL safe UUID.
688 695
689 696 If the url parameter is provided, set the namespace to the provided
690 697 URL and generate a UUID.
691 698
692 699 :param url to get the uuid for
693 700 :truncate_to: truncate the basic 22 UUID to shorter version
694 701
695 702 The IDs won't be universally unique any longer, but the probability of
696 703 a collision will still be very low.
697 704 """
698 705 # Define our alphabet.
699 706 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
700 707
701 708 # If no URL is given, generate a random UUID.
702 709 if url is None:
703 710 unique_id = uuid.uuid4().int
704 711 else:
705 712 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
706 713
707 714 alphabet_length = len(_ALPHABET)
708 715 output = []
709 716 while unique_id > 0:
710 717 digit = unique_id % alphabet_length
711 718 output.append(_ALPHABET[digit])
712 719 unique_id = int(unique_id / alphabet_length)
713 720 return "".join(output)[:truncate_to]
714 721
715 722
716 723 def get_current_rhodecode_user():
717 724 """
718 725 Gets rhodecode user from threadlocal tmpl_context variable if it's
719 726 defined, else returns None.
720 727 """
721 728 from pylons import tmpl_context as c
722 729 if hasattr(c, 'rhodecode_user'):
723 730 return c.rhodecode_user
724 731
725 732 return None
726 733
727 734
728 735 def action_logger_generic(action, namespace=''):
729 736 """
730 737 A generic logger for actions useful to the system overview, tries to find
731 738 an acting user for the context of the call otherwise reports unknown user
732 739
733 740 :param action: logging message eg 'comment 5 deleted'
734 741 :param type: string
735 742
736 743 :param namespace: namespace of the logging message eg. 'repo.comments'
737 744 :param type: string
738 745
739 746 """
740 747
741 748 logger_name = 'rhodecode.actions'
742 749
743 750 if namespace:
744 751 logger_name += '.' + namespace
745 752
746 753 log = logging.getLogger(logger_name)
747 754
748 755 # get a user if we can
749 756 user = get_current_rhodecode_user()
750 757
751 758 logfunc = log.info
752 759
753 760 if not user:
754 761 user = '<unknown user>'
755 762 logfunc = log.warning
756 763
757 764 logfunc('Logging action by {}: {}'.format(user, action))
758 765
759 766
760 767 def escape_split(text, sep=',', maxsplit=-1):
761 768 r"""
762 769 Allows for escaping of the separator: e.g. arg='foo\, bar'
763 770
764 771 It should be noted that the way bash et. al. do command line parsing, those
765 772 single quotes are required.
766 773 """
767 774 escaped_sep = r'\%s' % sep
768 775
769 776 if escaped_sep not in text:
770 777 return text.split(sep, maxsplit)
771 778
772 779 before, _mid, after = text.partition(escaped_sep)
773 780 startlist = before.split(sep, maxsplit) # a regular split is fine here
774 781 unfinished = startlist[-1]
775 782 startlist = startlist[:-1]
776 783
777 784 # recurse because there may be more escaped separators
778 785 endlist = escape_split(after, sep, maxsplit)
779 786
780 787 # finish building the escaped value. we use endlist[0] becaue the first
781 788 # part of the string sent in recursion is the rest of the escaped value.
782 789 unfinished += sep + endlist[0]
783 790
784 791 return startlist + [unfinished] + endlist[1:] # put together all the parts
785 792
786 793
787 794 class OptionalAttr(object):
788 795 """
789 796 Special Optional Option that defines other attribute. Example::
790 797
791 798 def test(apiuser, userid=Optional(OAttr('apiuser')):
792 799 user = Optional.extract(userid)
793 800 # calls
794 801
795 802 """
796 803
797 804 def __init__(self, attr_name):
798 805 self.attr_name = attr_name
799 806
800 807 def __repr__(self):
801 808 return '<OptionalAttr:%s>' % self.attr_name
802 809
803 810 def __call__(self):
804 811 return self
805 812
806 813
807 814 # alias
808 815 OAttr = OptionalAttr
809 816
810 817
811 818 class Optional(object):
812 819 """
813 820 Defines an optional parameter::
814 821
815 822 param = param.getval() if isinstance(param, Optional) else param
816 823 param = param() if isinstance(param, Optional) else param
817 824
818 825 is equivalent of::
819 826
820 827 param = Optional.extract(param)
821 828
822 829 """
823 830
824 831 def __init__(self, type_):
825 832 self.type_ = type_
826 833
827 834 def __repr__(self):
828 835 return '<Optional:%s>' % self.type_.__repr__()
829 836
830 837 def __call__(self):
831 838 return self.getval()
832 839
833 840 def getval(self):
834 841 """
835 842 returns value from this Optional instance
836 843 """
837 844 if isinstance(self.type_, OAttr):
838 845 # use params name
839 846 return self.type_.attr_name
840 847 return self.type_
841 848
842 849 @classmethod
843 850 def extract(cls, val):
844 851 """
845 852 Extracts value from Optional() instance
846 853
847 854 :param val:
848 855 :return: original value if it's not Optional instance else
849 856 value of instance
850 857 """
851 858 if isinstance(val, cls):
852 859 return val.getval()
853 860 return val
@@ -1,275 +1,278 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Various version Control System version lib (vcs) management abstraction layer
23 23 for Python. Build with server client architecture.
24 24 """
25 25
26 26
27 27 VERSION = (0, 5, 0, 'dev')
28 28
29 29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 30
31 31 __all__ = [
32 32 'get_version', 'get_repo', 'get_backend',
33 33 'VCSError', 'RepositoryError', 'CommitError'
34 34 ]
35 35
36 36 import atexit
37 37 import logging
38 38 import subprocess
39 39 import time
40 40 import urlparse
41 41 from cStringIO import StringIO
42 42
43 43 import pycurl
44 44 import Pyro4
45 45 from Pyro4.errors import CommunicationError
46 46
47 47 from rhodecode.lib.vcs.conf import settings
48 48 from rhodecode.lib.vcs.backends import get_repo, get_backend
49 49 from rhodecode.lib.vcs.exceptions import (
50 50 VCSError, RepositoryError, CommitError)
51 51
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 def get_version():
57 57 """
58 58 Returns shorter version (digit parts only) as string.
59 59 """
60 60 return '.'.join((str(each) for each in VERSION[:3]))
61 61
62 62
63 63 def connect_pyro4(server_and_port):
64 64 from rhodecode.lib.vcs import connection, client
65 65 from rhodecode.lib.middleware.utils import scm_app
66 66
67 67 git_remote = client.ThreadlocalProxyFactory(
68 68 settings.pyro_remote(settings.PYRO_GIT, server_and_port))
69 69 hg_remote = client.ThreadlocalProxyFactory(
70 70 settings.pyro_remote(settings.PYRO_HG, server_and_port))
71 71 svn_remote = client.ThreadlocalProxyFactory(
72 72 settings.pyro_remote(settings.PYRO_SVN, server_and_port))
73 73
74 74 connection.Git = client.RepoMaker(proxy_factory=git_remote)
75 75 connection.Hg = client.RepoMaker(proxy_factory=hg_remote)
76 76 connection.Svn = client.RepoMaker(proxy_factory=svn_remote)
77 77
78 78 scm_app.GIT_REMOTE_WSGI = Pyro4.Proxy(
79 79 settings.pyro_remote(
80 80 settings.PYRO_GIT_REMOTE_WSGI, server_and_port))
81 81 scm_app.HG_REMOTE_WSGI = Pyro4.Proxy(
82 82 settings.pyro_remote(
83 83 settings.PYRO_HG_REMOTE_WSGI, server_and_port))
84 84
85 85 @atexit.register
86 86 def free_connection_resources():
87 87 connection.Git = None
88 88 connection.Hg = None
89 89 connection.Svn = None
90 90
91 91
92 92 def connect_http(server_and_port):
93 93 from rhodecode.lib.vcs import connection, client_http
94 94 from rhodecode.lib.middleware.utils import scm_app
95 95
96 session = _create_http_rpc_session()
96 session_factory = client_http.ThreadlocalSessionFactory()
97 97
98 connection.Git = client_http.RepoMaker(server_and_port, '/git', session)
99 connection.Hg = client_http.RepoMaker(server_and_port, '/hg', session)
100 connection.Svn = client_http.RepoMaker(server_and_port, '/svn', session)
98 connection.Git = client_http.RepoMaker(
99 server_and_port, '/git', session_factory)
100 connection.Hg = client_http.RepoMaker(
101 server_and_port, '/hg', session_factory)
102 connection.Svn = client_http.RepoMaker(
103 server_and_port, '/svn', session_factory)
101 104
102 105 scm_app.HG_REMOTE_WSGI = client_http.VcsHttpProxy(
103 106 server_and_port, '/proxy/hg')
104 107 scm_app.GIT_REMOTE_WSGI = client_http.VcsHttpProxy(
105 108 server_and_port, '/proxy/git')
106 109
107 110 @atexit.register
108 111 def free_connection_resources():
109 112 connection.Git = None
110 113 connection.Hg = None
111 114 connection.Svn = None
112 115
113 116
114 117 def connect_vcs(server_and_port, protocol='pyro4'):
115 118 """
116 119 Initializes the connection to the vcs server.
117 120
118 121 :param server_and_port: str, e.g. "localhost:9900"
119 122 :param protocol: str, "pyro4" or "http"
120 123 """
121 124 if protocol == 'pyro4':
122 125 connect_pyro4(server_and_port)
123 126 elif protocol == 'http':
124 127 connect_http(server_and_port)
125 128
126 129
127 130 # TODO: johbo: This function should be moved into our test suite, there is
128 131 # no reason to support starting the vcsserver in Enterprise itself.
129 132 def start_vcs_server(server_and_port, protocol='pyro4', log_level=None):
130 133 """
131 134 Starts the vcs server in a subprocess.
132 135 """
133 136 log.info('Starting VCSServer as a sub process with %s protocol', protocol)
134 137 if protocol == 'http':
135 138 return _start_http_vcs_server(server_and_port, log_level)
136 139 elif protocol == 'pyro4':
137 140 return _start_pyro4_vcs_server(server_and_port, log_level)
138 141
139 142
140 143 def _start_pyro4_vcs_server(server_and_port, log_level=None):
141 144 _try_to_shutdown_running_server(server_and_port)
142 145 host, port = server_and_port.rsplit(":", 1)
143 146 host = host.strip('[]')
144 147 args = [
145 148 'vcsserver', '--port', port, '--host', host, '--locale', 'en_US.UTF-8',
146 149 '--threadpool', '32']
147 150 if log_level:
148 151 args += ['--log-level', log_level]
149 152 proc = subprocess.Popen(args)
150 153
151 154 def cleanup_server_process():
152 155 proc.kill()
153 156 atexit.register(cleanup_server_process)
154 157
155 158 server = create_vcsserver_proxy(server_and_port, protocol='pyro4')
156 159 _wait_until_vcs_server_is_reachable(server)
157 160
158 161
159 162 def _start_http_vcs_server(server_and_port, log_level=None):
160 163 # TODO: mikhail: shutdown if an http server already runs
161 164
162 165 host, port = server_and_port.rsplit(":", 1)
163 166 args = [
164 167 'pserve', 'vcsserver/development_pyramid.ini',
165 168 'http_port=%s' % (port, ), 'http_host=%s' % (host, )]
166 169 proc = subprocess.Popen(args)
167 170
168 171 def cleanup_server_process():
169 172 proc.kill()
170 173 atexit.register(cleanup_server_process)
171 174
172 175 server = create_vcsserver_proxy(server_and_port, protocol='http')
173 176 _wait_until_vcs_server_is_reachable(server)
174 177
175 178
176 179 def _wait_until_vcs_server_is_reachable(server):
177 180 while xrange(80): # max 40s of sleep
178 181 try:
179 182 server.ping()
180 183 break
181 184 except (CommunicationError, pycurl.error):
182 185 pass
183 186 time.sleep(0.5)
184 187
185 188
186 189 def _try_to_shutdown_running_server(server_and_port):
187 190 server = create_vcsserver_proxy(server_and_port)
188 191 try:
189 192 server.shutdown()
190 193 except (CommunicationError, pycurl.error):
191 194 return
192 195
193 196 # TODO: Not sure why this is important, but without it the following start
194 197 # of the server fails.
195 198 server = create_vcsserver_proxy(server_and_port)
196 199 server.ping()
197 200
198 201
199 202 def create_vcsserver_proxy(server_and_port, protocol='pyro4'):
200 203 if protocol == 'pyro4':
201 204 return _create_vcsserver_proxy_pyro4(server_and_port)
202 205 elif protocol == 'http':
203 206 return _create_vcsserver_proxy_http(server_and_port)
204 207
205 208
206 209 def _create_vcsserver_proxy_pyro4(server_and_port):
207 210 server = Pyro4.Proxy(
208 211 settings.pyro_remote(settings.PYRO_VCSSERVER, server_and_port))
209 212 return server
210 213
211 214
212 215 def _create_vcsserver_proxy_http(server_and_port):
213 216 from rhodecode.lib.vcs import client_http
214 217
215 218 session = _create_http_rpc_session()
216 219 url = urlparse.urljoin('http://%s' % server_and_port, '/server')
217 220 return client_http.RemoteObject(url, session)
218 221
219 222
220 223 class CurlSession(object):
221 224 """
222 225 Modeled so that it provides a subset of the requests interface.
223 226
224 227 This has been created so that it does only provide a minimal API for our
225 228 needs. The parts which it provides are based on the API of the library
226 229 `requests` which allows us to easily benchmark against it.
227 230
228 231 Please have a look at the class :class:`requests.Session` when you extend
229 232 it.
230 233 """
231 234
232 235 def __init__(self):
233 236 curl = pycurl.Curl()
234 237 # TODO: johbo: I did test with 7.19 of libcurl. This version has
235 238 # trouble with 100 - continue being set in the expect header. This
236 239 # can lead to massive performance drops, switching it off here.
237 240 curl.setopt(curl.HTTPHEADER, ["Expect:"])
238 241 curl.setopt(curl.TCP_NODELAY, True)
239 242 curl.setopt(curl.PROTOCOLS, curl.PROTO_HTTP)
240 243 self._curl = curl
241 244
242 245 def post(self, url, data, allow_redirects=False):
243 246 response_buffer = StringIO()
244 247
245 248 curl = self._curl
246 249 curl.setopt(curl.URL, url)
247 250 curl.setopt(curl.POST, True)
248 251 curl.setopt(curl.POSTFIELDS, data)
249 252 curl.setopt(curl.FOLLOWLOCATION, allow_redirects)
250 253 curl.setopt(curl.WRITEDATA, response_buffer)
251 254 curl.perform()
252 255
253 256 return CurlResponse(response_buffer)
254 257
255 258
256 259 class CurlResponse(object):
257 260 """
258 261 The response of a request, modeled after the requests API.
259 262
260 263 This class provides a subset of the response interface known from the
261 264 library `requests`. It is intentionally kept similar, so that we can use
262 265 `requests` as a drop in replacement for benchmarking purposes.
263 266 """
264 267
265 268 def __init__(self, response_buffer):
266 269 self._response_buffer = response_buffer
267 270
268 271 @property
269 272 def content(self):
270 273 return self._response_buffer.getvalue()
271 274
272 275
273 276 def _create_http_rpc_session():
274 277 session = CurlSession()
275 278 return session
@@ -1,218 +1,235 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Client for the VCSServer implemented based on HTTP.
23 23
24 24
25 25 Status
26 26 ------
27 27
28 28 This client implementation shall eventually replace the Pyro4 based
29 29 implementation.
30 30 """
31 31
32 32 import copy
33 33 import logging
34 import threading
34 35 import urllib2
35 36 import urlparse
36 37 import uuid
37 38
38 39 import msgpack
39 40 import requests
40 41
41 from . import exceptions
42 from . import exceptions, CurlSession
42 43
43 44
44 45 log = logging.getLogger(__name__)
45 46
46 47
47 48 # TODO: mikhail: Keep it in sync with vcsserver's
48 49 # HTTPApplication.ALLOWED_EXCEPTIONS
49 50 EXCEPTIONS_MAP = {
50 51 'KeyError': KeyError,
51 52 'URLError': urllib2.URLError,
52 53 }
53 54
54 55
55 56 class RepoMaker(object):
56 57
57 def __init__(self, server_and_port, backend_endpoint, session):
58 def __init__(self, server_and_port, backend_endpoint, session_factory):
58 59 self.url = urlparse.urljoin(
59 60 'http://%s' % server_and_port, backend_endpoint)
60 self._session = session
61 self._session_factory = session_factory
61 62
62 63 def __call__(self, path, config, with_wire=None):
63 64 log.debug('RepoMaker call on %s', path)
64 65 return RemoteRepo(
65 path, config, self.url, self._session, with_wire=with_wire)
66 path, config, self.url, self._session_factory(),
67 with_wire=with_wire)
66 68
67 69 def __getattr__(self, name):
68 70 def f(*args, **kwargs):
69 71 return self._call(name, *args, **kwargs)
70 72 return f
71 73
72 74 @exceptions.map_vcs_exceptions
73 75 def _call(self, name, *args, **kwargs):
74 76 payload = {
75 77 'id': str(uuid.uuid4()),
76 78 'method': name,
77 79 'params': {'args': args, 'kwargs': kwargs}
78 80 }
79 return _remote_call(self.url, payload, EXCEPTIONS_MAP, self._session)
81 return _remote_call(
82 self.url, payload, EXCEPTIONS_MAP, self._session_factory())
80 83
81 84
82 85 class RemoteRepo(object):
83 86
84 87 def __init__(self, path, config, url, session, with_wire=None):
85 88 self.url = url
86 89 self._session = session
87 90 self._wire = {
88 91 "path": path,
89 92 "config": config,
90 93 "context": str(uuid.uuid4()),
91 94 }
92 95 if with_wire:
93 96 self._wire.update(with_wire)
94 97
95 98 # johbo: Trading complexity for performance. Avoiding the call to
96 99 # log.debug brings a few percent gain even if is is not active.
97 100 if log.isEnabledFor(logging.DEBUG):
98 101 self._call = self._call_with_logging
99 102
100 103 def __getattr__(self, name):
101 104 def f(*args, **kwargs):
102 105 return self._call(name, *args, **kwargs)
103 106 return f
104 107
105 108 @exceptions.map_vcs_exceptions
106 109 def _call(self, name, *args, **kwargs):
107 110 # TODO: oliver: This is currently necessary pre-call since the
108 111 # config object is being changed for hooking scenarios
109 112 wire = copy.deepcopy(self._wire)
110 113 wire["config"] = wire["config"].serialize()
111 114 payload = {
112 115 'id': str(uuid.uuid4()),
113 116 'method': name,
114 117 'params': {'wire': wire, 'args': args, 'kwargs': kwargs}
115 118 }
116 119 return _remote_call(self.url, payload, EXCEPTIONS_MAP, self._session)
117 120
118 121 def _call_with_logging(self, name, *args, **kwargs):
119 122 log.debug('Calling %s@%s', self.url, name)
120 123 return RemoteRepo._call(self, name, *args, **kwargs)
121 124
122 125 def __getitem__(self, key):
123 126 return self.revision(key)
124 127
125 128
126 129 class RemoteObject(object):
127 130
128 131 def __init__(self, url, session):
129 132 self._url = url
130 133 self._session = session
131 134
132 135 # johbo: Trading complexity for performance. Avoiding the call to
133 136 # log.debug brings a few percent gain even if is is not active.
134 137 if log.isEnabledFor(logging.DEBUG):
135 138 self._call = self._call_with_logging
136 139
137 140 def __getattr__(self, name):
138 141 def f(*args, **kwargs):
139 142 return self._call(name, *args, **kwargs)
140 143 return f
141 144
142 145 @exceptions.map_vcs_exceptions
143 146 def _call(self, name, *args, **kwargs):
144 147 payload = {
145 148 'id': str(uuid.uuid4()),
146 149 'method': name,
147 150 'params': {'args': args, 'kwargs': kwargs}
148 151 }
149 152 return _remote_call(self._url, payload, EXCEPTIONS_MAP, self._session)
150 153
151 154 def _call_with_logging(self, name, *args, **kwargs):
152 155 log.debug('Calling %s@%s', self._url, name)
153 156 return RemoteObject._call(self, name, *args, **kwargs)
154 157
155 158
156 159 def _remote_call(url, payload, exceptions_map, session):
157 160 response = session.post(url, data=msgpack.packb(payload))
158 161 response = msgpack.unpackb(response.content)
159 162 error = response.get('error')
160 163 if error:
161 164 type_ = error.get('type', 'Exception')
162 165 exc = exceptions_map.get(type_, Exception)
163 166 exc = exc(error.get('message'))
164 167 try:
165 168 exc._vcs_kind = error['_vcs_kind']
166 169 except KeyError:
167 170 pass
168 171 raise exc
169 172 return response.get('result')
170 173
171 174
172 175 class VcsHttpProxy(object):
173 176
174 177 CHUNK_SIZE = 16384
175 178
176 179 def __init__(self, server_and_port, backend_endpoint):
177 180 adapter = requests.adapters.HTTPAdapter(max_retries=5)
178 181 self.base_url = urlparse.urljoin(
179 182 'http://%s' % server_and_port, backend_endpoint)
180 183 self.session = requests.Session()
181 184 self.session.mount('http://', adapter)
182 185
183 186 def handle(self, environment, input_data, *args, **kwargs):
184 187 data = {
185 188 'environment': environment,
186 189 'input_data': input_data,
187 190 'args': args,
188 191 'kwargs': kwargs
189 192 }
190 193 result = self.session.post(
191 194 self.base_url, msgpack.packb(data), stream=True)
192 195 return self._get_result(result)
193 196
194 197 def _deserialize_and_raise(self, error):
195 198 exception = Exception(error['message'])
196 199 try:
197 200 exception._vcs_kind = error['_vcs_kind']
198 201 except KeyError:
199 202 pass
200 203 raise exception
201 204
202 205 def _iterate(self, result):
203 206 unpacker = msgpack.Unpacker()
204 207 for line in result.iter_content(chunk_size=self.CHUNK_SIZE):
205 208 unpacker.feed(line)
206 209 for chunk in unpacker:
207 210 yield chunk
208 211
209 212 def _get_result(self, result):
210 213 iterator = self._iterate(result)
211 214 error = iterator.next()
212 215 if error:
213 216 self._deserialize_and_raise(error)
214 217
215 218 status = iterator.next()
216 219 headers = iterator.next()
217 220
218 221 return iterator, status, headers
222
223
224 class ThreadlocalSessionFactory(object):
225 """
226 Creates one CurlSession per thread on demand.
227 """
228
229 def __init__(self):
230 self._thread_local = threading.local()
231
232 def __call__(self):
233 if not hasattr(self._thread_local, 'curl_session'):
234 self._thread_local.curl_session = CurlSession()
235 return self._thread_local.curl_session
@@ -1,152 +1,164 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 The application's model objects
23 23
24 24 :example:
25 25
26 26 .. code-block:: python
27 27
28 28 from paste.deploy import appconfig
29 29 from pylons import config
30 30 from sqlalchemy import engine_from_config
31 31 from rhodecode.config.environment import load_environment
32 32
33 33 conf = appconfig('config:development.ini', relative_to = './../../')
34 34 load_environment(conf.global_conf, conf.local_conf)
35 35
36 36 engine = engine_from_config(config, 'sqlalchemy.')
37 37 init_model(engine)
38 38 # RUN YOUR CODE HERE
39 39
40 40 """
41 41
42 42
43 43 import logging
44 44
45 45 from pylons import config
46 from pyramid.threadlocal import get_current_registry
46 47
47 48 from rhodecode.model import meta, db
48 from rhodecode.lib.utils2 import obfuscate_url_pw
49 from rhodecode.lib.utils2 import obfuscate_url_pw, get_encryption_key
49 50
50 51 log = logging.getLogger(__name__)
51 52
52 53
53 54 def init_model(engine, encryption_key=None):
54 55 """
55 56 Initializes db session, bind the engine with the metadata,
56 57 Call this before using any of the tables or classes in the model,
57 58 preferably once in application start
58 59
59 60 :param engine: engine to bind to
60 61 """
61 62 engine_str = obfuscate_url_pw(str(engine.url))
62 63 log.info("initializing db for %s", engine_str)
63 64 meta.Base.metadata.bind = engine
64 65 db.ENCRYPTION_KEY = encryption_key
65 66
66 67
67 68 def init_model_encryption(migration_models):
68 migration_models.ENCRYPTION_KEY = config['beaker.session.secret']
69 db.ENCRYPTION_KEY = config['beaker.session.secret']
69 migration_models.ENCRYPTION_KEY = get_encryption_key(config)
70 db.ENCRYPTION_KEY = get_encryption_key(config)
70 71
71 72
72 73 class BaseModel(object):
73 74 """
74 75 Base Model for all RhodeCode models, it adds sql alchemy session
75 76 into instance of model
76 77
77 78 :param sa: If passed it reuses this session instead of creating a new one
78 79 """
79 80
80 81 cls = None # override in child class
81 82
82 83 def __init__(self, sa=None):
83 84 if sa is not None:
84 85 self.sa = sa
85 86 else:
86 87 self.sa = meta.Session()
87 88
88 89 def _get_instance(self, cls, instance, callback=None):
89 90 """
90 91 Gets instance of given cls using some simple lookup mechanism.
91 92
92 93 :param cls: class to fetch
93 94 :param instance: int or Instance
94 95 :param callback: callback to call if all lookups failed
95 96 """
96 97
97 98 if isinstance(instance, cls):
98 99 return instance
99 100 elif isinstance(instance, (int, long)):
100 101 return cls.get(instance)
101 102 else:
102 103 if instance:
103 104 if callback is None:
104 105 raise Exception(
105 106 'given object must be int, long or Instance of %s '
106 107 'got %s, no callback provided' % (cls, type(instance))
107 108 )
108 109 else:
109 110 return callback(instance)
110 111
111 112 def _get_user(self, user):
112 113 """
113 114 Helper method to get user by ID, or username fallback
114 115
115 116 :param user: UserID, username, or User instance
116 117 """
117 118 return self._get_instance(
118 119 db.User, user, callback=db.User.get_by_username)
119 120
120 121 def _get_user_group(self, user_group):
121 122 """
122 123 Helper method to get user by ID, or username fallback
123 124
124 125 :param user_group: UserGroupID, user_group_name, or UserGroup instance
125 126 """
126 127 return self._get_instance(
127 128 db.UserGroup, user_group, callback=db.UserGroup.get_by_group_name)
128 129
129 130 def _get_repo(self, repository):
130 131 """
131 132 Helper method to get repository by ID, or repository name
132 133
133 134 :param repository: RepoID, repository name or Repository Instance
134 135 """
135 136 return self._get_instance(
136 137 db.Repository, repository, callback=db.Repository.get_by_repo_name)
137 138
138 139 def _get_perm(self, permission):
139 140 """
140 141 Helper method to get permission by ID, or permission name
141 142
142 143 :param permission: PermissionID, permission_name or Permission instance
143 144 """
144 145 return self._get_instance(
145 146 db.Permission, permission, callback=db.Permission.get_by_key)
146 147
148 def send_event(self, event):
149 """
150 Helper method to send an event. This wraps the pyramid logic to send an
151 event.
152 """
153 # For the first step we are using pyramids thread locals here. If the
154 # event mechanism works out as a good solution we should think about
155 # passing the registry into the constructor to get rid of it.
156 registry = get_current_registry()
157 registry.notify(event)
158
147 159 @classmethod
148 160 def get_all(cls):
149 161 """
150 162 Returns all instances of what is defined in `cls` class variable
151 163 """
152 164 return cls.cls.getAll()
@@ -1,3429 +1,3476 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database Models for RhodeCode Enterprise
23 23 """
24 24
25 25 import os
26 26 import sys
27 27 import time
28 28 import hashlib
29 29 import logging
30 30 import datetime
31 31 import warnings
32 32 import ipaddress
33 33 import functools
34 34 import traceback
35 35 import collections
36 36
37 37
38 38 from sqlalchemy import *
39 39 from sqlalchemy.exc import IntegrityError
40 40 from sqlalchemy.ext.declarative import declared_attr
41 41 from sqlalchemy.ext.hybrid import hybrid_property
42 42 from sqlalchemy.orm import (
43 43 relationship, joinedload, class_mapper, validates, aliased)
44 44 from sqlalchemy.sql.expression import true
45 45 from beaker.cache import cache_region, region_invalidate
46 46 from webob.exc import HTTPNotFound
47 47 from zope.cachedescriptors.property import Lazy as LazyProperty
48 48
49 49 from pylons import url
50 50 from pylons.i18n.translation import lazy_ugettext as _
51 51
52 52 from rhodecode.lib.vcs import get_backend
53 53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 54 from rhodecode.lib.vcs.exceptions import VCSError
55 55 from rhodecode.lib.vcs.backends.base import (
56 56 EmptyCommit, Reference, MergeFailureReason)
57 57 from rhodecode.lib.utils2 import (
58 58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 60 from rhodecode.lib.ext_json import json
61 61 from rhodecode.lib.caching_query import FromCache
62 62 from rhodecode.lib.encrypt import AESCipher
63 63
64 64 from rhodecode.model.meta import Base, Session
65 65
66 66 URL_SEP = '/'
67 67 log = logging.getLogger(__name__)
68 68
69 69 # =============================================================================
70 70 # BASE CLASSES
71 71 # =============================================================================
72 72
73 # this is propagated from .ini file beaker.session.secret
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
74 # beaker.session.secret if first is not set.
74 75 # and initialized at environment.py
75 76 ENCRYPTION_KEY = None
76 77
77 78 # used to sort permissions by types, '#' used here is not allowed to be in
78 79 # usernames, and it's very early in sorted string.printable table.
79 80 PERMISSION_TYPE_SORT = {
80 81 'admin': '####',
81 82 'write': '###',
82 83 'read': '##',
83 84 'none': '#',
84 85 }
85 86
86 87
87 88 def display_sort(obj):
88 89 """
89 90 Sort function used to sort permissions in .permissions() function of
90 91 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 92 of all other resources
92 93 """
93 94
94 95 if obj.username == User.DEFAULT_USER:
95 96 return '#####'
96 97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 98 return prefix + obj.username
98 99
99 100
100 101 def _hash_key(k):
101 102 return md5_safe(k)
102 103
103 104
104 105 class EncryptedTextValue(TypeDecorator):
105 106 """
106 107 Special column for encrypted long text data, use like::
107 108
108 109 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109 110
110 111 This column is intelligent so if value is in unencrypted form it return
111 112 unencrypted form, but on save it always encrypts
112 113 """
113 114 impl = Text
114 115
115 116 def process_bind_param(self, value, dialect):
116 117 if not value:
117 118 return value
118 if value.startswith('enc$aes$'):
119 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
119 120 # protect against double encrypting if someone manually starts
120 121 # doing
121 122 raise ValueError('value needs to be in unencrypted format, ie. '
122 'not starting with enc$aes$')
123 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
123 'not starting with enc$aes')
124 return 'enc$aes_hmac$%s' % AESCipher(
125 ENCRYPTION_KEY, hmac=True).encrypt(value)
124 126
125 127 def process_result_value(self, value, dialect):
128 import rhodecode
129
126 130 if not value:
127 131 return value
128 132
129 133 parts = value.split('$', 3)
130 134 if not len(parts) == 3:
131 135 # probably not encrypted values
132 136 return value
133 137 else:
134 138 if parts[0] != 'enc':
135 139 # parts ok but without our header ?
136 140 return value
137
141 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
142 'rhodecode.encrypted_values.strict') or True)
138 143 # at that stage we know it's our encryption
139 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 if parts[1] == 'aes':
145 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
146 elif parts[1] == 'aes_hmac':
147 decrypted_data = AESCipher(
148 ENCRYPTION_KEY, hmac=True,
149 strict_verification=enc_strict_mode).decrypt(parts[2])
150 else:
151 raise ValueError(
152 'Encryption type part is wrong, must be `aes` '
153 'or `aes_hmac`, got `%s` instead' % (parts[1]))
140 154 return decrypted_data
141 155
142 156
143 157 class BaseModel(object):
144 158 """
145 159 Base Model for all classes
146 160 """
147 161
148 162 @classmethod
149 163 def _get_keys(cls):
150 164 """return column names for this model """
151 165 return class_mapper(cls).c.keys()
152 166
153 167 def get_dict(self):
154 168 """
155 169 return dict with keys and values corresponding
156 170 to this model data """
157 171
158 172 d = {}
159 173 for k in self._get_keys():
160 174 d[k] = getattr(self, k)
161 175
162 176 # also use __json__() if present to get additional fields
163 177 _json_attr = getattr(self, '__json__', None)
164 178 if _json_attr:
165 179 # update with attributes from __json__
166 180 if callable(_json_attr):
167 181 _json_attr = _json_attr()
168 182 for k, val in _json_attr.iteritems():
169 183 d[k] = val
170 184 return d
171 185
172 186 def get_appstruct(self):
173 187 """return list with keys and values tuples corresponding
174 188 to this model data """
175 189
176 190 l = []
177 191 for k in self._get_keys():
178 192 l.append((k, getattr(self, k),))
179 193 return l
180 194
181 195 def populate_obj(self, populate_dict):
182 196 """populate model with data from given populate_dict"""
183 197
184 198 for k in self._get_keys():
185 199 if k in populate_dict:
186 200 setattr(self, k, populate_dict[k])
187 201
188 202 @classmethod
189 203 def query(cls):
190 204 return Session().query(cls)
191 205
192 206 @classmethod
193 207 def get(cls, id_):
194 208 if id_:
195 209 return cls.query().get(id_)
196 210
197 211 @classmethod
198 212 def get_or_404(cls, id_):
199 213 try:
200 214 id_ = int(id_)
201 215 except (TypeError, ValueError):
202 216 raise HTTPNotFound
203 217
204 218 res = cls.query().get(id_)
205 219 if not res:
206 220 raise HTTPNotFound
207 221 return res
208 222
209 223 @classmethod
210 224 def getAll(cls):
211 225 # deprecated and left for backward compatibility
212 226 return cls.get_all()
213 227
214 228 @classmethod
215 229 def get_all(cls):
216 230 return cls.query().all()
217 231
218 232 @classmethod
219 233 def delete(cls, id_):
220 234 obj = cls.query().get(id_)
221 235 Session().delete(obj)
222 236
237 @classmethod
238 def identity_cache(cls, session, attr_name, value):
239 exist_in_session = []
240 for (item_cls, pkey), instance in session.identity_map.items():
241 if cls == item_cls and getattr(instance, attr_name) == value:
242 exist_in_session.append(instance)
243 if exist_in_session:
244 if len(exist_in_session) == 1:
245 return exist_in_session[0]
246 log.exception(
247 'multiple objects with attr %s and '
248 'value %s found with same name: %r',
249 attr_name, value, exist_in_session)
250
223 251 def __repr__(self):
224 252 if hasattr(self, '__unicode__'):
225 253 # python repr needs to return str
226 254 try:
227 255 return safe_str(self.__unicode__())
228 256 except UnicodeDecodeError:
229 257 pass
230 258 return '<DB:%s>' % (self.__class__.__name__)
231 259
232 260
233 261 class RhodeCodeSetting(Base, BaseModel):
234 262 __tablename__ = 'rhodecode_settings'
235 263 __table_args__ = (
236 264 UniqueConstraint('app_settings_name'),
237 265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
238 266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
239 267 )
240 268
241 269 SETTINGS_TYPES = {
242 270 'str': safe_str,
243 271 'int': safe_int,
244 272 'unicode': safe_unicode,
245 273 'bool': str2bool,
246 274 'list': functools.partial(aslist, sep=',')
247 275 }
248 276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
249 277 GLOBAL_CONF_KEY = 'app_settings'
250 278
251 279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
252 280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
253 281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
254 282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
255 283
256 284 def __init__(self, key='', val='', type='unicode'):
257 285 self.app_settings_name = key
258 286 self.app_settings_type = type
259 287 self.app_settings_value = val
260 288
261 289 @validates('_app_settings_value')
262 290 def validate_settings_value(self, key, val):
263 291 assert type(val) == unicode
264 292 return val
265 293
266 294 @hybrid_property
267 295 def app_settings_value(self):
268 296 v = self._app_settings_value
269 297 _type = self.app_settings_type
270 298 if _type:
271 299 _type = self.app_settings_type.split('.')[0]
272 300 # decode the encrypted value
273 301 if 'encrypted' in self.app_settings_type:
274 302 cipher = EncryptedTextValue()
275 303 v = safe_unicode(cipher.process_result_value(v, None))
276 304
277 305 converter = self.SETTINGS_TYPES.get(_type) or \
278 306 self.SETTINGS_TYPES['unicode']
279 307 return converter(v)
280 308
281 309 @app_settings_value.setter
282 310 def app_settings_value(self, val):
283 311 """
284 312 Setter that will always make sure we use unicode in app_settings_value
285 313
286 314 :param val:
287 315 """
288 316 val = safe_unicode(val)
289 317 # encode the encrypted value
290 318 if 'encrypted' in self.app_settings_type:
291 319 cipher = EncryptedTextValue()
292 320 val = safe_unicode(cipher.process_bind_param(val, None))
293 321 self._app_settings_value = val
294 322
295 323 @hybrid_property
296 324 def app_settings_type(self):
297 325 return self._app_settings_type
298 326
299 327 @app_settings_type.setter
300 328 def app_settings_type(self, val):
301 329 if val.split('.')[0] not in self.SETTINGS_TYPES:
302 330 raise Exception('type must be one of %s got %s'
303 331 % (self.SETTINGS_TYPES.keys(), val))
304 332 self._app_settings_type = val
305 333
306 334 def __unicode__(self):
307 335 return u"<%s('%s:%s[%s]')>" % (
308 336 self.__class__.__name__,
309 337 self.app_settings_name, self.app_settings_value,
310 338 self.app_settings_type
311 339 )
312 340
313 341
314 342 class RhodeCodeUi(Base, BaseModel):
315 343 __tablename__ = 'rhodecode_ui'
316 344 __table_args__ = (
317 345 UniqueConstraint('ui_key'),
318 346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
319 347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
320 348 )
321 349
322 350 HOOK_REPO_SIZE = 'changegroup.repo_size'
323 351 # HG
324 352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
325 353 HOOK_PULL = 'outgoing.pull_logger'
326 354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
327 355 HOOK_PUSH = 'changegroup.push_logger'
328 356
329 357 # TODO: johbo: Unify way how hooks are configured for git and hg,
330 358 # git part is currently hardcoded.
331 359
332 360 # SVN PATTERNS
333 361 SVN_BRANCH_ID = 'vcs_svn_branch'
334 362 SVN_TAG_ID = 'vcs_svn_tag'
335 363
336 364 ui_id = Column(
337 365 "ui_id", Integer(), nullable=False, unique=True, default=None,
338 366 primary_key=True)
339 367 ui_section = Column(
340 368 "ui_section", String(255), nullable=True, unique=None, default=None)
341 369 ui_key = Column(
342 370 "ui_key", String(255), nullable=True, unique=None, default=None)
343 371 ui_value = Column(
344 372 "ui_value", String(255), nullable=True, unique=None, default=None)
345 373 ui_active = Column(
346 374 "ui_active", Boolean(), nullable=True, unique=None, default=True)
347 375
348 376 def __repr__(self):
349 377 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
350 378 self.ui_key, self.ui_value)
351 379
352 380
353 381 class RepoRhodeCodeSetting(Base, BaseModel):
354 382 __tablename__ = 'repo_rhodecode_settings'
355 383 __table_args__ = (
356 384 UniqueConstraint(
357 385 'app_settings_name', 'repository_id',
358 386 name='uq_repo_rhodecode_setting_name_repo_id'),
359 387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
360 388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
361 389 )
362 390
363 391 repository_id = Column(
364 392 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
365 393 nullable=False)
366 394 app_settings_id = Column(
367 395 "app_settings_id", Integer(), nullable=False, unique=True,
368 396 default=None, primary_key=True)
369 397 app_settings_name = Column(
370 398 "app_settings_name", String(255), nullable=True, unique=None,
371 399 default=None)
372 400 _app_settings_value = Column(
373 401 "app_settings_value", String(4096), nullable=True, unique=None,
374 402 default=None)
375 403 _app_settings_type = Column(
376 404 "app_settings_type", String(255), nullable=True, unique=None,
377 405 default=None)
378 406
379 407 repository = relationship('Repository')
380 408
381 409 def __init__(self, repository_id, key='', val='', type='unicode'):
382 410 self.repository_id = repository_id
383 411 self.app_settings_name = key
384 412 self.app_settings_type = type
385 413 self.app_settings_value = val
386 414
387 415 @validates('_app_settings_value')
388 416 def validate_settings_value(self, key, val):
389 417 assert type(val) == unicode
390 418 return val
391 419
392 420 @hybrid_property
393 421 def app_settings_value(self):
394 422 v = self._app_settings_value
395 423 type_ = self.app_settings_type
396 424 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
397 425 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
398 426 return converter(v)
399 427
400 428 @app_settings_value.setter
401 429 def app_settings_value(self, val):
402 430 """
403 431 Setter that will always make sure we use unicode in app_settings_value
404 432
405 433 :param val:
406 434 """
407 435 self._app_settings_value = safe_unicode(val)
408 436
409 437 @hybrid_property
410 438 def app_settings_type(self):
411 439 return self._app_settings_type
412 440
413 441 @app_settings_type.setter
414 442 def app_settings_type(self, val):
415 443 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
416 444 if val not in SETTINGS_TYPES:
417 445 raise Exception('type must be one of %s got %s'
418 446 % (SETTINGS_TYPES.keys(), val))
419 447 self._app_settings_type = val
420 448
421 449 def __unicode__(self):
422 450 return u"<%s('%s:%s:%s[%s]')>" % (
423 451 self.__class__.__name__, self.repository.repo_name,
424 452 self.app_settings_name, self.app_settings_value,
425 453 self.app_settings_type
426 454 )
427 455
428 456
429 457 class RepoRhodeCodeUi(Base, BaseModel):
430 458 __tablename__ = 'repo_rhodecode_ui'
431 459 __table_args__ = (
432 460 UniqueConstraint(
433 461 'repository_id', 'ui_section', 'ui_key',
434 462 name='uq_repo_rhodecode_ui_repository_id_section_key'),
435 463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
436 464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
437 465 )
438 466
439 467 repository_id = Column(
440 468 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
441 469 nullable=False)
442 470 ui_id = Column(
443 471 "ui_id", Integer(), nullable=False, unique=True, default=None,
444 472 primary_key=True)
445 473 ui_section = Column(
446 474 "ui_section", String(255), nullable=True, unique=None, default=None)
447 475 ui_key = Column(
448 476 "ui_key", String(255), nullable=True, unique=None, default=None)
449 477 ui_value = Column(
450 478 "ui_value", String(255), nullable=True, unique=None, default=None)
451 479 ui_active = Column(
452 480 "ui_active", Boolean(), nullable=True, unique=None, default=True)
453 481
454 482 repository = relationship('Repository')
455 483
456 484 def __repr__(self):
457 485 return '<%s[%s:%s]%s=>%s]>' % (
458 486 self.__class__.__name__, self.repository.repo_name,
459 487 self.ui_section, self.ui_key, self.ui_value)
460 488
461 489
462 490 class User(Base, BaseModel):
463 491 __tablename__ = 'users'
464 492 __table_args__ = (
465 493 UniqueConstraint('username'), UniqueConstraint('email'),
466 494 Index('u_username_idx', 'username'),
467 495 Index('u_email_idx', 'email'),
468 496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 498 )
471 499 DEFAULT_USER = 'default'
472 500 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
473 501 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
474 502
475 503 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
476 504 username = Column("username", String(255), nullable=True, unique=None, default=None)
477 505 password = Column("password", String(255), nullable=True, unique=None, default=None)
478 506 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
479 507 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
480 508 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
481 509 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
482 510 _email = Column("email", String(255), nullable=True, unique=None, default=None)
483 511 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
484 512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
485 513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
486 514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
487 515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
488 516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
489 517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
490 518
491 519 user_log = relationship('UserLog')
492 520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
493 521
494 522 repositories = relationship('Repository')
495 523 repository_groups = relationship('RepoGroup')
496 524 user_groups = relationship('UserGroup')
497 525
498 526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
499 527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
500 528
501 529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
502 530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
503 531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
504 532
505 533 group_member = relationship('UserGroupMember', cascade='all')
506 534
507 535 notifications = relationship('UserNotification', cascade='all')
508 536 # notifications assigned to this user
509 537 user_created_notifications = relationship('Notification', cascade='all')
510 538 # comments created by this user
511 539 user_comments = relationship('ChangesetComment', cascade='all')
512 540 # user profile extra info
513 541 user_emails = relationship('UserEmailMap', cascade='all')
514 542 user_ip_map = relationship('UserIpMap', cascade='all')
515 543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
516 544 # gists
517 545 user_gists = relationship('Gist', cascade='all')
518 546 # user pull requests
519 547 user_pull_requests = relationship('PullRequest', cascade='all')
520 548 # external identities
521 549 extenal_identities = relationship(
522 550 'ExternalIdentity',
523 551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
524 552 cascade='all')
525 553
526 554 def __unicode__(self):
527 555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
528 556 self.user_id, self.username)
529 557
530 558 @hybrid_property
531 559 def email(self):
532 560 return self._email
533 561
534 562 @email.setter
535 563 def email(self, val):
536 564 self._email = val.lower() if val else None
537 565
538 566 @property
539 567 def firstname(self):
540 568 # alias for future
541 569 return self.name
542 570
543 571 @property
544 572 def emails(self):
545 573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
546 574 return [self.email] + [x.email for x in other]
547 575
548 576 @property
549 577 def auth_tokens(self):
550 578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
551 579
552 580 @property
553 581 def extra_auth_tokens(self):
554 582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
555 583
556 584 @property
557 585 def feed_token(self):
558 586 feed_tokens = UserApiKeys.query()\
559 587 .filter(UserApiKeys.user == self)\
560 588 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
561 589 .all()
562 590 if feed_tokens:
563 591 return feed_tokens[0].api_key
564 592 else:
565 593 # use the main token so we don't end up with nothing...
566 594 return self.api_key
567 595
568 596 @classmethod
569 597 def extra_valid_auth_tokens(cls, user, role=None):
570 598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
571 599 .filter(or_(UserApiKeys.expires == -1,
572 600 UserApiKeys.expires >= time.time()))
573 601 if role:
574 602 tokens = tokens.filter(or_(UserApiKeys.role == role,
575 603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
576 604 return tokens.all()
577 605
578 606 @property
579 607 def ip_addresses(self):
580 608 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
581 609 return [x.ip_addr for x in ret]
582 610
583 611 @property
584 612 def username_and_name(self):
585 613 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
586 614
587 615 @property
588 616 def username_or_name_or_email(self):
589 617 full_name = self.full_name if self.full_name is not ' ' else None
590 618 return self.username or full_name or self.email
591 619
592 620 @property
593 621 def full_name(self):
594 622 return '%s %s' % (self.firstname, self.lastname)
595 623
596 624 @property
597 625 def full_name_or_username(self):
598 626 return ('%s %s' % (self.firstname, self.lastname)
599 627 if (self.firstname and self.lastname) else self.username)
600 628
601 629 @property
602 630 def full_contact(self):
603 631 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
604 632
605 633 @property
606 634 def short_contact(self):
607 635 return '%s %s' % (self.firstname, self.lastname)
608 636
609 637 @property
610 638 def is_admin(self):
611 639 return self.admin
612 640
613 641 @property
614 642 def AuthUser(self):
615 643 """
616 644 Returns instance of AuthUser for this user
617 645 """
618 646 from rhodecode.lib.auth import AuthUser
619 647 return AuthUser(user_id=self.user_id, api_key=self.api_key,
620 648 username=self.username)
621 649
622 650 @hybrid_property
623 651 def user_data(self):
624 652 if not self._user_data:
625 653 return {}
626 654
627 655 try:
628 656 return json.loads(self._user_data)
629 657 except TypeError:
630 658 return {}
631 659
632 660 @user_data.setter
633 661 def user_data(self, val):
634 662 if not isinstance(val, dict):
635 663 raise Exception('user_data must be dict, got %s' % type(val))
636 664 try:
637 665 self._user_data = json.dumps(val)
638 666 except Exception:
639 667 log.error(traceback.format_exc())
640 668
641 669 @classmethod
642 def get_by_username(cls, username, case_insensitive=False, cache=False):
670 def get_by_username(cls, username, case_insensitive=False,
671 cache=False, identity_cache=False):
672 session = Session()
673
643 674 if case_insensitive:
644 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
675 q = cls.query().filter(
676 func.lower(cls.username) == func.lower(username))
645 677 else:
646 678 q = cls.query().filter(cls.username == username)
647 679
648 680 if cache:
649 q = q.options(FromCache(
650 "sql_cache_short",
651 "get_user_%s" % _hash_key(username)))
681 if identity_cache:
682 val = cls.identity_cache(session, 'username', username)
683 if val:
684 return val
685 else:
686 q = q.options(
687 FromCache("sql_cache_short",
688 "get_user_by_name_%s" % _hash_key(username)))
689
652 690 return q.scalar()
653 691
654 692 @classmethod
655 693 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
656 694 q = cls.query().filter(cls.api_key == auth_token)
657 695
658 696 if cache:
659 697 q = q.options(FromCache("sql_cache_short",
660 698 "get_auth_token_%s" % auth_token))
661 699 res = q.scalar()
662 700
663 701 if fallback and not res:
664 702 #fallback to additional keys
665 703 _res = UserApiKeys.query()\
666 704 .filter(UserApiKeys.api_key == auth_token)\
667 705 .filter(or_(UserApiKeys.expires == -1,
668 706 UserApiKeys.expires >= time.time()))\
669 707 .first()
670 708 if _res:
671 709 res = _res.user
672 710 return res
673 711
674 712 @classmethod
675 713 def get_by_email(cls, email, case_insensitive=False, cache=False):
676 714
677 715 if case_insensitive:
678 716 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
679 717
680 718 else:
681 719 q = cls.query().filter(cls.email == email)
682 720
683 721 if cache:
684 722 q = q.options(FromCache("sql_cache_short",
685 723 "get_email_key_%s" % email))
686 724
687 725 ret = q.scalar()
688 726 if ret is None:
689 727 q = UserEmailMap.query()
690 728 # try fetching in alternate email map
691 729 if case_insensitive:
692 730 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
693 731 else:
694 732 q = q.filter(UserEmailMap.email == email)
695 733 q = q.options(joinedload(UserEmailMap.user))
696 734 if cache:
697 735 q = q.options(FromCache("sql_cache_short",
698 736 "get_email_map_key_%s" % email))
699 737 ret = getattr(q.scalar(), 'user', None)
700 738
701 739 return ret
702 740
703 741 @classmethod
704 742 def get_from_cs_author(cls, author):
705 743 """
706 744 Tries to get User objects out of commit author string
707 745
708 746 :param author:
709 747 """
710 748 from rhodecode.lib.helpers import email, author_name
711 749 # Valid email in the attribute passed, see if they're in the system
712 750 _email = email(author)
713 751 if _email:
714 752 user = cls.get_by_email(_email, case_insensitive=True)
715 753 if user:
716 754 return user
717 755 # Maybe we can match by username?
718 756 _author = author_name(author)
719 757 user = cls.get_by_username(_author, case_insensitive=True)
720 758 if user:
721 759 return user
722 760
723 761 def update_userdata(self, **kwargs):
724 762 usr = self
725 763 old = usr.user_data
726 764 old.update(**kwargs)
727 765 usr.user_data = old
728 766 Session().add(usr)
729 767 log.debug('updated userdata with ', kwargs)
730 768
731 769 def update_lastlogin(self):
732 770 """Update user lastlogin"""
733 771 self.last_login = datetime.datetime.now()
734 772 Session().add(self)
735 773 log.debug('updated user %s lastlogin', self.username)
736 774
737 775 def update_lastactivity(self):
738 776 """Update user lastactivity"""
739 777 usr = self
740 778 old = usr.user_data
741 779 old.update({'last_activity': time.time()})
742 780 usr.user_data = old
743 781 Session().add(usr)
744 782 log.debug('updated user %s lastactivity', usr.username)
745 783
746 784 def update_password(self, new_password, change_api_key=False):
747 785 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
748 786
749 787 self.password = get_crypt_password(new_password)
750 788 if change_api_key:
751 789 self.api_key = generate_auth_token(self.username)
752 790 Session().add(self)
753 791
754 792 @classmethod
755 def get_first_admin(cls):
756 user = User.query().filter(User.admin == True).first()
793 def get_first_super_admin(cls):
794 user = User.query().filter(User.admin == true()).first()
757 795 if user is None:
758 raise Exception('Missing administrative account!')
796 raise Exception('FATAL: Missing administrative account!')
759 797 return user
760 798
761 799 @classmethod
762 800 def get_all_super_admins(cls):
763 801 """
764 802 Returns all admin accounts sorted by username
765 803 """
766 804 return User.query().filter(User.admin == true())\
767 805 .order_by(User.username.asc()).all()
768 806
769 807 @classmethod
770 808 def get_default_user(cls, cache=False):
771 809 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
772 810 if user is None:
773 raise Exception('Missing default account!')
811 raise Exception('FATAL: Missing default account!')
774 812 return user
775 813
776 814 def _get_default_perms(self, user, suffix=''):
777 815 from rhodecode.model.permission import PermissionModel
778 816 return PermissionModel().get_default_perms(user.user_perms, suffix)
779 817
780 818 def get_default_perms(self, suffix=''):
781 819 return self._get_default_perms(self, suffix)
782 820
783 821 def get_api_data(self, include_secrets=False, details='full'):
784 822 """
785 823 Common function for generating user related data for API
786 824
787 825 :param include_secrets: By default secrets in the API data will be replaced
788 826 by a placeholder value to prevent exposing this data by accident. In case
789 827 this data shall be exposed, set this flag to ``True``.
790 828
791 829 :param details: details can be 'basic|full' basic gives only a subset of
792 830 the available user information that includes user_id, name and emails.
793 831 """
794 832 user = self
795 833 user_data = self.user_data
796 834 data = {
797 835 'user_id': user.user_id,
798 836 'username': user.username,
799 837 'firstname': user.name,
800 838 'lastname': user.lastname,
801 839 'email': user.email,
802 840 'emails': user.emails,
803 841 }
804 842 if details == 'basic':
805 843 return data
806 844
807 845 api_key_length = 40
808 846 api_key_replacement = '*' * api_key_length
809 847
810 848 extras = {
811 849 'api_key': api_key_replacement,
812 850 'api_keys': [api_key_replacement],
813 851 'active': user.active,
814 852 'admin': user.admin,
815 853 'extern_type': user.extern_type,
816 854 'extern_name': user.extern_name,
817 855 'last_login': user.last_login,
818 856 'ip_addresses': user.ip_addresses,
819 857 'language': user_data.get('language')
820 858 }
821 859 data.update(extras)
822 860
823 861 if include_secrets:
824 862 data['api_key'] = user.api_key
825 863 data['api_keys'] = user.auth_tokens
826 864 return data
827 865
828 866 def __json__(self):
829 867 data = {
830 868 'full_name': self.full_name,
831 869 'full_name_or_username': self.full_name_or_username,
832 870 'short_contact': self.short_contact,
833 871 'full_contact': self.full_contact,
834 872 }
835 873 data.update(self.get_api_data())
836 874 return data
837 875
838 876
839 877 class UserApiKeys(Base, BaseModel):
840 878 __tablename__ = 'user_api_keys'
841 879 __table_args__ = (
842 880 Index('uak_api_key_idx', 'api_key'),
843 881 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
844 882 UniqueConstraint('api_key'),
845 883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
846 884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
847 885 )
848 886 __mapper_args__ = {}
849 887
850 888 # ApiKey role
851 889 ROLE_ALL = 'token_role_all'
852 890 ROLE_HTTP = 'token_role_http'
853 891 ROLE_VCS = 'token_role_vcs'
854 892 ROLE_API = 'token_role_api'
855 893 ROLE_FEED = 'token_role_feed'
856 894 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
857 895
858 896 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
860 898 api_key = Column("api_key", String(255), nullable=False, unique=True)
861 899 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
862 900 expires = Column('expires', Float(53), nullable=False)
863 901 role = Column('role', String(255), nullable=True)
864 902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
865 903
866 904 user = relationship('User', lazy='joined')
867 905
868 906 @classmethod
869 907 def _get_role_name(cls, role):
870 908 return {
871 909 cls.ROLE_ALL: _('all'),
872 910 cls.ROLE_HTTP: _('http/web interface'),
873 911 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
874 912 cls.ROLE_API: _('api calls'),
875 913 cls.ROLE_FEED: _('feed access'),
876 914 }.get(role, role)
877 915
878 916 @property
879 917 def expired(self):
880 918 if self.expires == -1:
881 919 return False
882 920 return time.time() > self.expires
883 921
884 922 @property
885 923 def role_humanized(self):
886 924 return self._get_role_name(self.role)
887 925
888 926
889 927 class UserEmailMap(Base, BaseModel):
890 928 __tablename__ = 'user_email_map'
891 929 __table_args__ = (
892 930 Index('uem_email_idx', 'email'),
893 931 UniqueConstraint('email'),
894 932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
895 933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
896 934 )
897 935 __mapper_args__ = {}
898 936
899 937 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
900 938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
901 939 _email = Column("email", String(255), nullable=True, unique=False, default=None)
902 940 user = relationship('User', lazy='joined')
903 941
904 942 @validates('_email')
905 943 def validate_email(self, key, email):
906 944 # check if this email is not main one
907 945 main_email = Session().query(User).filter(User.email == email).scalar()
908 946 if main_email is not None:
909 947 raise AttributeError('email %s is present is user table' % email)
910 948 return email
911 949
912 950 @hybrid_property
913 951 def email(self):
914 952 return self._email
915 953
916 954 @email.setter
917 955 def email(self, val):
918 956 self._email = val.lower() if val else None
919 957
920 958
921 959 class UserIpMap(Base, BaseModel):
922 960 __tablename__ = 'user_ip_map'
923 961 __table_args__ = (
924 962 UniqueConstraint('user_id', 'ip_addr'),
925 963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
926 964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
927 965 )
928 966 __mapper_args__ = {}
929 967
930 968 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
931 969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
932 970 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
933 971 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
934 972 description = Column("description", String(10000), nullable=True, unique=None, default=None)
935 973 user = relationship('User', lazy='joined')
936 974
937 975 @classmethod
938 976 def _get_ip_range(cls, ip_addr):
939 977 net = ipaddress.ip_network(ip_addr, strict=False)
940 978 return [str(net.network_address), str(net.broadcast_address)]
941 979
942 980 def __json__(self):
943 981 return {
944 982 'ip_addr': self.ip_addr,
945 983 'ip_range': self._get_ip_range(self.ip_addr),
946 984 }
947 985
948 986 def __unicode__(self):
949 987 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
950 988 self.user_id, self.ip_addr)
951 989
952 990 class UserLog(Base, BaseModel):
953 991 __tablename__ = 'user_logs'
954 992 __table_args__ = (
955 993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
956 994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
957 995 )
958 996 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
959 997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
960 998 username = Column("username", String(255), nullable=True, unique=None, default=None)
961 999 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
962 1000 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
963 1001 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
964 1002 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
965 1003 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
966 1004
967 1005 def __unicode__(self):
968 1006 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
969 1007 self.repository_name,
970 1008 self.action)
971 1009
972 1010 @property
973 1011 def action_as_day(self):
974 1012 return datetime.date(*self.action_date.timetuple()[:3])
975 1013
976 1014 user = relationship('User')
977 1015 repository = relationship('Repository', cascade='')
978 1016
979 1017
980 1018 class UserGroup(Base, BaseModel):
981 1019 __tablename__ = 'users_groups'
982 1020 __table_args__ = (
983 1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
984 1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
985 1023 )
986 1024
987 1025 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
988 1026 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
989 1027 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
990 1028 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
991 1029 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
992 1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
993 1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
994 1032 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
995 1033
996 1034 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
997 1035 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
998 1036 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
999 1037 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1000 1038 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1001 1039 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1002 1040
1003 1041 user = relationship('User')
1004 1042
1005 1043 @hybrid_property
1006 1044 def group_data(self):
1007 1045 if not self._group_data:
1008 1046 return {}
1009 1047
1010 1048 try:
1011 1049 return json.loads(self._group_data)
1012 1050 except TypeError:
1013 1051 return {}
1014 1052
1015 1053 @group_data.setter
1016 1054 def group_data(self, val):
1017 1055 try:
1018 1056 self._group_data = json.dumps(val)
1019 1057 except Exception:
1020 1058 log.error(traceback.format_exc())
1021 1059
1022 1060 def __unicode__(self):
1023 1061 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1024 1062 self.users_group_id,
1025 1063 self.users_group_name)
1026 1064
1027 1065 @classmethod
1028 1066 def get_by_group_name(cls, group_name, cache=False,
1029 1067 case_insensitive=False):
1030 1068 if case_insensitive:
1031 1069 q = cls.query().filter(func.lower(cls.users_group_name) ==
1032 1070 func.lower(group_name))
1033 1071
1034 1072 else:
1035 1073 q = cls.query().filter(cls.users_group_name == group_name)
1036 1074 if cache:
1037 1075 q = q.options(FromCache(
1038 1076 "sql_cache_short",
1039 1077 "get_group_%s" % _hash_key(group_name)))
1040 1078 return q.scalar()
1041 1079
1042 1080 @classmethod
1043 1081 def get(cls, user_group_id, cache=False):
1044 1082 user_group = cls.query()
1045 1083 if cache:
1046 1084 user_group = user_group.options(FromCache("sql_cache_short",
1047 1085 "get_users_group_%s" % user_group_id))
1048 1086 return user_group.get(user_group_id)
1049 1087
1050 1088 def permissions(self, with_admins=True, with_owner=True):
1051 1089 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1052 1090 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1053 1091 joinedload(UserUserGroupToPerm.user),
1054 1092 joinedload(UserUserGroupToPerm.permission),)
1055 1093
1056 1094 # get owners and admins and permissions. We do a trick of re-writing
1057 1095 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1058 1096 # has a global reference and changing one object propagates to all
1059 1097 # others. This means if admin is also an owner admin_row that change
1060 1098 # would propagate to both objects
1061 1099 perm_rows = []
1062 1100 for _usr in q.all():
1063 1101 usr = AttributeDict(_usr.user.get_dict())
1064 1102 usr.permission = _usr.permission.permission_name
1065 1103 perm_rows.append(usr)
1066 1104
1067 1105 # filter the perm rows by 'default' first and then sort them by
1068 1106 # admin,write,read,none permissions sorted again alphabetically in
1069 1107 # each group
1070 1108 perm_rows = sorted(perm_rows, key=display_sort)
1071 1109
1072 1110 _admin_perm = 'usergroup.admin'
1073 1111 owner_row = []
1074 1112 if with_owner:
1075 1113 usr = AttributeDict(self.user.get_dict())
1076 1114 usr.owner_row = True
1077 1115 usr.permission = _admin_perm
1078 1116 owner_row.append(usr)
1079 1117
1080 1118 super_admin_rows = []
1081 1119 if with_admins:
1082 1120 for usr in User.get_all_super_admins():
1083 1121 # if this admin is also owner, don't double the record
1084 1122 if usr.user_id == owner_row[0].user_id:
1085 1123 owner_row[0].admin_row = True
1086 1124 else:
1087 1125 usr = AttributeDict(usr.get_dict())
1088 1126 usr.admin_row = True
1089 1127 usr.permission = _admin_perm
1090 1128 super_admin_rows.append(usr)
1091 1129
1092 1130 return super_admin_rows + owner_row + perm_rows
1093 1131
1094 1132 def permission_user_groups(self):
1095 1133 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1096 1134 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1097 1135 joinedload(UserGroupUserGroupToPerm.target_user_group),
1098 1136 joinedload(UserGroupUserGroupToPerm.permission),)
1099 1137
1100 1138 perm_rows = []
1101 1139 for _user_group in q.all():
1102 1140 usr = AttributeDict(_user_group.user_group.get_dict())
1103 1141 usr.permission = _user_group.permission.permission_name
1104 1142 perm_rows.append(usr)
1105 1143
1106 1144 return perm_rows
1107 1145
1108 1146 def _get_default_perms(self, user_group, suffix=''):
1109 1147 from rhodecode.model.permission import PermissionModel
1110 1148 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1111 1149
1112 1150 def get_default_perms(self, suffix=''):
1113 1151 return self._get_default_perms(self, suffix)
1114 1152
1115 1153 def get_api_data(self, with_group_members=True, include_secrets=False):
1116 1154 """
1117 1155 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1118 1156 basically forwarded.
1119 1157
1120 1158 """
1121 1159 user_group = self
1122 1160
1123 1161 data = {
1124 1162 'users_group_id': user_group.users_group_id,
1125 1163 'group_name': user_group.users_group_name,
1126 1164 'group_description': user_group.user_group_description,
1127 1165 'active': user_group.users_group_active,
1128 1166 'owner': user_group.user.username,
1129 1167 }
1130 1168 if with_group_members:
1131 1169 users = []
1132 1170 for user in user_group.members:
1133 1171 user = user.user
1134 1172 users.append(user.get_api_data(include_secrets=include_secrets))
1135 1173 data['users'] = users
1136 1174
1137 1175 return data
1138 1176
1139 1177
1140 1178 class UserGroupMember(Base, BaseModel):
1141 1179 __tablename__ = 'users_groups_members'
1142 1180 __table_args__ = (
1143 1181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1144 1182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1145 1183 )
1146 1184
1147 1185 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1148 1186 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1149 1187 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1150 1188
1151 1189 user = relationship('User', lazy='joined')
1152 1190 users_group = relationship('UserGroup')
1153 1191
1154 1192 def __init__(self, gr_id='', u_id=''):
1155 1193 self.users_group_id = gr_id
1156 1194 self.user_id = u_id
1157 1195
1158 1196
1159 1197 class RepositoryField(Base, BaseModel):
1160 1198 __tablename__ = 'repositories_fields'
1161 1199 __table_args__ = (
1162 1200 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1163 1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1164 1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1165 1203 )
1166 1204 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1167 1205
1168 1206 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1169 1207 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1170 1208 field_key = Column("field_key", String(250))
1171 1209 field_label = Column("field_label", String(1024), nullable=False)
1172 1210 field_value = Column("field_value", String(10000), nullable=False)
1173 1211 field_desc = Column("field_desc", String(1024), nullable=False)
1174 1212 field_type = Column("field_type", String(255), nullable=False, unique=None)
1175 1213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1176 1214
1177 1215 repository = relationship('Repository')
1178 1216
1179 1217 @property
1180 1218 def field_key_prefixed(self):
1181 1219 return 'ex_%s' % self.field_key
1182 1220
1183 1221 @classmethod
1184 1222 def un_prefix_key(cls, key):
1185 1223 if key.startswith(cls.PREFIX):
1186 1224 return key[len(cls.PREFIX):]
1187 1225 return key
1188 1226
1189 1227 @classmethod
1190 1228 def get_by_key_name(cls, key, repo):
1191 1229 row = cls.query()\
1192 1230 .filter(cls.repository == repo)\
1193 1231 .filter(cls.field_key == key).scalar()
1194 1232 return row
1195 1233
1196 1234
1197 1235 class Repository(Base, BaseModel):
1198 1236 __tablename__ = 'repositories'
1199 1237 __table_args__ = (
1200 1238 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1201 1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1202 1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1203 1241 )
1204 1242 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1205 1243 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1206 1244
1207 1245 STATE_CREATED = 'repo_state_created'
1208 1246 STATE_PENDING = 'repo_state_pending'
1209 1247 STATE_ERROR = 'repo_state_error'
1210 1248
1211 1249 LOCK_AUTOMATIC = 'lock_auto'
1212 1250 LOCK_API = 'lock_api'
1213 1251 LOCK_WEB = 'lock_web'
1214 1252 LOCK_PULL = 'lock_pull'
1215 1253
1216 1254 NAME_SEP = URL_SEP
1217 1255
1218 1256 repo_id = Column(
1219 1257 "repo_id", Integer(), nullable=False, unique=True, default=None,
1220 1258 primary_key=True)
1221 1259 _repo_name = Column(
1222 1260 "repo_name", Text(), nullable=False, default=None)
1223 1261 _repo_name_hash = Column(
1224 1262 "repo_name_hash", String(255), nullable=False, unique=True)
1225 1263 repo_state = Column("repo_state", String(255), nullable=True)
1226 1264
1227 1265 clone_uri = Column(
1228 1266 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1229 1267 default=None)
1230 1268 repo_type = Column(
1231 1269 "repo_type", String(255), nullable=False, unique=False, default=None)
1232 1270 user_id = Column(
1233 1271 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1234 1272 unique=False, default=None)
1235 1273 private = Column(
1236 1274 "private", Boolean(), nullable=True, unique=None, default=None)
1237 1275 enable_statistics = Column(
1238 1276 "statistics", Boolean(), nullable=True, unique=None, default=True)
1239 1277 enable_downloads = Column(
1240 1278 "downloads", Boolean(), nullable=True, unique=None, default=True)
1241 1279 description = Column(
1242 1280 "description", String(10000), nullable=True, unique=None, default=None)
1243 1281 created_on = Column(
1244 1282 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1245 1283 default=datetime.datetime.now)
1246 1284 updated_on = Column(
1247 1285 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1248 1286 default=datetime.datetime.now)
1249 1287 _landing_revision = Column(
1250 1288 "landing_revision", String(255), nullable=False, unique=False,
1251 1289 default=None)
1252 1290 enable_locking = Column(
1253 1291 "enable_locking", Boolean(), nullable=False, unique=None,
1254 1292 default=False)
1255 1293 _locked = Column(
1256 1294 "locked", String(255), nullable=True, unique=False, default=None)
1257 1295 _changeset_cache = Column(
1258 1296 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1259 1297
1260 1298 fork_id = Column(
1261 1299 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1262 1300 nullable=True, unique=False, default=None)
1263 1301 group_id = Column(
1264 1302 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1265 1303 unique=False, default=None)
1266 1304
1267 user = relationship('User')
1268 fork = relationship('Repository', remote_side=repo_id)
1269 group = relationship('RepoGroup')
1305 user = relationship('User', lazy='joined')
1306 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1307 group = relationship('RepoGroup', lazy='joined')
1270 1308 repo_to_perm = relationship(
1271 1309 'UserRepoToPerm', cascade='all',
1272 1310 order_by='UserRepoToPerm.repo_to_perm_id')
1273 1311 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1274 1312 stats = relationship('Statistics', cascade='all', uselist=False)
1275 1313
1276 1314 followers = relationship(
1277 1315 'UserFollowing',
1278 1316 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1279 1317 cascade='all')
1280 1318 extra_fields = relationship(
1281 1319 'RepositoryField', cascade="all, delete, delete-orphan")
1282 1320 logs = relationship('UserLog')
1283 1321 comments = relationship(
1284 1322 'ChangesetComment', cascade="all, delete, delete-orphan")
1285 1323 pull_requests_source = relationship(
1286 1324 'PullRequest',
1287 1325 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1288 1326 cascade="all, delete, delete-orphan")
1289 1327 pull_requests_target = relationship(
1290 1328 'PullRequest',
1291 1329 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1292 1330 cascade="all, delete, delete-orphan")
1293 1331 ui = relationship('RepoRhodeCodeUi', cascade="all")
1294 1332 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1295 1333
1296 1334 def __unicode__(self):
1297 1335 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1298 1336 safe_unicode(self.repo_name))
1299 1337
1300 1338 @hybrid_property
1301 1339 def landing_rev(self):
1302 1340 # always should return [rev_type, rev]
1303 1341 if self._landing_revision:
1304 1342 _rev_info = self._landing_revision.split(':')
1305 1343 if len(_rev_info) < 2:
1306 1344 _rev_info.insert(0, 'rev')
1307 1345 return [_rev_info[0], _rev_info[1]]
1308 1346 return [None, None]
1309 1347
1310 1348 @landing_rev.setter
1311 1349 def landing_rev(self, val):
1312 1350 if ':' not in val:
1313 1351 raise ValueError('value must be delimited with `:` and consist '
1314 1352 'of <rev_type>:<rev>, got %s instead' % val)
1315 1353 self._landing_revision = val
1316 1354
1317 1355 @hybrid_property
1318 1356 def locked(self):
1319 1357 if self._locked:
1320 1358 user_id, timelocked, reason = self._locked.split(':')
1321 1359 lock_values = int(user_id), timelocked, reason
1322 1360 else:
1323 1361 lock_values = [None, None, None]
1324 1362 return lock_values
1325 1363
1326 1364 @locked.setter
1327 1365 def locked(self, val):
1328 1366 if val and isinstance(val, (list, tuple)):
1329 1367 self._locked = ':'.join(map(str, val))
1330 1368 else:
1331 1369 self._locked = None
1332 1370
1333 1371 @hybrid_property
1334 1372 def changeset_cache(self):
1335 1373 from rhodecode.lib.vcs.backends.base import EmptyCommit
1336 1374 dummy = EmptyCommit().__json__()
1337 1375 if not self._changeset_cache:
1338 1376 return dummy
1339 1377 try:
1340 1378 return json.loads(self._changeset_cache)
1341 1379 except TypeError:
1342 1380 return dummy
1343 1381 except Exception:
1344 1382 log.error(traceback.format_exc())
1345 1383 return dummy
1346 1384
1347 1385 @changeset_cache.setter
1348 1386 def changeset_cache(self, val):
1349 1387 try:
1350 1388 self._changeset_cache = json.dumps(val)
1351 1389 except Exception:
1352 1390 log.error(traceback.format_exc())
1353 1391
1354 1392 @hybrid_property
1355 1393 def repo_name(self):
1356 1394 return self._repo_name
1357 1395
1358 1396 @repo_name.setter
1359 1397 def repo_name(self, value):
1360 1398 self._repo_name = value
1361 1399 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1362 1400
1363 1401 @classmethod
1364 1402 def normalize_repo_name(cls, repo_name):
1365 1403 """
1366 1404 Normalizes os specific repo_name to the format internally stored inside
1367 dabatabase using URL_SEP
1405 database using URL_SEP
1368 1406
1369 1407 :param cls:
1370 1408 :param repo_name:
1371 1409 """
1372 1410 return cls.NAME_SEP.join(repo_name.split(os.sep))
1373 1411
1374 1412 @classmethod
1375 def get_by_repo_name(cls, repo_name):
1376 q = Session().query(cls).filter(cls.repo_name == repo_name)
1377 q = q.options(joinedload(Repository.fork))\
1378 .options(joinedload(Repository.user))\
1379 .options(joinedload(Repository.group))
1413 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1414 session = Session()
1415 q = session.query(cls).filter(cls.repo_name == repo_name)
1416
1417 if cache:
1418 if identity_cache:
1419 val = cls.identity_cache(session, 'repo_name', repo_name)
1420 if val:
1421 return val
1422 else:
1423 q = q.options(
1424 FromCache("sql_cache_short",
1425 "get_repo_by_name_%s" % _hash_key(repo_name)))
1426
1380 1427 return q.scalar()
1381 1428
1382 1429 @classmethod
1383 1430 def get_by_full_path(cls, repo_full_path):
1384 1431 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1385 1432 repo_name = cls.normalize_repo_name(repo_name)
1386 1433 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1387 1434
1388 1435 @classmethod
1389 1436 def get_repo_forks(cls, repo_id):
1390 1437 return cls.query().filter(Repository.fork_id == repo_id)
1391 1438
1392 1439 @classmethod
1393 1440 def base_path(cls):
1394 1441 """
1395 1442 Returns base path when all repos are stored
1396 1443
1397 1444 :param cls:
1398 1445 """
1399 1446 q = Session().query(RhodeCodeUi)\
1400 1447 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1401 1448 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1402 1449 return q.one().ui_value
1403 1450
1404 1451 @classmethod
1405 1452 def is_valid(cls, repo_name):
1406 1453 """
1407 1454 returns True if given repo name is a valid filesystem repository
1408 1455
1409 1456 :param cls:
1410 1457 :param repo_name:
1411 1458 """
1412 1459 from rhodecode.lib.utils import is_valid_repo
1413 1460
1414 1461 return is_valid_repo(repo_name, cls.base_path())
1415 1462
1416 1463 @classmethod
1417 1464 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1418 1465 case_insensitive=True):
1419 1466 q = Repository.query()
1420 1467
1421 1468 if not isinstance(user_id, Optional):
1422 1469 q = q.filter(Repository.user_id == user_id)
1423 1470
1424 1471 if not isinstance(group_id, Optional):
1425 1472 q = q.filter(Repository.group_id == group_id)
1426 1473
1427 1474 if case_insensitive:
1428 1475 q = q.order_by(func.lower(Repository.repo_name))
1429 1476 else:
1430 1477 q = q.order_by(Repository.repo_name)
1431 1478 return q.all()
1432 1479
1433 1480 @property
1434 1481 def forks(self):
1435 1482 """
1436 1483 Return forks of this repo
1437 1484 """
1438 1485 return Repository.get_repo_forks(self.repo_id)
1439 1486
1440 1487 @property
1441 1488 def parent(self):
1442 1489 """
1443 1490 Returns fork parent
1444 1491 """
1445 1492 return self.fork
1446 1493
1447 1494 @property
1448 1495 def just_name(self):
1449 1496 return self.repo_name.split(self.NAME_SEP)[-1]
1450 1497
1451 1498 @property
1452 1499 def groups_with_parents(self):
1453 1500 groups = []
1454 1501 if self.group is None:
1455 1502 return groups
1456 1503
1457 1504 cur_gr = self.group
1458 1505 groups.insert(0, cur_gr)
1459 1506 while 1:
1460 1507 gr = getattr(cur_gr, 'parent_group', None)
1461 1508 cur_gr = cur_gr.parent_group
1462 1509 if gr is None:
1463 1510 break
1464 1511 groups.insert(0, gr)
1465 1512
1466 1513 return groups
1467 1514
1468 1515 @property
1469 1516 def groups_and_repo(self):
1470 1517 return self.groups_with_parents, self
1471 1518
1472 1519 @LazyProperty
1473 1520 def repo_path(self):
1474 1521 """
1475 1522 Returns base full path for that repository means where it actually
1476 1523 exists on a filesystem
1477 1524 """
1478 1525 q = Session().query(RhodeCodeUi).filter(
1479 1526 RhodeCodeUi.ui_key == self.NAME_SEP)
1480 1527 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1481 1528 return q.one().ui_value
1482 1529
1483 1530 @property
1484 1531 def repo_full_path(self):
1485 1532 p = [self.repo_path]
1486 1533 # we need to split the name by / since this is how we store the
1487 1534 # names in the database, but that eventually needs to be converted
1488 1535 # into a valid system path
1489 1536 p += self.repo_name.split(self.NAME_SEP)
1490 1537 return os.path.join(*map(safe_unicode, p))
1491 1538
1492 1539 @property
1493 1540 def cache_keys(self):
1494 1541 """
1495 1542 Returns associated cache keys for that repo
1496 1543 """
1497 1544 return CacheKey.query()\
1498 1545 .filter(CacheKey.cache_args == self.repo_name)\
1499 1546 .order_by(CacheKey.cache_key)\
1500 1547 .all()
1501 1548
1502 1549 def get_new_name(self, repo_name):
1503 1550 """
1504 1551 returns new full repository name based on assigned group and new new
1505 1552
1506 1553 :param group_name:
1507 1554 """
1508 1555 path_prefix = self.group.full_path_splitted if self.group else []
1509 1556 return self.NAME_SEP.join(path_prefix + [repo_name])
1510 1557
1511 1558 @property
1512 1559 def _config(self):
1513 1560 """
1514 1561 Returns db based config object.
1515 1562 """
1516 1563 from rhodecode.lib.utils import make_db_config
1517 1564 return make_db_config(clear_session=False, repo=self)
1518 1565
1519 1566 def permissions(self, with_admins=True, with_owner=True):
1520 1567 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1521 1568 q = q.options(joinedload(UserRepoToPerm.repository),
1522 1569 joinedload(UserRepoToPerm.user),
1523 1570 joinedload(UserRepoToPerm.permission),)
1524 1571
1525 1572 # get owners and admins and permissions. We do a trick of re-writing
1526 1573 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1527 1574 # has a global reference and changing one object propagates to all
1528 1575 # others. This means if admin is also an owner admin_row that change
1529 1576 # would propagate to both objects
1530 1577 perm_rows = []
1531 1578 for _usr in q.all():
1532 1579 usr = AttributeDict(_usr.user.get_dict())
1533 1580 usr.permission = _usr.permission.permission_name
1534 1581 perm_rows.append(usr)
1535 1582
1536 1583 # filter the perm rows by 'default' first and then sort them by
1537 1584 # admin,write,read,none permissions sorted again alphabetically in
1538 1585 # each group
1539 1586 perm_rows = sorted(perm_rows, key=display_sort)
1540 1587
1541 1588 _admin_perm = 'repository.admin'
1542 1589 owner_row = []
1543 1590 if with_owner:
1544 1591 usr = AttributeDict(self.user.get_dict())
1545 1592 usr.owner_row = True
1546 1593 usr.permission = _admin_perm
1547 1594 owner_row.append(usr)
1548 1595
1549 1596 super_admin_rows = []
1550 1597 if with_admins:
1551 1598 for usr in User.get_all_super_admins():
1552 1599 # if this admin is also owner, don't double the record
1553 1600 if usr.user_id == owner_row[0].user_id:
1554 1601 owner_row[0].admin_row = True
1555 1602 else:
1556 1603 usr = AttributeDict(usr.get_dict())
1557 1604 usr.admin_row = True
1558 1605 usr.permission = _admin_perm
1559 1606 super_admin_rows.append(usr)
1560 1607
1561 1608 return super_admin_rows + owner_row + perm_rows
1562 1609
1563 1610 def permission_user_groups(self):
1564 1611 q = UserGroupRepoToPerm.query().filter(
1565 1612 UserGroupRepoToPerm.repository == self)
1566 1613 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1567 1614 joinedload(UserGroupRepoToPerm.users_group),
1568 1615 joinedload(UserGroupRepoToPerm.permission),)
1569 1616
1570 1617 perm_rows = []
1571 1618 for _user_group in q.all():
1572 1619 usr = AttributeDict(_user_group.users_group.get_dict())
1573 1620 usr.permission = _user_group.permission.permission_name
1574 1621 perm_rows.append(usr)
1575 1622
1576 1623 return perm_rows
1577 1624
1578 1625 def get_api_data(self, include_secrets=False):
1579 1626 """
1580 1627 Common function for generating repo api data
1581 1628
1582 1629 :param include_secrets: See :meth:`User.get_api_data`.
1583 1630
1584 1631 """
1585 1632 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1586 1633 # move this methods on models level.
1587 1634 from rhodecode.model.settings import SettingsModel
1588 1635
1589 1636 repo = self
1590 1637 _user_id, _time, _reason = self.locked
1591 1638
1592 1639 data = {
1593 1640 'repo_id': repo.repo_id,
1594 1641 'repo_name': repo.repo_name,
1595 1642 'repo_type': repo.repo_type,
1596 1643 'clone_uri': repo.clone_uri or '',
1597 1644 'private': repo.private,
1598 1645 'created_on': repo.created_on,
1599 1646 'description': repo.description,
1600 1647 'landing_rev': repo.landing_rev,
1601 1648 'owner': repo.user.username,
1602 1649 'fork_of': repo.fork.repo_name if repo.fork else None,
1603 1650 'enable_statistics': repo.enable_statistics,
1604 1651 'enable_locking': repo.enable_locking,
1605 1652 'enable_downloads': repo.enable_downloads,
1606 1653 'last_changeset': repo.changeset_cache,
1607 1654 'locked_by': User.get(_user_id).get_api_data(
1608 1655 include_secrets=include_secrets) if _user_id else None,
1609 1656 'locked_date': time_to_datetime(_time) if _time else None,
1610 1657 'lock_reason': _reason if _reason else None,
1611 1658 }
1612 1659
1613 1660 # TODO: mikhail: should be per-repo settings here
1614 1661 rc_config = SettingsModel().get_all_settings()
1615 1662 repository_fields = str2bool(
1616 1663 rc_config.get('rhodecode_repository_fields'))
1617 1664 if repository_fields:
1618 1665 for f in self.extra_fields:
1619 1666 data[f.field_key_prefixed] = f.field_value
1620 1667
1621 1668 return data
1622 1669
1623 1670 @classmethod
1624 1671 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1625 1672 if not lock_time:
1626 1673 lock_time = time.time()
1627 1674 if not lock_reason:
1628 1675 lock_reason = cls.LOCK_AUTOMATIC
1629 1676 repo.locked = [user_id, lock_time, lock_reason]
1630 1677 Session().add(repo)
1631 1678 Session().commit()
1632 1679
1633 1680 @classmethod
1634 1681 def unlock(cls, repo):
1635 1682 repo.locked = None
1636 1683 Session().add(repo)
1637 1684 Session().commit()
1638 1685
1639 1686 @classmethod
1640 1687 def getlock(cls, repo):
1641 1688 return repo.locked
1642 1689
1643 1690 def is_user_lock(self, user_id):
1644 1691 if self.lock[0]:
1645 1692 lock_user_id = safe_int(self.lock[0])
1646 1693 user_id = safe_int(user_id)
1647 1694 # both are ints, and they are equal
1648 1695 return all([lock_user_id, user_id]) and lock_user_id == user_id
1649 1696
1650 1697 return False
1651 1698
1652 1699 def get_locking_state(self, action, user_id, only_when_enabled=True):
1653 1700 """
1654 1701 Checks locking on this repository, if locking is enabled and lock is
1655 1702 present returns a tuple of make_lock, locked, locked_by.
1656 1703 make_lock can have 3 states None (do nothing) True, make lock
1657 1704 False release lock, This value is later propagated to hooks, which
1658 1705 do the locking. Think about this as signals passed to hooks what to do.
1659 1706
1660 1707 """
1661 1708 # TODO: johbo: This is part of the business logic and should be moved
1662 1709 # into the RepositoryModel.
1663 1710
1664 1711 if action not in ('push', 'pull'):
1665 1712 raise ValueError("Invalid action value: %s" % repr(action))
1666 1713
1667 1714 # defines if locked error should be thrown to user
1668 1715 currently_locked = False
1669 1716 # defines if new lock should be made, tri-state
1670 1717 make_lock = None
1671 1718 repo = self
1672 1719 user = User.get(user_id)
1673 1720
1674 1721 lock_info = repo.locked
1675 1722
1676 1723 if repo and (repo.enable_locking or not only_when_enabled):
1677 1724 if action == 'push':
1678 1725 # check if it's already locked !, if it is compare users
1679 1726 locked_by_user_id = lock_info[0]
1680 1727 if user.user_id == locked_by_user_id:
1681 1728 log.debug(
1682 1729 'Got `push` action from user %s, now unlocking', user)
1683 1730 # unlock if we have push from user who locked
1684 1731 make_lock = False
1685 1732 else:
1686 1733 # we're not the same user who locked, ban with
1687 1734 # code defined in settings (default is 423 HTTP Locked) !
1688 1735 log.debug('Repo %s is currently locked by %s', repo, user)
1689 1736 currently_locked = True
1690 1737 elif action == 'pull':
1691 1738 # [0] user [1] date
1692 1739 if lock_info[0] and lock_info[1]:
1693 1740 log.debug('Repo %s is currently locked by %s', repo, user)
1694 1741 currently_locked = True
1695 1742 else:
1696 1743 log.debug('Setting lock on repo %s by %s', repo, user)
1697 1744 make_lock = True
1698 1745
1699 1746 else:
1700 1747 log.debug('Repository %s do not have locking enabled', repo)
1701 1748
1702 1749 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1703 1750 make_lock, currently_locked, lock_info)
1704 1751
1705 1752 from rhodecode.lib.auth import HasRepoPermissionAny
1706 1753 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1707 1754 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1708 1755 # if we don't have at least write permission we cannot make a lock
1709 1756 log.debug('lock state reset back to FALSE due to lack '
1710 1757 'of at least read permission')
1711 1758 make_lock = False
1712 1759
1713 1760 return make_lock, currently_locked, lock_info
1714 1761
1715 1762 @property
1716 1763 def last_db_change(self):
1717 1764 return self.updated_on
1718 1765
1719 1766 @property
1720 1767 def clone_uri_hidden(self):
1721 1768 clone_uri = self.clone_uri
1722 1769 if clone_uri:
1723 1770 import urlobject
1724 url_obj = urlobject.URLObject(self.clone_uri)
1771 url_obj = urlobject.URLObject(clone_uri)
1725 1772 if url_obj.password:
1726 1773 clone_uri = url_obj.with_password('*****')
1727 1774 return clone_uri
1728 1775
1729 1776 def clone_url(self, **override):
1730 1777 qualified_home_url = url('home', qualified=True)
1731 1778
1732 1779 uri_tmpl = None
1733 1780 if 'with_id' in override:
1734 1781 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1735 1782 del override['with_id']
1736 1783
1737 1784 if 'uri_tmpl' in override:
1738 1785 uri_tmpl = override['uri_tmpl']
1739 1786 del override['uri_tmpl']
1740 1787
1741 1788 # we didn't override our tmpl from **overrides
1742 1789 if not uri_tmpl:
1743 1790 uri_tmpl = self.DEFAULT_CLONE_URI
1744 1791 try:
1745 1792 from pylons import tmpl_context as c
1746 1793 uri_tmpl = c.clone_uri_tmpl
1747 1794 except Exception:
1748 1795 # in any case if we call this outside of request context,
1749 1796 # ie, not having tmpl_context set up
1750 1797 pass
1751 1798
1752 1799 return get_clone_url(uri_tmpl=uri_tmpl,
1753 1800 qualifed_home_url=qualified_home_url,
1754 1801 repo_name=self.repo_name,
1755 1802 repo_id=self.repo_id, **override)
1756 1803
1757 1804 def set_state(self, state):
1758 1805 self.repo_state = state
1759 1806 Session().add(self)
1760 1807 #==========================================================================
1761 1808 # SCM PROPERTIES
1762 1809 #==========================================================================
1763 1810
1764 1811 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1765 1812 return get_commit_safe(
1766 1813 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1767 1814
1768 1815 def get_changeset(self, rev=None, pre_load=None):
1769 1816 warnings.warn("Use get_commit", DeprecationWarning)
1770 1817 commit_id = None
1771 1818 commit_idx = None
1772 1819 if isinstance(rev, basestring):
1773 1820 commit_id = rev
1774 1821 else:
1775 1822 commit_idx = rev
1776 1823 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1777 1824 pre_load=pre_load)
1778 1825
1779 1826 def get_landing_commit(self):
1780 1827 """
1781 1828 Returns landing commit, or if that doesn't exist returns the tip
1782 1829 """
1783 1830 _rev_type, _rev = self.landing_rev
1784 1831 commit = self.get_commit(_rev)
1785 1832 if isinstance(commit, EmptyCommit):
1786 1833 return self.get_commit()
1787 1834 return commit
1788 1835
1789 1836 def update_commit_cache(self, cs_cache=None, config=None):
1790 1837 """
1791 1838 Update cache of last changeset for repository, keys should be::
1792 1839
1793 1840 short_id
1794 1841 raw_id
1795 1842 revision
1796 1843 parents
1797 1844 message
1798 1845 date
1799 1846 author
1800 1847
1801 1848 :param cs_cache:
1802 1849 """
1803 1850 from rhodecode.lib.vcs.backends.base import BaseChangeset
1804 1851 if cs_cache is None:
1805 1852 # use no-cache version here
1806 1853 scm_repo = self.scm_instance(cache=False, config=config)
1807 1854 if scm_repo:
1808 1855 cs_cache = scm_repo.get_commit(
1809 1856 pre_load=["author", "date", "message", "parents"])
1810 1857 else:
1811 1858 cs_cache = EmptyCommit()
1812 1859
1813 1860 if isinstance(cs_cache, BaseChangeset):
1814 1861 cs_cache = cs_cache.__json__()
1815 1862
1816 1863 def is_outdated(new_cs_cache):
1817 1864 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1818 1865 return True
1819 1866 return False
1820 1867
1821 1868 # check if we have maybe already latest cached revision
1822 1869 if is_outdated(cs_cache) or not self.changeset_cache:
1823 1870 _default = datetime.datetime.fromtimestamp(0)
1824 1871 last_change = cs_cache.get('date') or _default
1825 1872 log.debug('updated repo %s with new cs cache %s',
1826 1873 self.repo_name, cs_cache)
1827 1874 self.updated_on = last_change
1828 1875 self.changeset_cache = cs_cache
1829 1876 Session().add(self)
1830 1877 Session().commit()
1831 1878 else:
1832 1879 log.debug('Skipping update_commit_cache for repo:`%s` '
1833 1880 'commit already with latest changes', self.repo_name)
1834 1881
1835 1882 @property
1836 1883 def tip(self):
1837 1884 return self.get_commit('tip')
1838 1885
1839 1886 @property
1840 1887 def author(self):
1841 1888 return self.tip.author
1842 1889
1843 1890 @property
1844 1891 def last_change(self):
1845 1892 return self.scm_instance().last_change
1846 1893
1847 1894 def get_comments(self, revisions=None):
1848 1895 """
1849 1896 Returns comments for this repository grouped by revisions
1850 1897
1851 1898 :param revisions: filter query by revisions only
1852 1899 """
1853 1900 cmts = ChangesetComment.query()\
1854 1901 .filter(ChangesetComment.repo == self)
1855 1902 if revisions:
1856 1903 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1857 1904 grouped = collections.defaultdict(list)
1858 1905 for cmt in cmts.all():
1859 1906 grouped[cmt.revision].append(cmt)
1860 1907 return grouped
1861 1908
1862 1909 def statuses(self, revisions=None):
1863 1910 """
1864 1911 Returns statuses for this repository
1865 1912
1866 1913 :param revisions: list of revisions to get statuses for
1867 1914 """
1868 1915 statuses = ChangesetStatus.query()\
1869 1916 .filter(ChangesetStatus.repo == self)\
1870 1917 .filter(ChangesetStatus.version == 0)
1871 1918
1872 1919 if revisions:
1873 1920 # Try doing the filtering in chunks to avoid hitting limits
1874 1921 size = 500
1875 1922 status_results = []
1876 1923 for chunk in xrange(0, len(revisions), size):
1877 1924 status_results += statuses.filter(
1878 1925 ChangesetStatus.revision.in_(
1879 1926 revisions[chunk: chunk+size])
1880 1927 ).all()
1881 1928 else:
1882 1929 status_results = statuses.all()
1883 1930
1884 1931 grouped = {}
1885 1932
1886 1933 # maybe we have open new pullrequest without a status?
1887 1934 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1888 1935 status_lbl = ChangesetStatus.get_status_lbl(stat)
1889 1936 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1890 1937 for rev in pr.revisions:
1891 1938 pr_id = pr.pull_request_id
1892 1939 pr_repo = pr.target_repo.repo_name
1893 1940 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1894 1941
1895 1942 for stat in status_results:
1896 1943 pr_id = pr_repo = None
1897 1944 if stat.pull_request:
1898 1945 pr_id = stat.pull_request.pull_request_id
1899 1946 pr_repo = stat.pull_request.target_repo.repo_name
1900 1947 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1901 1948 pr_id, pr_repo]
1902 1949 return grouped
1903 1950
1904 1951 # ==========================================================================
1905 1952 # SCM CACHE INSTANCE
1906 1953 # ==========================================================================
1907 1954
1908 1955 def scm_instance(self, **kwargs):
1909 1956 import rhodecode
1910 1957
1911 1958 # Passing a config will not hit the cache currently only used
1912 1959 # for repo2dbmapper
1913 1960 config = kwargs.pop('config', None)
1914 1961 cache = kwargs.pop('cache', None)
1915 1962 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1916 1963 # if cache is NOT defined use default global, else we have a full
1917 1964 # control over cache behaviour
1918 1965 if cache is None and full_cache and not config:
1919 1966 return self._get_instance_cached()
1920 1967 return self._get_instance(cache=bool(cache), config=config)
1921 1968
1922 1969 def _get_instance_cached(self):
1923 1970 @cache_region('long_term')
1924 1971 def _get_repo(cache_key):
1925 1972 return self._get_instance()
1926 1973
1927 1974 invalidator_context = CacheKey.repo_context_cache(
1928 1975 _get_repo, self.repo_name, None)
1929 1976
1930 1977 with invalidator_context as context:
1931 1978 context.invalidate()
1932 1979 repo = context.compute()
1933 1980
1934 1981 return repo
1935 1982
1936 1983 def _get_instance(self, cache=True, config=None):
1937 1984 repo_full_path = self.repo_full_path
1938 1985 try:
1939 1986 vcs_alias = get_scm(repo_full_path)[0]
1940 1987 log.debug(
1941 1988 'Creating instance of %s repository from %s',
1942 1989 vcs_alias, repo_full_path)
1943 1990 backend = get_backend(vcs_alias)
1944 1991 except VCSError:
1945 1992 log.exception(
1946 1993 'Perhaps this repository is in db and not in '
1947 1994 'filesystem run rescan repositories with '
1948 1995 '"destroy old data" option from admin panel')
1949 1996 return
1950 1997
1951 1998 config = config or self._config
1952 1999 custom_wire = {
1953 2000 'cache': cache # controls the vcs.remote cache
1954 2001 }
1955 2002 repo = backend(
1956 2003 safe_str(repo_full_path), config=config, create=False,
1957 2004 with_wire=custom_wire)
1958 2005
1959 2006 return repo
1960 2007
1961 2008 def __json__(self):
1962 2009 return {'landing_rev': self.landing_rev}
1963 2010
1964 2011 def get_dict(self):
1965 2012
1966 2013 # Since we transformed `repo_name` to a hybrid property, we need to
1967 2014 # keep compatibility with the code which uses `repo_name` field.
1968 2015
1969 2016 result = super(Repository, self).get_dict()
1970 2017 result['repo_name'] = result.pop('_repo_name', None)
1971 2018 return result
1972 2019
1973 2020
1974 2021 class RepoGroup(Base, BaseModel):
1975 2022 __tablename__ = 'groups'
1976 2023 __table_args__ = (
1977 2024 UniqueConstraint('group_name', 'group_parent_id'),
1978 2025 CheckConstraint('group_id != group_parent_id'),
1979 2026 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1980 2027 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1981 2028 )
1982 2029 __mapper_args__ = {'order_by': 'group_name'}
1983 2030
1984 2031 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
1985 2032
1986 2033 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1987 2034 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
1988 2035 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1989 2036 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
1990 2037 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1991 2038 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1992 2039 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1993 2040
1994 2041 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1995 2042 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1996 2043 parent_group = relationship('RepoGroup', remote_side=group_id)
1997 2044 user = relationship('User')
1998 2045
1999 2046 def __init__(self, group_name='', parent_group=None):
2000 2047 self.group_name = group_name
2001 2048 self.parent_group = parent_group
2002 2049
2003 2050 def __unicode__(self):
2004 2051 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2005 2052 self.group_name)
2006 2053
2007 2054 @classmethod
2008 2055 def _generate_choice(cls, repo_group):
2009 2056 from webhelpers.html import literal as _literal
2010 2057 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2011 2058 return repo_group.group_id, _name(repo_group.full_path_splitted)
2012 2059
2013 2060 @classmethod
2014 2061 def groups_choices(cls, groups=None, show_empty_group=True):
2015 2062 if not groups:
2016 2063 groups = cls.query().all()
2017 2064
2018 2065 repo_groups = []
2019 2066 if show_empty_group:
2020 2067 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2021 2068
2022 2069 repo_groups.extend([cls._generate_choice(x) for x in groups])
2023 2070
2024 2071 repo_groups = sorted(
2025 2072 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2026 2073 return repo_groups
2027 2074
2028 2075 @classmethod
2029 2076 def url_sep(cls):
2030 2077 return URL_SEP
2031 2078
2032 2079 @classmethod
2033 2080 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2034 2081 if case_insensitive:
2035 2082 gr = cls.query().filter(func.lower(cls.group_name)
2036 2083 == func.lower(group_name))
2037 2084 else:
2038 2085 gr = cls.query().filter(cls.group_name == group_name)
2039 2086 if cache:
2040 2087 gr = gr.options(FromCache(
2041 2088 "sql_cache_short",
2042 2089 "get_group_%s" % _hash_key(group_name)))
2043 2090 return gr.scalar()
2044 2091
2045 2092 @classmethod
2046 2093 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2047 2094 case_insensitive=True):
2048 2095 q = RepoGroup.query()
2049 2096
2050 2097 if not isinstance(user_id, Optional):
2051 2098 q = q.filter(RepoGroup.user_id == user_id)
2052 2099
2053 2100 if not isinstance(group_id, Optional):
2054 2101 q = q.filter(RepoGroup.group_parent_id == group_id)
2055 2102
2056 2103 if case_insensitive:
2057 2104 q = q.order_by(func.lower(RepoGroup.group_name))
2058 2105 else:
2059 2106 q = q.order_by(RepoGroup.group_name)
2060 2107 return q.all()
2061 2108
2062 2109 @property
2063 2110 def parents(self):
2064 2111 parents_recursion_limit = 10
2065 2112 groups = []
2066 2113 if self.parent_group is None:
2067 2114 return groups
2068 2115 cur_gr = self.parent_group
2069 2116 groups.insert(0, cur_gr)
2070 2117 cnt = 0
2071 2118 while 1:
2072 2119 cnt += 1
2073 2120 gr = getattr(cur_gr, 'parent_group', None)
2074 2121 cur_gr = cur_gr.parent_group
2075 2122 if gr is None:
2076 2123 break
2077 2124 if cnt == parents_recursion_limit:
2078 2125 # this will prevent accidental infinit loops
2079 2126 log.error(('more than %s parents found for group %s, stopping '
2080 2127 'recursive parent fetching' % (parents_recursion_limit, self)))
2081 2128 break
2082 2129
2083 2130 groups.insert(0, gr)
2084 2131 return groups
2085 2132
2086 2133 @property
2087 2134 def children(self):
2088 2135 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2089 2136
2090 2137 @property
2091 2138 def name(self):
2092 2139 return self.group_name.split(RepoGroup.url_sep())[-1]
2093 2140
2094 2141 @property
2095 2142 def full_path(self):
2096 2143 return self.group_name
2097 2144
2098 2145 @property
2099 2146 def full_path_splitted(self):
2100 2147 return self.group_name.split(RepoGroup.url_sep())
2101 2148
2102 2149 @property
2103 2150 def repositories(self):
2104 2151 return Repository.query()\
2105 2152 .filter(Repository.group == self)\
2106 2153 .order_by(Repository.repo_name)
2107 2154
2108 2155 @property
2109 2156 def repositories_recursive_count(self):
2110 2157 cnt = self.repositories.count()
2111 2158
2112 2159 def children_count(group):
2113 2160 cnt = 0
2114 2161 for child in group.children:
2115 2162 cnt += child.repositories.count()
2116 2163 cnt += children_count(child)
2117 2164 return cnt
2118 2165
2119 2166 return cnt + children_count(self)
2120 2167
2121 2168 def _recursive_objects(self, include_repos=True):
2122 2169 all_ = []
2123 2170
2124 2171 def _get_members(root_gr):
2125 2172 if include_repos:
2126 2173 for r in root_gr.repositories:
2127 2174 all_.append(r)
2128 2175 childs = root_gr.children.all()
2129 2176 if childs:
2130 2177 for gr in childs:
2131 2178 all_.append(gr)
2132 2179 _get_members(gr)
2133 2180
2134 2181 _get_members(self)
2135 2182 return [self] + all_
2136 2183
2137 2184 def recursive_groups_and_repos(self):
2138 2185 """
2139 2186 Recursive return all groups, with repositories in those groups
2140 2187 """
2141 2188 return self._recursive_objects()
2142 2189
2143 2190 def recursive_groups(self):
2144 2191 """
2145 2192 Returns all children groups for this group including children of children
2146 2193 """
2147 2194 return self._recursive_objects(include_repos=False)
2148 2195
2149 2196 def get_new_name(self, group_name):
2150 2197 """
2151 2198 returns new full group name based on parent and new name
2152 2199
2153 2200 :param group_name:
2154 2201 """
2155 2202 path_prefix = (self.parent_group.full_path_splitted if
2156 2203 self.parent_group else [])
2157 2204 return RepoGroup.url_sep().join(path_prefix + [group_name])
2158 2205
2159 2206 def permissions(self, with_admins=True, with_owner=True):
2160 2207 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2161 2208 q = q.options(joinedload(UserRepoGroupToPerm.group),
2162 2209 joinedload(UserRepoGroupToPerm.user),
2163 2210 joinedload(UserRepoGroupToPerm.permission),)
2164 2211
2165 2212 # get owners and admins and permissions. We do a trick of re-writing
2166 2213 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2167 2214 # has a global reference and changing one object propagates to all
2168 2215 # others. This means if admin is also an owner admin_row that change
2169 2216 # would propagate to both objects
2170 2217 perm_rows = []
2171 2218 for _usr in q.all():
2172 2219 usr = AttributeDict(_usr.user.get_dict())
2173 2220 usr.permission = _usr.permission.permission_name
2174 2221 perm_rows.append(usr)
2175 2222
2176 2223 # filter the perm rows by 'default' first and then sort them by
2177 2224 # admin,write,read,none permissions sorted again alphabetically in
2178 2225 # each group
2179 2226 perm_rows = sorted(perm_rows, key=display_sort)
2180 2227
2181 2228 _admin_perm = 'group.admin'
2182 2229 owner_row = []
2183 2230 if with_owner:
2184 2231 usr = AttributeDict(self.user.get_dict())
2185 2232 usr.owner_row = True
2186 2233 usr.permission = _admin_perm
2187 2234 owner_row.append(usr)
2188 2235
2189 2236 super_admin_rows = []
2190 2237 if with_admins:
2191 2238 for usr in User.get_all_super_admins():
2192 2239 # if this admin is also owner, don't double the record
2193 2240 if usr.user_id == owner_row[0].user_id:
2194 2241 owner_row[0].admin_row = True
2195 2242 else:
2196 2243 usr = AttributeDict(usr.get_dict())
2197 2244 usr.admin_row = True
2198 2245 usr.permission = _admin_perm
2199 2246 super_admin_rows.append(usr)
2200 2247
2201 2248 return super_admin_rows + owner_row + perm_rows
2202 2249
2203 2250 def permission_user_groups(self):
2204 2251 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2205 2252 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2206 2253 joinedload(UserGroupRepoGroupToPerm.users_group),
2207 2254 joinedload(UserGroupRepoGroupToPerm.permission),)
2208 2255
2209 2256 perm_rows = []
2210 2257 for _user_group in q.all():
2211 2258 usr = AttributeDict(_user_group.users_group.get_dict())
2212 2259 usr.permission = _user_group.permission.permission_name
2213 2260 perm_rows.append(usr)
2214 2261
2215 2262 return perm_rows
2216 2263
2217 2264 def get_api_data(self):
2218 2265 """
2219 2266 Common function for generating api data
2220 2267
2221 2268 """
2222 2269 group = self
2223 2270 data = {
2224 2271 'group_id': group.group_id,
2225 2272 'group_name': group.group_name,
2226 2273 'group_description': group.group_description,
2227 2274 'parent_group': group.parent_group.group_name if group.parent_group else None,
2228 2275 'repositories': [x.repo_name for x in group.repositories],
2229 2276 'owner': group.user.username,
2230 2277 }
2231 2278 return data
2232 2279
2233 2280
2234 2281 class Permission(Base, BaseModel):
2235 2282 __tablename__ = 'permissions'
2236 2283 __table_args__ = (
2237 2284 Index('p_perm_name_idx', 'permission_name'),
2238 2285 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2239 2286 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2240 2287 )
2241 2288 PERMS = [
2242 2289 ('hg.admin', _('RhodeCode Super Administrator')),
2243 2290
2244 2291 ('repository.none', _('Repository no access')),
2245 2292 ('repository.read', _('Repository read access')),
2246 2293 ('repository.write', _('Repository write access')),
2247 2294 ('repository.admin', _('Repository admin access')),
2248 2295
2249 2296 ('group.none', _('Repository group no access')),
2250 2297 ('group.read', _('Repository group read access')),
2251 2298 ('group.write', _('Repository group write access')),
2252 2299 ('group.admin', _('Repository group admin access')),
2253 2300
2254 2301 ('usergroup.none', _('User group no access')),
2255 2302 ('usergroup.read', _('User group read access')),
2256 2303 ('usergroup.write', _('User group write access')),
2257 2304 ('usergroup.admin', _('User group admin access')),
2258 2305
2259 2306 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2260 2307 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2261 2308
2262 2309 ('hg.usergroup.create.false', _('User Group creation disabled')),
2263 2310 ('hg.usergroup.create.true', _('User Group creation enabled')),
2264 2311
2265 2312 ('hg.create.none', _('Repository creation disabled')),
2266 2313 ('hg.create.repository', _('Repository creation enabled')),
2267 2314 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2268 2315 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2269 2316
2270 2317 ('hg.fork.none', _('Repository forking disabled')),
2271 2318 ('hg.fork.repository', _('Repository forking enabled')),
2272 2319
2273 2320 ('hg.register.none', _('Registration disabled')),
2274 2321 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2275 2322 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2276 2323
2277 2324 ('hg.extern_activate.manual', _('Manual activation of external account')),
2278 2325 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2279 2326
2280 2327 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2281 2328 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2282 2329 ]
2283 2330
2284 2331 # definition of system default permissions for DEFAULT user
2285 2332 DEFAULT_USER_PERMISSIONS = [
2286 2333 'repository.read',
2287 2334 'group.read',
2288 2335 'usergroup.read',
2289 2336 'hg.create.repository',
2290 2337 'hg.repogroup.create.false',
2291 2338 'hg.usergroup.create.false',
2292 2339 'hg.create.write_on_repogroup.true',
2293 2340 'hg.fork.repository',
2294 2341 'hg.register.manual_activate',
2295 2342 'hg.extern_activate.auto',
2296 2343 'hg.inherit_default_perms.true',
2297 2344 ]
2298 2345
2299 2346 # defines which permissions are more important higher the more important
2300 2347 # Weight defines which permissions are more important.
2301 2348 # The higher number the more important.
2302 2349 PERM_WEIGHTS = {
2303 2350 'repository.none': 0,
2304 2351 'repository.read': 1,
2305 2352 'repository.write': 3,
2306 2353 'repository.admin': 4,
2307 2354
2308 2355 'group.none': 0,
2309 2356 'group.read': 1,
2310 2357 'group.write': 3,
2311 2358 'group.admin': 4,
2312 2359
2313 2360 'usergroup.none': 0,
2314 2361 'usergroup.read': 1,
2315 2362 'usergroup.write': 3,
2316 2363 'usergroup.admin': 4,
2317 2364
2318 2365 'hg.repogroup.create.false': 0,
2319 2366 'hg.repogroup.create.true': 1,
2320 2367
2321 2368 'hg.usergroup.create.false': 0,
2322 2369 'hg.usergroup.create.true': 1,
2323 2370
2324 2371 'hg.fork.none': 0,
2325 2372 'hg.fork.repository': 1,
2326 2373 'hg.create.none': 0,
2327 2374 'hg.create.repository': 1
2328 2375 }
2329 2376
2330 2377 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2331 2378 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2332 2379 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2333 2380
2334 2381 def __unicode__(self):
2335 2382 return u"<%s('%s:%s')>" % (
2336 2383 self.__class__.__name__, self.permission_id, self.permission_name
2337 2384 )
2338 2385
2339 2386 @classmethod
2340 2387 def get_by_key(cls, key):
2341 2388 return cls.query().filter(cls.permission_name == key).scalar()
2342 2389
2343 2390 @classmethod
2344 2391 def get_default_repo_perms(cls, user_id, repo_id=None):
2345 2392 q = Session().query(UserRepoToPerm, Repository, Permission)\
2346 2393 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2347 2394 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2348 2395 .filter(UserRepoToPerm.user_id == user_id)
2349 2396 if repo_id:
2350 2397 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2351 2398 return q.all()
2352 2399
2353 2400 @classmethod
2354 2401 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2355 2402 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2356 2403 .join(
2357 2404 Permission,
2358 2405 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2359 2406 .join(
2360 2407 Repository,
2361 2408 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2362 2409 .join(
2363 2410 UserGroup,
2364 2411 UserGroupRepoToPerm.users_group_id ==
2365 2412 UserGroup.users_group_id)\
2366 2413 .join(
2367 2414 UserGroupMember,
2368 2415 UserGroupRepoToPerm.users_group_id ==
2369 2416 UserGroupMember.users_group_id)\
2370 2417 .filter(
2371 2418 UserGroupMember.user_id == user_id,
2372 2419 UserGroup.users_group_active == true())
2373 2420 if repo_id:
2374 2421 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2375 2422 return q.all()
2376 2423
2377 2424 @classmethod
2378 2425 def get_default_group_perms(cls, user_id, repo_group_id=None):
2379 2426 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2380 2427 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2381 2428 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2382 2429 .filter(UserRepoGroupToPerm.user_id == user_id)
2383 2430 if repo_group_id:
2384 2431 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2385 2432 return q.all()
2386 2433
2387 2434 @classmethod
2388 2435 def get_default_group_perms_from_user_group(
2389 2436 cls, user_id, repo_group_id=None):
2390 2437 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2391 2438 .join(
2392 2439 Permission,
2393 2440 UserGroupRepoGroupToPerm.permission_id ==
2394 2441 Permission.permission_id)\
2395 2442 .join(
2396 2443 RepoGroup,
2397 2444 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2398 2445 .join(
2399 2446 UserGroup,
2400 2447 UserGroupRepoGroupToPerm.users_group_id ==
2401 2448 UserGroup.users_group_id)\
2402 2449 .join(
2403 2450 UserGroupMember,
2404 2451 UserGroupRepoGroupToPerm.users_group_id ==
2405 2452 UserGroupMember.users_group_id)\
2406 2453 .filter(
2407 2454 UserGroupMember.user_id == user_id,
2408 2455 UserGroup.users_group_active == true())
2409 2456 if repo_group_id:
2410 2457 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2411 2458 return q.all()
2412 2459
2413 2460 @classmethod
2414 2461 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2415 2462 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2416 2463 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2417 2464 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2418 2465 .filter(UserUserGroupToPerm.user_id == user_id)
2419 2466 if user_group_id:
2420 2467 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2421 2468 return q.all()
2422 2469
2423 2470 @classmethod
2424 2471 def get_default_user_group_perms_from_user_group(
2425 2472 cls, user_id, user_group_id=None):
2426 2473 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2427 2474 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2428 2475 .join(
2429 2476 Permission,
2430 2477 UserGroupUserGroupToPerm.permission_id ==
2431 2478 Permission.permission_id)\
2432 2479 .join(
2433 2480 TargetUserGroup,
2434 2481 UserGroupUserGroupToPerm.target_user_group_id ==
2435 2482 TargetUserGroup.users_group_id)\
2436 2483 .join(
2437 2484 UserGroup,
2438 2485 UserGroupUserGroupToPerm.user_group_id ==
2439 2486 UserGroup.users_group_id)\
2440 2487 .join(
2441 2488 UserGroupMember,
2442 2489 UserGroupUserGroupToPerm.user_group_id ==
2443 2490 UserGroupMember.users_group_id)\
2444 2491 .filter(
2445 2492 UserGroupMember.user_id == user_id,
2446 2493 UserGroup.users_group_active == true())
2447 2494 if user_group_id:
2448 2495 q = q.filter(
2449 2496 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2450 2497
2451 2498 return q.all()
2452 2499
2453 2500
2454 2501 class UserRepoToPerm(Base, BaseModel):
2455 2502 __tablename__ = 'repo_to_perm'
2456 2503 __table_args__ = (
2457 2504 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2458 2505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2459 2506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2460 2507 )
2461 2508 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2462 2509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2463 2510 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2464 2511 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2465 2512
2466 2513 user = relationship('User')
2467 2514 repository = relationship('Repository')
2468 2515 permission = relationship('Permission')
2469 2516
2470 2517 @classmethod
2471 2518 def create(cls, user, repository, permission):
2472 2519 n = cls()
2473 2520 n.user = user
2474 2521 n.repository = repository
2475 2522 n.permission = permission
2476 2523 Session().add(n)
2477 2524 return n
2478 2525
2479 2526 def __unicode__(self):
2480 2527 return u'<%s => %s >' % (self.user, self.repository)
2481 2528
2482 2529
2483 2530 class UserUserGroupToPerm(Base, BaseModel):
2484 2531 __tablename__ = 'user_user_group_to_perm'
2485 2532 __table_args__ = (
2486 2533 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2487 2534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2488 2535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2489 2536 )
2490 2537 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2491 2538 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2492 2539 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2493 2540 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2494 2541
2495 2542 user = relationship('User')
2496 2543 user_group = relationship('UserGroup')
2497 2544 permission = relationship('Permission')
2498 2545
2499 2546 @classmethod
2500 2547 def create(cls, user, user_group, permission):
2501 2548 n = cls()
2502 2549 n.user = user
2503 2550 n.user_group = user_group
2504 2551 n.permission = permission
2505 2552 Session().add(n)
2506 2553 return n
2507 2554
2508 2555 def __unicode__(self):
2509 2556 return u'<%s => %s >' % (self.user, self.user_group)
2510 2557
2511 2558
2512 2559 class UserToPerm(Base, BaseModel):
2513 2560 __tablename__ = 'user_to_perm'
2514 2561 __table_args__ = (
2515 2562 UniqueConstraint('user_id', 'permission_id'),
2516 2563 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2517 2564 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2518 2565 )
2519 2566 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2520 2567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2521 2568 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2522 2569
2523 2570 user = relationship('User')
2524 2571 permission = relationship('Permission', lazy='joined')
2525 2572
2526 2573 def __unicode__(self):
2527 2574 return u'<%s => %s >' % (self.user, self.permission)
2528 2575
2529 2576
2530 2577 class UserGroupRepoToPerm(Base, BaseModel):
2531 2578 __tablename__ = 'users_group_repo_to_perm'
2532 2579 __table_args__ = (
2533 2580 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2534 2581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2535 2582 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2536 2583 )
2537 2584 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2538 2585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2539 2586 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2540 2587 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2541 2588
2542 2589 users_group = relationship('UserGroup')
2543 2590 permission = relationship('Permission')
2544 2591 repository = relationship('Repository')
2545 2592
2546 2593 @classmethod
2547 2594 def create(cls, users_group, repository, permission):
2548 2595 n = cls()
2549 2596 n.users_group = users_group
2550 2597 n.repository = repository
2551 2598 n.permission = permission
2552 2599 Session().add(n)
2553 2600 return n
2554 2601
2555 2602 def __unicode__(self):
2556 2603 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2557 2604
2558 2605
2559 2606 class UserGroupUserGroupToPerm(Base, BaseModel):
2560 2607 __tablename__ = 'user_group_user_group_to_perm'
2561 2608 __table_args__ = (
2562 2609 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2563 2610 CheckConstraint('target_user_group_id != user_group_id'),
2564 2611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2565 2612 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2566 2613 )
2567 2614 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2568 2615 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2569 2616 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2570 2617 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2571 2618
2572 2619 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2573 2620 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2574 2621 permission = relationship('Permission')
2575 2622
2576 2623 @classmethod
2577 2624 def create(cls, target_user_group, user_group, permission):
2578 2625 n = cls()
2579 2626 n.target_user_group = target_user_group
2580 2627 n.user_group = user_group
2581 2628 n.permission = permission
2582 2629 Session().add(n)
2583 2630 return n
2584 2631
2585 2632 def __unicode__(self):
2586 2633 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2587 2634
2588 2635
2589 2636 class UserGroupToPerm(Base, BaseModel):
2590 2637 __tablename__ = 'users_group_to_perm'
2591 2638 __table_args__ = (
2592 2639 UniqueConstraint('users_group_id', 'permission_id',),
2593 2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2594 2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2595 2642 )
2596 2643 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2597 2644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2598 2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2599 2646
2600 2647 users_group = relationship('UserGroup')
2601 2648 permission = relationship('Permission')
2602 2649
2603 2650
2604 2651 class UserRepoGroupToPerm(Base, BaseModel):
2605 2652 __tablename__ = 'user_repo_group_to_perm'
2606 2653 __table_args__ = (
2607 2654 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2608 2655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2609 2656 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2610 2657 )
2611 2658
2612 2659 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2613 2660 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2614 2661 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2615 2662 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2616 2663
2617 2664 user = relationship('User')
2618 2665 group = relationship('RepoGroup')
2619 2666 permission = relationship('Permission')
2620 2667
2621 2668 @classmethod
2622 2669 def create(cls, user, repository_group, permission):
2623 2670 n = cls()
2624 2671 n.user = user
2625 2672 n.group = repository_group
2626 2673 n.permission = permission
2627 2674 Session().add(n)
2628 2675 return n
2629 2676
2630 2677
2631 2678 class UserGroupRepoGroupToPerm(Base, BaseModel):
2632 2679 __tablename__ = 'users_group_repo_group_to_perm'
2633 2680 __table_args__ = (
2634 2681 UniqueConstraint('users_group_id', 'group_id'),
2635 2682 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2636 2683 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2637 2684 )
2638 2685
2639 2686 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2640 2687 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2641 2688 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2642 2689 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2643 2690
2644 2691 users_group = relationship('UserGroup')
2645 2692 permission = relationship('Permission')
2646 2693 group = relationship('RepoGroup')
2647 2694
2648 2695 @classmethod
2649 2696 def create(cls, user_group, repository_group, permission):
2650 2697 n = cls()
2651 2698 n.users_group = user_group
2652 2699 n.group = repository_group
2653 2700 n.permission = permission
2654 2701 Session().add(n)
2655 2702 return n
2656 2703
2657 2704 def __unicode__(self):
2658 2705 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2659 2706
2660 2707
2661 2708 class Statistics(Base, BaseModel):
2662 2709 __tablename__ = 'statistics'
2663 2710 __table_args__ = (
2664 2711 UniqueConstraint('repository_id'),
2665 2712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2666 2713 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2667 2714 )
2668 2715 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2669 2716 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2670 2717 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2671 2718 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2672 2719 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2673 2720 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2674 2721
2675 2722 repository = relationship('Repository', single_parent=True)
2676 2723
2677 2724
2678 2725 class UserFollowing(Base, BaseModel):
2679 2726 __tablename__ = 'user_followings'
2680 2727 __table_args__ = (
2681 2728 UniqueConstraint('user_id', 'follows_repository_id'),
2682 2729 UniqueConstraint('user_id', 'follows_user_id'),
2683 2730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2684 2731 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2685 2732 )
2686 2733
2687 2734 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2688 2735 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2689 2736 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2690 2737 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2691 2738 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2692 2739
2693 2740 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2694 2741
2695 2742 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2696 2743 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2697 2744
2698 2745 @classmethod
2699 2746 def get_repo_followers(cls, repo_id):
2700 2747 return cls.query().filter(cls.follows_repo_id == repo_id)
2701 2748
2702 2749
2703 2750 class CacheKey(Base, BaseModel):
2704 2751 __tablename__ = 'cache_invalidation'
2705 2752 __table_args__ = (
2706 2753 UniqueConstraint('cache_key'),
2707 2754 Index('key_idx', 'cache_key'),
2708 2755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2709 2756 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2710 2757 )
2711 2758 CACHE_TYPE_ATOM = 'ATOM'
2712 2759 CACHE_TYPE_RSS = 'RSS'
2713 2760 CACHE_TYPE_README = 'README'
2714 2761
2715 2762 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2716 2763 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2717 2764 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2718 2765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2719 2766
2720 2767 def __init__(self, cache_key, cache_args=''):
2721 2768 self.cache_key = cache_key
2722 2769 self.cache_args = cache_args
2723 2770 self.cache_active = False
2724 2771
2725 2772 def __unicode__(self):
2726 2773 return u"<%s('%s:%s[%s]')>" % (
2727 2774 self.__class__.__name__,
2728 2775 self.cache_id, self.cache_key, self.cache_active)
2729 2776
2730 2777 def _cache_key_partition(self):
2731 2778 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2732 2779 return prefix, repo_name, suffix
2733 2780
2734 2781 def get_prefix(self):
2735 2782 """
2736 2783 Try to extract prefix from existing cache key. The key could consist
2737 2784 of prefix, repo_name, suffix
2738 2785 """
2739 2786 # this returns prefix, repo_name, suffix
2740 2787 return self._cache_key_partition()[0]
2741 2788
2742 2789 def get_suffix(self):
2743 2790 """
2744 2791 get suffix that might have been used in _get_cache_key to
2745 2792 generate self.cache_key. Only used for informational purposes
2746 2793 in repo_edit.html.
2747 2794 """
2748 2795 # prefix, repo_name, suffix
2749 2796 return self._cache_key_partition()[2]
2750 2797
2751 2798 @classmethod
2752 2799 def delete_all_cache(cls):
2753 2800 """
2754 2801 Delete all cache keys from database.
2755 2802 Should only be run when all instances are down and all entries
2756 2803 thus stale.
2757 2804 """
2758 2805 cls.query().delete()
2759 2806 Session().commit()
2760 2807
2761 2808 @classmethod
2762 2809 def get_cache_key(cls, repo_name, cache_type):
2763 2810 """
2764 2811
2765 2812 Generate a cache key for this process of RhodeCode instance.
2766 2813 Prefix most likely will be process id or maybe explicitly set
2767 2814 instance_id from .ini file.
2768 2815 """
2769 2816 import rhodecode
2770 2817 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2771 2818
2772 2819 repo_as_unicode = safe_unicode(repo_name)
2773 2820 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2774 2821 if cache_type else repo_as_unicode
2775 2822
2776 2823 return u'{}{}'.format(prefix, key)
2777 2824
2778 2825 @classmethod
2779 2826 def set_invalidate(cls, repo_name, delete=False):
2780 2827 """
2781 2828 Mark all caches of a repo as invalid in the database.
2782 2829 """
2783 2830
2784 2831 try:
2785 2832 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2786 2833 if delete:
2787 2834 log.debug('cache objects deleted for repo %s',
2788 2835 safe_str(repo_name))
2789 2836 qry.delete()
2790 2837 else:
2791 2838 log.debug('cache objects marked as invalid for repo %s',
2792 2839 safe_str(repo_name))
2793 2840 qry.update({"cache_active": False})
2794 2841
2795 2842 Session().commit()
2796 2843 except Exception:
2797 2844 log.exception(
2798 2845 'Cache key invalidation failed for repository %s',
2799 2846 safe_str(repo_name))
2800 2847 Session().rollback()
2801 2848
2802 2849 @classmethod
2803 2850 def get_active_cache(cls, cache_key):
2804 2851 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2805 2852 if inv_obj:
2806 2853 return inv_obj
2807 2854 return None
2808 2855
2809 2856 @classmethod
2810 2857 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2811 2858 """
2812 2859 @cache_region('long_term')
2813 2860 def _heavy_calculation(cache_key):
2814 2861 return 'result'
2815 2862
2816 2863 cache_context = CacheKey.repo_context_cache(
2817 2864 _heavy_calculation, repo_name, cache_type)
2818 2865
2819 2866 with cache_context as context:
2820 2867 context.invalidate()
2821 2868 computed = context.compute()
2822 2869
2823 2870 assert computed == 'result'
2824 2871 """
2825 2872 from rhodecode.lib import caches
2826 2873 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2827 2874
2828 2875
2829 2876 class ChangesetComment(Base, BaseModel):
2830 2877 __tablename__ = 'changeset_comments'
2831 2878 __table_args__ = (
2832 2879 Index('cc_revision_idx', 'revision'),
2833 2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2834 2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2835 2882 )
2836 2883
2837 2884 COMMENT_OUTDATED = u'comment_outdated'
2838 2885
2839 2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2840 2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2841 2888 revision = Column('revision', String(40), nullable=True)
2842 2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2843 2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2844 2891 line_no = Column('line_no', Unicode(10), nullable=True)
2845 2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2846 2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2847 2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2848 2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2849 2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2850 2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2851 2898 renderer = Column('renderer', Unicode(64), nullable=True)
2852 2899 display_state = Column('display_state', Unicode(128), nullable=True)
2853 2900
2854 2901 author = relationship('User', lazy='joined')
2855 2902 repo = relationship('Repository')
2856 2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2857 2904 pull_request = relationship('PullRequest', lazy='joined')
2858 2905 pull_request_version = relationship('PullRequestVersion')
2859 2906
2860 2907 @classmethod
2861 2908 def get_users(cls, revision=None, pull_request_id=None):
2862 2909 """
2863 2910 Returns user associated with this ChangesetComment. ie those
2864 2911 who actually commented
2865 2912
2866 2913 :param cls:
2867 2914 :param revision:
2868 2915 """
2869 2916 q = Session().query(User)\
2870 2917 .join(ChangesetComment.author)
2871 2918 if revision:
2872 2919 q = q.filter(cls.revision == revision)
2873 2920 elif pull_request_id:
2874 2921 q = q.filter(cls.pull_request_id == pull_request_id)
2875 2922 return q.all()
2876 2923
2877 2924 def render(self, mentions=False):
2878 2925 from rhodecode.lib import helpers as h
2879 2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2880 2927
2881 2928 def __repr__(self):
2882 2929 if self.comment_id:
2883 2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2884 2931 else:
2885 2932 return '<DB:ChangesetComment at %#x>' % id(self)
2886 2933
2887 2934
2888 2935 class ChangesetStatus(Base, BaseModel):
2889 2936 __tablename__ = 'changeset_statuses'
2890 2937 __table_args__ = (
2891 2938 Index('cs_revision_idx', 'revision'),
2892 2939 Index('cs_version_idx', 'version'),
2893 2940 UniqueConstraint('repo_id', 'revision', 'version'),
2894 2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2895 2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2896 2943 )
2897 2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2898 2945 STATUS_APPROVED = 'approved'
2899 2946 STATUS_REJECTED = 'rejected'
2900 2947 STATUS_UNDER_REVIEW = 'under_review'
2901 2948
2902 2949 STATUSES = [
2903 2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2904 2951 (STATUS_APPROVED, _("Approved")),
2905 2952 (STATUS_REJECTED, _("Rejected")),
2906 2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2907 2954 ]
2908 2955
2909 2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2910 2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2911 2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2912 2959 revision = Column('revision', String(40), nullable=False)
2913 2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2914 2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2915 2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2916 2963 version = Column('version', Integer(), nullable=False, default=0)
2917 2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2918 2965
2919 2966 author = relationship('User', lazy='joined')
2920 2967 repo = relationship('Repository')
2921 2968 comment = relationship('ChangesetComment', lazy='joined')
2922 2969 pull_request = relationship('PullRequest', lazy='joined')
2923 2970
2924 2971 def __unicode__(self):
2925 2972 return u"<%s('%s[%s]:%s')>" % (
2926 2973 self.__class__.__name__,
2927 2974 self.status, self.version, self.author
2928 2975 )
2929 2976
2930 2977 @classmethod
2931 2978 def get_status_lbl(cls, value):
2932 2979 return dict(cls.STATUSES).get(value)
2933 2980
2934 2981 @property
2935 2982 def status_lbl(self):
2936 2983 return ChangesetStatus.get_status_lbl(self.status)
2937 2984
2938 2985
2939 2986 class _PullRequestBase(BaseModel):
2940 2987 """
2941 2988 Common attributes of pull request and version entries.
2942 2989 """
2943 2990
2944 2991 # .status values
2945 2992 STATUS_NEW = u'new'
2946 2993 STATUS_OPEN = u'open'
2947 2994 STATUS_CLOSED = u'closed'
2948 2995
2949 2996 title = Column('title', Unicode(255), nullable=True)
2950 2997 description = Column(
2951 2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2952 2999 nullable=True)
2953 3000 # new/open/closed status of pull request (not approve/reject/etc)
2954 3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2955 3002 created_on = Column(
2956 3003 'created_on', DateTime(timezone=False), nullable=False,
2957 3004 default=datetime.datetime.now)
2958 3005 updated_on = Column(
2959 3006 'updated_on', DateTime(timezone=False), nullable=False,
2960 3007 default=datetime.datetime.now)
2961 3008
2962 3009 @declared_attr
2963 3010 def user_id(cls):
2964 3011 return Column(
2965 3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
2966 3013 unique=None)
2967 3014
2968 3015 # 500 revisions max
2969 3016 _revisions = Column(
2970 3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
2971 3018
2972 3019 @declared_attr
2973 3020 def source_repo_id(cls):
2974 3021 # TODO: dan: rename column to source_repo_id
2975 3022 return Column(
2976 3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
2977 3024 nullable=False)
2978 3025
2979 3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
2980 3027
2981 3028 @declared_attr
2982 3029 def target_repo_id(cls):
2983 3030 # TODO: dan: rename column to target_repo_id
2984 3031 return Column(
2985 3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
2986 3033 nullable=False)
2987 3034
2988 3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
2989 3036
2990 3037 # TODO: dan: rename column to last_merge_source_rev
2991 3038 _last_merge_source_rev = Column(
2992 3039 'last_merge_org_rev', String(40), nullable=True)
2993 3040 # TODO: dan: rename column to last_merge_target_rev
2994 3041 _last_merge_target_rev = Column(
2995 3042 'last_merge_other_rev', String(40), nullable=True)
2996 3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
2997 3044 merge_rev = Column('merge_rev', String(40), nullable=True)
2998 3045
2999 3046 @hybrid_property
3000 3047 def revisions(self):
3001 3048 return self._revisions.split(':') if self._revisions else []
3002 3049
3003 3050 @revisions.setter
3004 3051 def revisions(self, val):
3005 3052 self._revisions = ':'.join(val)
3006 3053
3007 3054 @declared_attr
3008 3055 def author(cls):
3009 3056 return relationship('User', lazy='joined')
3010 3057
3011 3058 @declared_attr
3012 3059 def source_repo(cls):
3013 3060 return relationship(
3014 3061 'Repository',
3015 3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3016 3063
3017 3064 @property
3018 3065 def source_ref_parts(self):
3019 3066 refs = self.source_ref.split(':')
3020 3067 return Reference(refs[0], refs[1], refs[2])
3021 3068
3022 3069 @declared_attr
3023 3070 def target_repo(cls):
3024 3071 return relationship(
3025 3072 'Repository',
3026 3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3027 3074
3028 3075 @property
3029 3076 def target_ref_parts(self):
3030 3077 refs = self.target_ref.split(':')
3031 3078 return Reference(refs[0], refs[1], refs[2])
3032 3079
3033 3080
3034 3081 class PullRequest(Base, _PullRequestBase):
3035 3082 __tablename__ = 'pull_requests'
3036 3083 __table_args__ = (
3037 3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3038 3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3039 3086 )
3040 3087
3041 3088 pull_request_id = Column(
3042 3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3043 3090
3044 3091 def __repr__(self):
3045 3092 if self.pull_request_id:
3046 3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3047 3094 else:
3048 3095 return '<DB:PullRequest at %#x>' % id(self)
3049 3096
3050 3097 reviewers = relationship('PullRequestReviewers',
3051 3098 cascade="all, delete, delete-orphan")
3052 3099 statuses = relationship('ChangesetStatus')
3053 3100 comments = relationship('ChangesetComment',
3054 3101 cascade="all, delete, delete-orphan")
3055 3102 versions = relationship('PullRequestVersion',
3056 3103 cascade="all, delete, delete-orphan")
3057 3104
3058 3105 def is_closed(self):
3059 3106 return self.status == self.STATUS_CLOSED
3060 3107
3061 3108 def get_api_data(self):
3062 3109 from rhodecode.model.pull_request import PullRequestModel
3063 3110 pull_request = self
3064 3111 merge_status = PullRequestModel().merge_status(pull_request)
3065 3112 data = {
3066 3113 'pull_request_id': pull_request.pull_request_id,
3067 3114 'url': url('pullrequest_show',
3068 3115 repo_name=pull_request.target_repo.repo_name,
3069 3116 pull_request_id=pull_request.pull_request_id,
3070 3117 qualified=True),
3071 3118 'title': pull_request.title,
3072 3119 'description': pull_request.description,
3073 3120 'status': pull_request.status,
3074 3121 'created_on': pull_request.created_on,
3075 3122 'updated_on': pull_request.updated_on,
3076 3123 'commit_ids': pull_request.revisions,
3077 3124 'review_status': pull_request.calculated_review_status(),
3078 3125 'mergeable': {
3079 3126 'status': merge_status[0],
3080 3127 'message': unicode(merge_status[1]),
3081 3128 },
3082 3129 'source': {
3083 3130 'clone_url': pull_request.source_repo.clone_url(),
3084 3131 'repository': pull_request.source_repo.repo_name,
3085 3132 'reference': {
3086 3133 'name': pull_request.source_ref_parts.name,
3087 3134 'type': pull_request.source_ref_parts.type,
3088 3135 'commit_id': pull_request.source_ref_parts.commit_id,
3089 3136 },
3090 3137 },
3091 3138 'target': {
3092 3139 'clone_url': pull_request.target_repo.clone_url(),
3093 3140 'repository': pull_request.target_repo.repo_name,
3094 3141 'reference': {
3095 3142 'name': pull_request.target_ref_parts.name,
3096 3143 'type': pull_request.target_ref_parts.type,
3097 3144 'commit_id': pull_request.target_ref_parts.commit_id,
3098 3145 },
3099 3146 },
3100 3147 'author': pull_request.author.get_api_data(include_secrets=False,
3101 3148 details='basic'),
3102 3149 'reviewers': [
3103 3150 {
3104 3151 'user': reviewer.get_api_data(include_secrets=False,
3105 3152 details='basic'),
3106 3153 'review_status': st[0][1].status if st else 'not_reviewed',
3107 3154 }
3108 3155 for reviewer, st in pull_request.reviewers_statuses()
3109 3156 ]
3110 3157 }
3111 3158
3112 3159 return data
3113 3160
3114 3161 def __json__(self):
3115 3162 return {
3116 3163 'revisions': self.revisions,
3117 3164 }
3118 3165
3119 3166 def calculated_review_status(self):
3120 3167 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3121 3168 # because it's tricky on how to use ChangesetStatusModel from there
3122 3169 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3123 3170 from rhodecode.model.changeset_status import ChangesetStatusModel
3124 3171 return ChangesetStatusModel().calculated_review_status(self)
3125 3172
3126 3173 def reviewers_statuses(self):
3127 3174 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3128 3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3129 3176 return ChangesetStatusModel().reviewers_statuses(self)
3130 3177
3131 3178
3132 3179 class PullRequestVersion(Base, _PullRequestBase):
3133 3180 __tablename__ = 'pull_request_versions'
3134 3181 __table_args__ = (
3135 3182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3136 3183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3137 3184 )
3138 3185
3139 3186 pull_request_version_id = Column(
3140 3187 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3141 3188 pull_request_id = Column(
3142 3189 'pull_request_id', Integer(),
3143 3190 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3144 3191 pull_request = relationship('PullRequest')
3145 3192
3146 3193 def __repr__(self):
3147 3194 if self.pull_request_version_id:
3148 3195 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3149 3196 else:
3150 3197 return '<DB:PullRequestVersion at %#x>' % id(self)
3151 3198
3152 3199
3153 3200 class PullRequestReviewers(Base, BaseModel):
3154 3201 __tablename__ = 'pull_request_reviewers'
3155 3202 __table_args__ = (
3156 3203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3157 3204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3158 3205 )
3159 3206
3160 3207 def __init__(self, user=None, pull_request=None):
3161 3208 self.user = user
3162 3209 self.pull_request = pull_request
3163 3210
3164 3211 pull_requests_reviewers_id = Column(
3165 3212 'pull_requests_reviewers_id', Integer(), nullable=False,
3166 3213 primary_key=True)
3167 3214 pull_request_id = Column(
3168 3215 "pull_request_id", Integer(),
3169 3216 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3170 3217 user_id = Column(
3171 3218 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3172 3219
3173 3220 user = relationship('User')
3174 3221 pull_request = relationship('PullRequest')
3175 3222
3176 3223
3177 3224 class Notification(Base, BaseModel):
3178 3225 __tablename__ = 'notifications'
3179 3226 __table_args__ = (
3180 3227 Index('notification_type_idx', 'type'),
3181 3228 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3182 3229 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3183 3230 )
3184 3231
3185 3232 TYPE_CHANGESET_COMMENT = u'cs_comment'
3186 3233 TYPE_MESSAGE = u'message'
3187 3234 TYPE_MENTION = u'mention'
3188 3235 TYPE_REGISTRATION = u'registration'
3189 3236 TYPE_PULL_REQUEST = u'pull_request'
3190 3237 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3191 3238
3192 3239 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3193 3240 subject = Column('subject', Unicode(512), nullable=True)
3194 3241 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3195 3242 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3196 3243 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3197 3244 type_ = Column('type', Unicode(255))
3198 3245
3199 3246 created_by_user = relationship('User')
3200 3247 notifications_to_users = relationship('UserNotification', lazy='joined',
3201 3248 cascade="all, delete, delete-orphan")
3202 3249
3203 3250 @property
3204 3251 def recipients(self):
3205 3252 return [x.user for x in UserNotification.query()\
3206 3253 .filter(UserNotification.notification == self)\
3207 3254 .order_by(UserNotification.user_id.asc()).all()]
3208 3255
3209 3256 @classmethod
3210 3257 def create(cls, created_by, subject, body, recipients, type_=None):
3211 3258 if type_ is None:
3212 3259 type_ = Notification.TYPE_MESSAGE
3213 3260
3214 3261 notification = cls()
3215 3262 notification.created_by_user = created_by
3216 3263 notification.subject = subject
3217 3264 notification.body = body
3218 3265 notification.type_ = type_
3219 3266 notification.created_on = datetime.datetime.now()
3220 3267
3221 3268 for u in recipients:
3222 3269 assoc = UserNotification()
3223 3270 assoc.notification = notification
3224 3271
3225 3272 # if created_by is inside recipients mark his notification
3226 3273 # as read
3227 3274 if u.user_id == created_by.user_id:
3228 3275 assoc.read = True
3229 3276
3230 3277 u.notifications.append(assoc)
3231 3278 Session().add(notification)
3232 3279
3233 3280 return notification
3234 3281
3235 3282 @property
3236 3283 def description(self):
3237 3284 from rhodecode.model.notification import NotificationModel
3238 3285 return NotificationModel().make_description(self)
3239 3286
3240 3287
3241 3288 class UserNotification(Base, BaseModel):
3242 3289 __tablename__ = 'user_to_notification'
3243 3290 __table_args__ = (
3244 3291 UniqueConstraint('user_id', 'notification_id'),
3245 3292 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3246 3293 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3247 3294 )
3248 3295 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3249 3296 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3250 3297 read = Column('read', Boolean, default=False)
3251 3298 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3252 3299
3253 3300 user = relationship('User', lazy="joined")
3254 3301 notification = relationship('Notification', lazy="joined",
3255 3302 order_by=lambda: Notification.created_on.desc(),)
3256 3303
3257 3304 def mark_as_read(self):
3258 3305 self.read = True
3259 3306 Session().add(self)
3260 3307
3261 3308
3262 3309 class Gist(Base, BaseModel):
3263 3310 __tablename__ = 'gists'
3264 3311 __table_args__ = (
3265 3312 Index('g_gist_access_id_idx', 'gist_access_id'),
3266 3313 Index('g_created_on_idx', 'created_on'),
3267 3314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3268 3315 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3269 3316 )
3270 3317 GIST_PUBLIC = u'public'
3271 3318 GIST_PRIVATE = u'private'
3272 3319 DEFAULT_FILENAME = u'gistfile1.txt'
3273 3320
3274 3321 ACL_LEVEL_PUBLIC = u'acl_public'
3275 3322 ACL_LEVEL_PRIVATE = u'acl_private'
3276 3323
3277 3324 gist_id = Column('gist_id', Integer(), primary_key=True)
3278 3325 gist_access_id = Column('gist_access_id', Unicode(250))
3279 3326 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3280 3327 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3281 3328 gist_expires = Column('gist_expires', Float(53), nullable=False)
3282 3329 gist_type = Column('gist_type', Unicode(128), nullable=False)
3283 3330 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3284 3331 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3285 3332 acl_level = Column('acl_level', Unicode(128), nullable=True)
3286 3333
3287 3334 owner = relationship('User')
3288 3335
3289 3336 def __repr__(self):
3290 3337 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3291 3338
3292 3339 @classmethod
3293 3340 def get_or_404(cls, id_):
3294 3341 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3295 3342 if not res:
3296 3343 raise HTTPNotFound
3297 3344 return res
3298 3345
3299 3346 @classmethod
3300 3347 def get_by_access_id(cls, gist_access_id):
3301 3348 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3302 3349
3303 3350 def gist_url(self):
3304 3351 import rhodecode
3305 3352 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3306 3353 if alias_url:
3307 3354 return alias_url.replace('{gistid}', self.gist_access_id)
3308 3355
3309 3356 return url('gist', gist_id=self.gist_access_id, qualified=True)
3310 3357
3311 3358 @classmethod
3312 3359 def base_path(cls):
3313 3360 """
3314 3361 Returns base path when all gists are stored
3315 3362
3316 3363 :param cls:
3317 3364 """
3318 3365 from rhodecode.model.gist import GIST_STORE_LOC
3319 3366 q = Session().query(RhodeCodeUi)\
3320 3367 .filter(RhodeCodeUi.ui_key == URL_SEP)
3321 3368 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3322 3369 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3323 3370
3324 3371 def get_api_data(self):
3325 3372 """
3326 3373 Common function for generating gist related data for API
3327 3374 """
3328 3375 gist = self
3329 3376 data = {
3330 3377 'gist_id': gist.gist_id,
3331 3378 'type': gist.gist_type,
3332 3379 'access_id': gist.gist_access_id,
3333 3380 'description': gist.gist_description,
3334 3381 'url': gist.gist_url(),
3335 3382 'expires': gist.gist_expires,
3336 3383 'created_on': gist.created_on,
3337 3384 'modified_at': gist.modified_at,
3338 3385 'content': None,
3339 3386 'acl_level': gist.acl_level,
3340 3387 }
3341 3388 return data
3342 3389
3343 3390 def __json__(self):
3344 3391 data = dict(
3345 3392 )
3346 3393 data.update(self.get_api_data())
3347 3394 return data
3348 3395 # SCM functions
3349 3396
3350 3397 def scm_instance(self, **kwargs):
3351 3398 from rhodecode.lib.vcs import get_repo
3352 3399 base_path = self.base_path()
3353 3400 return get_repo(os.path.join(*map(safe_str,
3354 3401 [base_path, self.gist_access_id])))
3355 3402
3356 3403
3357 3404 class DbMigrateVersion(Base, BaseModel):
3358 3405 __tablename__ = 'db_migrate_version'
3359 3406 __table_args__ = (
3360 3407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3361 3408 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3362 3409 )
3363 3410 repository_id = Column('repository_id', String(250), primary_key=True)
3364 3411 repository_path = Column('repository_path', Text)
3365 3412 version = Column('version', Integer)
3366 3413
3367 3414
3368 3415 class ExternalIdentity(Base, BaseModel):
3369 3416 __tablename__ = 'external_identities'
3370 3417 __table_args__ = (
3371 3418 Index('local_user_id_idx', 'local_user_id'),
3372 3419 Index('external_id_idx', 'external_id'),
3373 3420 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3374 3421 'mysql_charset': 'utf8'})
3375 3422
3376 3423 external_id = Column('external_id', Unicode(255), default=u'',
3377 3424 primary_key=True)
3378 3425 external_username = Column('external_username', Unicode(1024), default=u'')
3379 3426 local_user_id = Column('local_user_id', Integer(),
3380 3427 ForeignKey('users.user_id'), primary_key=True)
3381 3428 provider_name = Column('provider_name', Unicode(255), default=u'',
3382 3429 primary_key=True)
3383 3430 access_token = Column('access_token', String(1024), default=u'')
3384 3431 alt_token = Column('alt_token', String(1024), default=u'')
3385 3432 token_secret = Column('token_secret', String(1024), default=u'')
3386 3433
3387 3434 @classmethod
3388 3435 def by_external_id_and_provider(cls, external_id, provider_name,
3389 3436 local_user_id=None):
3390 3437 """
3391 3438 Returns ExternalIdentity instance based on search params
3392 3439
3393 3440 :param external_id:
3394 3441 :param provider_name:
3395 3442 :return: ExternalIdentity
3396 3443 """
3397 3444 query = cls.query()
3398 3445 query = query.filter(cls.external_id == external_id)
3399 3446 query = query.filter(cls.provider_name == provider_name)
3400 3447 if local_user_id:
3401 3448 query = query.filter(cls.local_user_id == local_user_id)
3402 3449 return query.first()
3403 3450
3404 3451 @classmethod
3405 3452 def user_by_external_id_and_provider(cls, external_id, provider_name):
3406 3453 """
3407 3454 Returns User instance based on search params
3408 3455
3409 3456 :param external_id:
3410 3457 :param provider_name:
3411 3458 :return: User
3412 3459 """
3413 3460 query = User.query()
3414 3461 query = query.filter(cls.external_id == external_id)
3415 3462 query = query.filter(cls.provider_name == provider_name)
3416 3463 query = query.filter(User.user_id == cls.local_user_id)
3417 3464 return query.first()
3418 3465
3419 3466 @classmethod
3420 3467 def by_local_user_id(cls, local_user_id):
3421 3468 """
3422 3469 Returns all tokens for user
3423 3470
3424 3471 :param local_user_id:
3425 3472 :return: ExternalIdentity
3426 3473 """
3427 3474 query = cls.query()
3428 3475 query = query.filter(cls.local_user_id == local_user_id)
3429 3476 return query
@@ -1,547 +1,561 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 this is forms validation classes
23 23 http://formencode.org/module-formencode.validators.html
24 24 for list off all availible validators
25 25
26 26 we can create our own validators
27 27
28 28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 29 pre_validators [] These validators will be applied before the schema
30 30 chained_validators [] These validators will be applied after the schema
31 31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35 35
36 36
37 37 <name> = formencode.validators.<name of validator>
38 38 <name> must equal form name
39 39 list=[1,2,3,4,5]
40 40 for SELECT use formencode.All(OneOf(list), Int())
41 41
42 42 """
43 43
44 44 import logging
45 45
46 46 import formencode
47 47 from formencode import All, Pipe
48 48
49 49 from pylons.i18n.translation import _
50 50
51 51 from rhodecode import BACKENDS
52 52 from rhodecode.model import validators as v
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 def LoginForm():
58 58 class _LoginForm(formencode.Schema):
59 59 allow_extra_fields = True
60 60 filter_extra_fields = True
61 61 username = v.UnicodeString(
62 62 strip=True,
63 63 min=1,
64 64 not_empty=True,
65 65 messages={
66 66 'empty': _(u'Please enter a login'),
67 67 'tooShort': _(u'Enter a value %(min)i characters long or more')
68 68 }
69 69 )
70 70
71 71 password = v.UnicodeString(
72 72 strip=False,
73 73 min=3,
74 74 not_empty=True,
75 75 messages={
76 76 'empty': _(u'Please enter a password'),
77 77 'tooShort': _(u'Enter %(min)i characters or more')}
78 78 )
79 79
80 80 remember = v.StringBoolean(if_missing=False)
81 81
82 82 chained_validators = [v.ValidAuth()]
83 83 return _LoginForm
84 84
85 85
86 86 def PasswordChangeForm(username):
87 87 class _PasswordChangeForm(formencode.Schema):
88 88 allow_extra_fields = True
89 89 filter_extra_fields = True
90 90
91 91 current_password = v.ValidOldPassword(username)(not_empty=True)
92 92 new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
93 93 new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
94 94
95 95 chained_validators = [v.ValidPasswordsMatch('new_password',
96 96 'new_password_confirmation')]
97 97 return _PasswordChangeForm
98 98
99 99
100 100 def UserForm(edit=False, available_languages=[], old_data={}):
101 101 class _UserForm(formencode.Schema):
102 102 allow_extra_fields = True
103 103 filter_extra_fields = True
104 104 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
105 105 v.ValidUsername(edit, old_data))
106 106 if edit:
107 107 new_password = All(
108 108 v.ValidPassword(),
109 109 v.UnicodeString(strip=False, min=6, not_empty=False)
110 110 )
111 111 password_confirmation = All(
112 112 v.ValidPassword(),
113 113 v.UnicodeString(strip=False, min=6, not_empty=False),
114 114 )
115 115 admin = v.StringBoolean(if_missing=False)
116 116 else:
117 117 password = All(
118 118 v.ValidPassword(),
119 119 v.UnicodeString(strip=False, min=6, not_empty=True)
120 120 )
121 121 password_confirmation = All(
122 122 v.ValidPassword(),
123 123 v.UnicodeString(strip=False, min=6, not_empty=False)
124 124 )
125 125
126 126 password_change = v.StringBoolean(if_missing=False)
127 127 create_repo_group = v.StringBoolean(if_missing=False)
128 128
129 129 active = v.StringBoolean(if_missing=False)
130 130 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
131 131 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
132 132 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
133 133 extern_name = v.UnicodeString(strip=True)
134 134 extern_type = v.UnicodeString(strip=True)
135 135 language = v.OneOf(available_languages, hideList=False,
136 136 testValueList=True, if_missing=None)
137 137 chained_validators = [v.ValidPasswordsMatch()]
138 138 return _UserForm
139 139
140 140
141 def UserGroupForm(edit=False, old_data={}, available_members=[]):
141 def UserGroupForm(edit=False, old_data=None, available_members=None,
142 allow_disabled=False):
143 old_data = old_data or {}
144 available_members = available_members or []
145
142 146 class _UserGroupForm(formencode.Schema):
143 147 allow_extra_fields = True
144 148 filter_extra_fields = True
145 149
146 150 users_group_name = All(
147 151 v.UnicodeString(strip=True, min=1, not_empty=True),
148 152 v.ValidUserGroup(edit, old_data)
149 153 )
150 154 user_group_description = v.UnicodeString(strip=True, min=1,
151 155 not_empty=False)
152 156
153 157 users_group_active = v.StringBoolean(if_missing=False)
154 158
155 159 if edit:
156 160 users_group_members = v.OneOf(
157 161 available_members, hideList=False, testValueList=True,
158 162 if_missing=None, not_empty=False
159 163 )
160 #this is user group owner
161 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
162
164 # this is user group owner
165 user = All(
166 v.UnicodeString(not_empty=True),
167 v.ValidRepoUser(allow_disabled))
163 168 return _UserGroupForm
164 169
165 170
166 def RepoGroupForm(edit=False, old_data={}, available_groups=[],
167 can_create_in_root=False):
171 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
172 can_create_in_root=False, allow_disabled=False):
173 old_data = old_data or {}
174 available_groups = available_groups or []
175
168 176 class _RepoGroupForm(formencode.Schema):
169 177 allow_extra_fields = True
170 178 filter_extra_fields = False
171 179
172 180 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
173 181 v.SlugifyName(),)
174 182 group_description = v.UnicodeString(strip=True, min=1,
175 183 not_empty=False)
176 184 group_copy_permissions = v.StringBoolean(if_missing=False)
177 185
178 186 group_parent_id = v.OneOf(available_groups, hideList=False,
179 187 testValueList=True, not_empty=True)
180 188 enable_locking = v.StringBoolean(if_missing=False)
181 chained_validators = [v.ValidRepoGroup(edit, old_data, can_create_in_root)]
189 chained_validators = [
190 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
182 191
183 192 if edit:
184 #this is repo group owner
185 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
193 # this is repo group owner
194 user = All(
195 v.UnicodeString(not_empty=True),
196 v.ValidRepoUser(allow_disabled))
186 197
187 198 return _RepoGroupForm
188 199
189 200
190 201 def RegisterForm(edit=False, old_data={}):
191 202 class _RegisterForm(formencode.Schema):
192 203 allow_extra_fields = True
193 204 filter_extra_fields = True
194 205 username = All(
195 206 v.ValidUsername(edit, old_data),
196 207 v.UnicodeString(strip=True, min=1, not_empty=True)
197 208 )
198 209 password = All(
199 210 v.ValidPassword(),
200 211 v.UnicodeString(strip=False, min=6, not_empty=True)
201 212 )
202 213 password_confirmation = All(
203 214 v.ValidPassword(),
204 215 v.UnicodeString(strip=False, min=6, not_empty=True)
205 216 )
206 217 active = v.StringBoolean(if_missing=False)
207 218 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
208 219 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
209 220 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
210 221
211 222 chained_validators = [v.ValidPasswordsMatch()]
212 223
213 224 return _RegisterForm
214 225
215 226
216 227 def PasswordResetForm():
217 228 class _PasswordResetForm(formencode.Schema):
218 229 allow_extra_fields = True
219 230 filter_extra_fields = True
220 231 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
221 232 return _PasswordResetForm
222 233
223 234
224 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None):
235 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
236 allow_disabled=False):
225 237 old_data = old_data or {}
226 238 repo_groups = repo_groups or []
227 239 landing_revs = landing_revs or []
228 240 supported_backends = BACKENDS.keys()
229 241
230 242 class _RepoForm(formencode.Schema):
231 243 allow_extra_fields = True
232 244 filter_extra_fields = False
233 245 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
234 246 v.SlugifyName())
235 247 repo_group = All(v.CanWriteGroup(old_data),
236 248 v.OneOf(repo_groups, hideList=True))
237 249 repo_type = v.OneOf(supported_backends, required=False,
238 250 if_missing=old_data.get('repo_type'))
239 251 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
240 252 repo_private = v.StringBoolean(if_missing=False)
241 253 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
242 254 repo_copy_permissions = v.StringBoolean(if_missing=False)
243 255 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
244 256
245 257 repo_enable_statistics = v.StringBoolean(if_missing=False)
246 258 repo_enable_downloads = v.StringBoolean(if_missing=False)
247 259 repo_enable_locking = v.StringBoolean(if_missing=False)
248 260
249 261 if edit:
250 262 # this is repo owner
251 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
263 user = All(
264 v.UnicodeString(not_empty=True),
265 v.ValidRepoUser(allow_disabled))
252 266 clone_uri_change = v.UnicodeString(
253 267 not_empty=False, if_missing=v.Missing)
254 268
255 269 chained_validators = [v.ValidCloneUri(),
256 270 v.ValidRepoName(edit, old_data)]
257 271 return _RepoForm
258 272
259 273
260 274 def RepoPermsForm():
261 275 class _RepoPermsForm(formencode.Schema):
262 276 allow_extra_fields = True
263 277 filter_extra_fields = False
264 278 chained_validators = [v.ValidPerms(type_='repo')]
265 279 return _RepoPermsForm
266 280
267 281
268 282 def RepoGroupPermsForm(valid_recursive_choices):
269 283 class _RepoGroupPermsForm(formencode.Schema):
270 284 allow_extra_fields = True
271 285 filter_extra_fields = False
272 286 recursive = v.OneOf(valid_recursive_choices)
273 287 chained_validators = [v.ValidPerms(type_='repo_group')]
274 288 return _RepoGroupPermsForm
275 289
276 290
277 291 def UserGroupPermsForm():
278 292 class _UserPermsForm(formencode.Schema):
279 293 allow_extra_fields = True
280 294 filter_extra_fields = False
281 295 chained_validators = [v.ValidPerms(type_='user_group')]
282 296 return _UserPermsForm
283 297
284 298
285 299 def RepoFieldForm():
286 300 class _RepoFieldForm(formencode.Schema):
287 301 filter_extra_fields = True
288 302 allow_extra_fields = True
289 303
290 304 new_field_key = All(v.FieldKey(),
291 305 v.UnicodeString(strip=True, min=3, not_empty=True))
292 306 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
293 307 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
294 308 if_missing='str')
295 309 new_field_label = v.UnicodeString(not_empty=False)
296 310 new_field_desc = v.UnicodeString(not_empty=False)
297 311
298 312 return _RepoFieldForm
299 313
300 314
301 315 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
302 316 repo_groups=[], landing_revs=[]):
303 317 class _RepoForkForm(formencode.Schema):
304 318 allow_extra_fields = True
305 319 filter_extra_fields = False
306 320 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
307 321 v.SlugifyName())
308 322 repo_group = All(v.CanWriteGroup(),
309 323 v.OneOf(repo_groups, hideList=True))
310 324 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
311 325 description = v.UnicodeString(strip=True, min=1, not_empty=True)
312 326 private = v.StringBoolean(if_missing=False)
313 327 copy_permissions = v.StringBoolean(if_missing=False)
314 328 fork_parent_id = v.UnicodeString()
315 329 chained_validators = [v.ValidForkName(edit, old_data)]
316 330 landing_rev = v.OneOf(landing_revs, hideList=True)
317 331
318 332 return _RepoForkForm
319 333
320 334
321 335 def ApplicationSettingsForm():
322 336 class _ApplicationSettingsForm(formencode.Schema):
323 337 allow_extra_fields = True
324 338 filter_extra_fields = False
325 339 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
326 340 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
327 341 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
328 342 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
329 343 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
330 344 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
331 345
332 346 return _ApplicationSettingsForm
333 347
334 348
335 349 def ApplicationVisualisationForm():
336 350 class _ApplicationVisualisationForm(formencode.Schema):
337 351 allow_extra_fields = True
338 352 filter_extra_fields = False
339 353 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
340 354 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
341 355 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
342 356
343 357 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
344 358 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
345 359 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
346 360 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
347 361 rhodecode_show_version = v.StringBoolean(if_missing=False)
348 362 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
349 363 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
350 364 rhodecode_gravatar_url = v.UnicodeString(min=3)
351 365 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
352 366 rhodecode_support_url = v.UnicodeString()
353 367 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
354 368 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
355 369
356 370 return _ApplicationVisualisationForm
357 371
358 372
359 373 class _BaseVcsSettingsForm(formencode.Schema):
360 374 allow_extra_fields = True
361 375 filter_extra_fields = False
362 376 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
363 377 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
364 378 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
365 379
366 380 extensions_largefiles = v.StringBoolean(if_missing=False)
367 381 phases_publish = v.StringBoolean(if_missing=False)
368 382
369 383 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
370 384 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
371 385
372 386
373 387 def ApplicationUiSettingsForm():
374 388 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
375 389 web_push_ssl = v.StringBoolean(if_missing=False)
376 390 paths_root_path = All(
377 391 v.ValidPath(),
378 392 v.UnicodeString(strip=True, min=1, not_empty=True)
379 393 )
380 394 extensions_hgsubversion = v.StringBoolean(if_missing=False)
381 395 extensions_hggit = v.StringBoolean(if_missing=False)
382 396 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
383 397 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
384 398
385 399 return _ApplicationUiSettingsForm
386 400
387 401
388 402 def RepoVcsSettingsForm(repo_name):
389 403 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
390 404 inherit_global_settings = v.StringBoolean(if_missing=False)
391 405 new_svn_branch = v.ValidSvnPattern(
392 406 section='vcs_svn_branch', repo_name=repo_name)
393 407 new_svn_tag = v.ValidSvnPattern(
394 408 section='vcs_svn_tag', repo_name=repo_name)
395 409
396 410 return _RepoVcsSettingsForm
397 411
398 412
399 413 def LabsSettingsForm():
400 414 class _LabSettingsForm(formencode.Schema):
401 415 allow_extra_fields = True
402 416 filter_extra_fields = False
403 417
404 418 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
405 419 rhodecode_proxy_subversion_http_requests = v.StringBoolean(
406 420 if_missing=False)
407 421 rhodecode_subversion_http_server_url = v.UnicodeString(
408 422 strip=True, if_missing=None)
409 423
410 424 return _LabSettingsForm
411 425
412 426
413 427 def ApplicationPermissionsForm(register_choices, extern_activate_choices):
414 428 class _DefaultPermissionsForm(formencode.Schema):
415 429 allow_extra_fields = True
416 430 filter_extra_fields = True
417 431
418 432 anonymous = v.StringBoolean(if_missing=False)
419 433 default_register = v.OneOf(register_choices)
420 434 default_register_message = v.UnicodeString()
421 435 default_extern_activate = v.OneOf(extern_activate_choices)
422 436
423 437 return _DefaultPermissionsForm
424 438
425 439
426 440 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
427 441 user_group_perms_choices):
428 442 class _ObjectPermissionsForm(formencode.Schema):
429 443 allow_extra_fields = True
430 444 filter_extra_fields = True
431 445 overwrite_default_repo = v.StringBoolean(if_missing=False)
432 446 overwrite_default_group = v.StringBoolean(if_missing=False)
433 447 overwrite_default_user_group = v.StringBoolean(if_missing=False)
434 448 default_repo_perm = v.OneOf(repo_perms_choices)
435 449 default_group_perm = v.OneOf(group_perms_choices)
436 450 default_user_group_perm = v.OneOf(user_group_perms_choices)
437 451
438 452 return _ObjectPermissionsForm
439 453
440 454
441 455 def UserPermissionsForm(create_choices, create_on_write_choices,
442 456 repo_group_create_choices, user_group_create_choices,
443 457 fork_choices, inherit_default_permissions_choices):
444 458 class _DefaultPermissionsForm(formencode.Schema):
445 459 allow_extra_fields = True
446 460 filter_extra_fields = True
447 461
448 462 anonymous = v.StringBoolean(if_missing=False)
449 463
450 464 default_repo_create = v.OneOf(create_choices)
451 465 default_repo_create_on_write = v.OneOf(create_on_write_choices)
452 466 default_user_group_create = v.OneOf(user_group_create_choices)
453 467 default_repo_group_create = v.OneOf(repo_group_create_choices)
454 468 default_fork_create = v.OneOf(fork_choices)
455 469 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
456 470
457 471 return _DefaultPermissionsForm
458 472
459 473
460 474 def UserIndividualPermissionsForm():
461 475 class _DefaultPermissionsForm(formencode.Schema):
462 476 allow_extra_fields = True
463 477 filter_extra_fields = True
464 478
465 479 inherit_default_permissions = v.StringBoolean(if_missing=False)
466 480
467 481 return _DefaultPermissionsForm
468 482
469 483
470 484 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
471 485 class _DefaultsForm(formencode.Schema):
472 486 allow_extra_fields = True
473 487 filter_extra_fields = True
474 488 default_repo_type = v.OneOf(supported_backends)
475 489 default_repo_private = v.StringBoolean(if_missing=False)
476 490 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
477 491 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
478 492 default_repo_enable_locking = v.StringBoolean(if_missing=False)
479 493
480 494 return _DefaultsForm
481 495
482 496
483 497 def AuthSettingsForm():
484 498 class _AuthSettingsForm(formencode.Schema):
485 499 allow_extra_fields = True
486 500 filter_extra_fields = True
487 501 auth_plugins = All(v.ValidAuthPlugins(),
488 502 v.UniqueListFromString()(not_empty=True))
489 503
490 504 return _AuthSettingsForm
491 505
492 506
493 507 def UserExtraEmailForm():
494 508 class _UserExtraEmailForm(formencode.Schema):
495 509 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
496 510 return _UserExtraEmailForm
497 511
498 512
499 513 def UserExtraIpForm():
500 514 class _UserExtraIpForm(formencode.Schema):
501 515 ip = v.ValidIp()(not_empty=True)
502 516 return _UserExtraIpForm
503 517
504 518
505 519 def PullRequestForm(repo_id):
506 520 class _PullRequestForm(formencode.Schema):
507 521 allow_extra_fields = True
508 522 filter_extra_fields = True
509 523
510 524 user = v.UnicodeString(strip=True, required=True)
511 525 source_repo = v.UnicodeString(strip=True, required=True)
512 526 source_ref = v.UnicodeString(strip=True, required=True)
513 527 target_repo = v.UnicodeString(strip=True, required=True)
514 528 target_ref = v.UnicodeString(strip=True, required=True)
515 529 revisions = All(#v.NotReviewedRevisions(repo_id)(),
516 530 v.UniqueList()(not_empty=True))
517 531 review_members = v.UniqueList(convert=int)(not_empty=True)
518 532
519 533 pullrequest_title = v.UnicodeString(strip=True, required=True)
520 534 pullrequest_desc = v.UnicodeString(strip=True, required=False)
521 535
522 536 return _PullRequestForm
523 537
524 538
525 539 def GistForm(lifetime_options, acl_level_options):
526 540 class _GistForm(formencode.Schema):
527 541
528 542 gistid = All(v.UniqGistId(), v.UnicodeString(strip=True, min=3, not_empty=False, if_missing=None))
529 543 filename = All(v.BasePath()(),
530 544 v.UnicodeString(strip=True, required=False))
531 545 description = v.UnicodeString(required=False, if_missing=u'')
532 546 lifetime = v.OneOf(lifetime_options)
533 547 mimetype = v.UnicodeString(required=False, if_missing=None)
534 548 content = v.UnicodeString(required=True, not_empty=True)
535 549 public = v.UnicodeString(required=False, if_missing=u'')
536 550 private = v.UnicodeString(required=False, if_missing=u'')
537 551 acl_level = v.OneOf(acl_level_options)
538 552
539 553 return _GistForm
540 554
541 555
542 556 def IssueTrackerPatternsForm():
543 557 class _IssueTrackerPatternsForm(formencode.Schema):
544 558 allow_extra_fields = True
545 559 filter_extra_fields = False
546 560 chained_validators = [v.ValidPattern()]
547 561 return _IssueTrackerPatternsForm
@@ -1,918 +1,924 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Repository model for rhodecode
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 import shutil
29 29 import time
30 30 import traceback
31 31 from datetime import datetime
32 32
33 33 from sqlalchemy.sql import func
34 34 from sqlalchemy.sql.expression import true, or_
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 39 from rhodecode.lib.caching_query import FromCache
40 40 from rhodecode.lib.exceptions import AttachedForksError
41 41 from rhodecode.lib.hooks_base import log_delete_repository
42 42 from rhodecode.lib.utils import make_db_config
43 43 from rhodecode.lib.utils2 import (
44 44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
45 45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
46 46 from rhodecode.lib.vcs.backends import get_backend
47 47 from rhodecode.model import BaseModel
48 48 from rhodecode.model.db import (
49 49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
50 50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
51 51 RepoGroup, RepositoryField)
52 52 from rhodecode.model.scm import UserGroupList
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class RepoModel(BaseModel):
60 60
61 61 cls = Repository
62 62
63 63 def _get_user_group(self, users_group):
64 64 return self._get_instance(UserGroup, users_group,
65 65 callback=UserGroup.get_by_group_name)
66 66
67 67 def _get_repo_group(self, repo_group):
68 68 return self._get_instance(RepoGroup, repo_group,
69 69 callback=RepoGroup.get_by_group_name)
70 70
71 71 def _create_default_perms(self, repository, private):
72 72 # create default permission
73 73 default = 'repository.read'
74 74 def_user = User.get_default_user()
75 75 for p in def_user.user_perms:
76 76 if p.permission.permission_name.startswith('repository.'):
77 77 default = p.permission.permission_name
78 78 break
79 79
80 80 default_perm = 'repository.none' if private else default
81 81
82 82 repo_to_perm = UserRepoToPerm()
83 83 repo_to_perm.permission = Permission.get_by_key(default_perm)
84 84
85 85 repo_to_perm.repository = repository
86 86 repo_to_perm.user_id = def_user.user_id
87 87
88 88 return repo_to_perm
89 89
90 90 @LazyProperty
91 91 def repos_path(self):
92 92 """
93 93 Gets the repositories root path from database
94 94 """
95 95 settings_model = VcsSettingsModel(sa=self.sa)
96 96 return settings_model.get_repos_location()
97 97
98 98 def get(self, repo_id, cache=False):
99 99 repo = self.sa.query(Repository) \
100 100 .filter(Repository.repo_id == repo_id)
101 101
102 102 if cache:
103 103 repo = repo.options(FromCache("sql_cache_short",
104 104 "get_repo_%s" % repo_id))
105 105 return repo.scalar()
106 106
107 107 def get_repo(self, repository):
108 108 return self._get_repo(repository)
109 109
110 110 def get_by_repo_name(self, repo_name, cache=False):
111 111 repo = self.sa.query(Repository) \
112 112 .filter(Repository.repo_name == repo_name)
113 113
114 114 if cache:
115 115 repo = repo.options(FromCache("sql_cache_short",
116 116 "get_repo_%s" % repo_name))
117 117 return repo.scalar()
118 118
119 119 def _extract_id_from_repo_name(self, repo_name):
120 120 if repo_name.startswith('/'):
121 121 repo_name = repo_name.lstrip('/')
122 122 by_id_match = re.match(r'^_(\d{1,})', repo_name)
123 123 if by_id_match:
124 124 return by_id_match.groups()[0]
125 125
126 126 def get_repo_by_id(self, repo_name):
127 127 """
128 128 Extracts repo_name by id from special urls.
129 129 Example url is _11/repo_name
130 130
131 131 :param repo_name:
132 132 :return: repo object if matched else None
133 133 """
134 134 try:
135 135 _repo_id = self._extract_id_from_repo_name(repo_name)
136 136 if _repo_id:
137 137 return self.get(_repo_id)
138 138 except Exception:
139 139 log.exception('Failed to extract repo_name from URL')
140 140
141 141 return None
142 142
143 def get_users(self, name_contains=None, limit=20):
143 def get_users(self, name_contains=None, limit=20, only_active=True):
144 144 # TODO: mikhail: move this method to the UserModel.
145 145 query = self.sa.query(User)
146 query = query.filter(User.active == true())
146 if only_active:
147 query = query.filter(User.active == true())
148
147 149 if name_contains:
148 150 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
149 151 query = query.filter(
150 152 or_(
151 153 User.name.ilike(ilike_expression),
152 154 User.lastname.ilike(ilike_expression),
153 155 User.username.ilike(ilike_expression)
154 156 )
155 157 )
156 158 query = query.limit(limit)
157 159 users = query.all()
158 160
159 161 _users = [
160 162 {
161 163 'id': user.user_id,
162 164 'first_name': user.name,
163 165 'last_name': user.lastname,
164 166 'username': user.username,
165 167 'icon_link': h.gravatar_url(user.email, 14),
166 168 'value_display': h.person(user.email),
167 169 'value': user.username,
168 'value_type': 'user'
170 'value_type': 'user',
171 'active': user.active,
169 172 }
170 173 for user in users
171 174 ]
172 175 return _users
173 176
174 def get_user_groups(self, name_contains=None, limit=20):
177 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
175 178 # TODO: mikhail: move this method to the UserGroupModel.
176 179 query = self.sa.query(UserGroup)
177 query = query.filter(UserGroup.users_group_active == true())
180 if only_active:
181 query = query.filter(UserGroup.users_group_active == true())
182
178 183 if name_contains:
179 184 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
180 185 query = query.filter(
181 186 UserGroup.users_group_name.ilike(ilike_expression))\
182 187 .order_by(func.length(UserGroup.users_group_name))\
183 188 .order_by(UserGroup.users_group_name)
184 189
185 190 query = query.limit(limit)
186 191 user_groups = query.all()
187 192 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
188 193 user_groups = UserGroupList(user_groups, perm_set=perm_set)
189 194
190 195 _groups = [
191 196 {
192 197 'id': group.users_group_id,
193 198 # TODO: marcink figure out a way to generate the url for the
194 199 # icon
195 200 'icon_link': '',
196 201 'value_display': 'Group: %s (%d members)' % (
197 202 group.users_group_name, len(group.members),),
198 203 'value': group.users_group_name,
199 'value_type': 'user_group'
204 'value_type': 'user_group',
205 'active': group.users_group_active,
200 206 }
201 207 for group in user_groups
202 208 ]
203 209 return _groups
204 210
205 211 @classmethod
206 212 def update_repoinfo(cls, repositories=None):
207 213 if not repositories:
208 214 repositories = Repository.getAll()
209 215 for repo in repositories:
210 216 repo.update_commit_cache()
211 217
212 218 def get_repos_as_dict(self, repo_list=None, admin=False,
213 219 super_user_actions=False):
214 220
215 221 from rhodecode.lib.utils import PartialRenderer
216 222 _render = PartialRenderer('data_table/_dt_elements.html')
217 223 c = _render.c
218 224
219 225 def quick_menu(repo_name):
220 226 return _render('quick_menu', repo_name)
221 227
222 228 def repo_lnk(name, rtype, rstate, private, fork_of):
223 229 return _render('repo_name', name, rtype, rstate, private, fork_of,
224 230 short_name=not admin, admin=False)
225 231
226 232 def last_change(last_change):
227 233 return _render("last_change", last_change)
228 234
229 235 def rss_lnk(repo_name):
230 236 return _render("rss", repo_name)
231 237
232 238 def atom_lnk(repo_name):
233 239 return _render("atom", repo_name)
234 240
235 241 def last_rev(repo_name, cs_cache):
236 242 return _render('revision', repo_name, cs_cache.get('revision'),
237 243 cs_cache.get('raw_id'), cs_cache.get('author'),
238 244 cs_cache.get('message'))
239 245
240 246 def desc(desc):
241 247 if c.visual.stylify_metatags:
242 248 return h.urlify_text(h.escaped_stylize(h.truncate(desc, 60)))
243 249 else:
244 250 return h.urlify_text(h.html_escape(h.truncate(desc, 60)))
245 251
246 252 def state(repo_state):
247 253 return _render("repo_state", repo_state)
248 254
249 255 def repo_actions(repo_name):
250 256 return _render('repo_actions', repo_name, super_user_actions)
251 257
252 258 def user_profile(username):
253 259 return _render('user_profile', username)
254 260
255 261 repos_data = []
256 262 for repo in repo_list:
257 263 cs_cache = repo.changeset_cache
258 264 row = {
259 265 "menu": quick_menu(repo.repo_name),
260 266
261 267 "name": repo_lnk(repo.repo_name, repo.repo_type,
262 268 repo.repo_state, repo.private, repo.fork),
263 269 "name_raw": repo.repo_name.lower(),
264 270
265 271 "last_change": last_change(repo.last_db_change),
266 272 "last_change_raw": datetime_to_time(repo.last_db_change),
267 273
268 274 "last_changeset": last_rev(repo.repo_name, cs_cache),
269 275 "last_changeset_raw": cs_cache.get('revision'),
270 276
271 277 "desc": desc(repo.description),
272 278 "owner": user_profile(repo.user.username),
273 279
274 280 "state": state(repo.repo_state),
275 281 "rss": rss_lnk(repo.repo_name),
276 282
277 283 "atom": atom_lnk(repo.repo_name),
278 284 }
279 285 if admin:
280 286 row.update({
281 287 "action": repo_actions(repo.repo_name),
282 288 })
283 289 repos_data.append(row)
284 290
285 291 return repos_data
286 292
287 293 def _get_defaults(self, repo_name):
288 294 """
289 295 Gets information about repository, and returns a dict for
290 296 usage in forms
291 297
292 298 :param repo_name:
293 299 """
294 300
295 301 repo_info = Repository.get_by_repo_name(repo_name)
296 302
297 303 if repo_info is None:
298 304 return None
299 305
300 306 defaults = repo_info.get_dict()
301 307 defaults['repo_name'] = repo_info.just_name
302 308
303 309 groups = repo_info.groups_with_parents
304 310 parent_group = groups[-1] if groups else None
305 311
306 312 # we use -1 as this is how in HTML, we mark an empty group
307 313 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
308 314
309 315 keys_to_process = (
310 316 {'k': 'repo_type', 'strip': False},
311 317 {'k': 'repo_enable_downloads', 'strip': True},
312 318 {'k': 'repo_description', 'strip': True},
313 319 {'k': 'repo_enable_locking', 'strip': True},
314 320 {'k': 'repo_landing_rev', 'strip': True},
315 321 {'k': 'clone_uri', 'strip': False},
316 322 {'k': 'repo_private', 'strip': True},
317 323 {'k': 'repo_enable_statistics', 'strip': True}
318 324 )
319 325
320 326 for item in keys_to_process:
321 327 attr = item['k']
322 328 if item['strip']:
323 329 attr = remove_prefix(item['k'], 'repo_')
324 330
325 331 val = defaults[attr]
326 332 if item['k'] == 'repo_landing_rev':
327 333 val = ':'.join(defaults[attr])
328 334 defaults[item['k']] = val
329 335 if item['k'] == 'clone_uri':
330 336 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
331 337
332 338 # fill owner
333 339 if repo_info.user:
334 340 defaults.update({'user': repo_info.user.username})
335 341 else:
336 replacement_user = User.get_first_admin().username
342 replacement_user = User.get_first_super_admin().username
337 343 defaults.update({'user': replacement_user})
338 344
339 345 # fill repository users
340 346 for p in repo_info.repo_to_perm:
341 347 defaults.update({'u_perm_%s' % p.user.user_id:
342 348 p.permission.permission_name})
343 349
344 350 # fill repository groups
345 351 for p in repo_info.users_group_to_perm:
346 352 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
347 353 p.permission.permission_name})
348 354
349 355 return defaults
350 356
351 357 def update(self, repo, **kwargs):
352 358 try:
353 359 cur_repo = self._get_repo(repo)
354 360 source_repo_name = cur_repo.repo_name
355 361 if 'user' in kwargs:
356 362 cur_repo.user = User.get_by_username(kwargs['user'])
357 363
358 364 if 'repo_group' in kwargs:
359 365 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
360 366 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
361 367
362 368 update_keys = [
363 369 (1, 'repo_enable_downloads'),
364 370 (1, 'repo_description'),
365 371 (1, 'repo_enable_locking'),
366 372 (1, 'repo_landing_rev'),
367 373 (1, 'repo_private'),
368 374 (1, 'repo_enable_statistics'),
369 375 (0, 'clone_uri'),
370 376 (0, 'fork_id')
371 377 ]
372 378 for strip, k in update_keys:
373 379 if k in kwargs:
374 380 val = kwargs[k]
375 381 if strip:
376 382 k = remove_prefix(k, 'repo_')
377 383 if k == 'clone_uri':
378 384 from rhodecode.model.validators import Missing
379 385 _change = kwargs.get('clone_uri_change')
380 386 if _change in [Missing, 'OLD']:
381 387 # we don't change the value, so use original one
382 388 val = cur_repo.clone_uri
383 389
384 390 setattr(cur_repo, k, val)
385 391
386 392 new_name = cur_repo.get_new_name(kwargs['repo_name'])
387 393 cur_repo.repo_name = new_name
388 394
389 395 # if private flag is set, reset default permission to NONE
390 396 if kwargs.get('repo_private'):
391 397 EMPTY_PERM = 'repository.none'
392 398 RepoModel().grant_user_permission(
393 399 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
394 400 )
395 401
396 402 # handle extra fields
397 403 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
398 404 kwargs):
399 405 k = RepositoryField.un_prefix_key(field)
400 406 ex_field = RepositoryField.get_by_key_name(
401 407 key=k, repo=cur_repo)
402 408 if ex_field:
403 409 ex_field.field_value = kwargs[field]
404 410 self.sa.add(ex_field)
405 411 self.sa.add(cur_repo)
406 412
407 413 if source_repo_name != new_name:
408 414 # rename repository
409 415 self._rename_filesystem_repo(
410 416 old=source_repo_name, new=new_name)
411 417
412 418 return cur_repo
413 419 except Exception:
414 420 log.error(traceback.format_exc())
415 421 raise
416 422
417 423 def _create_repo(self, repo_name, repo_type, description, owner,
418 424 private=False, clone_uri=None, repo_group=None,
419 425 landing_rev='rev:tip', fork_of=None,
420 426 copy_fork_permissions=False, enable_statistics=False,
421 427 enable_locking=False, enable_downloads=False,
422 428 copy_group_permissions=False,
423 429 state=Repository.STATE_PENDING):
424 430 """
425 431 Create repository inside database with PENDING state, this should be
426 432 only executed by create() repo. With exception of importing existing
427 433 repos
428 434 """
429 435 from rhodecode.model.scm import ScmModel
430 436
431 437 owner = self._get_user(owner)
432 438 fork_of = self._get_repo(fork_of)
433 439 repo_group = self._get_repo_group(safe_int(repo_group))
434 440
435 441 try:
436 442 repo_name = safe_unicode(repo_name)
437 443 description = safe_unicode(description)
438 444 # repo name is just a name of repository
439 445 # while repo_name_full is a full qualified name that is combined
440 446 # with name and path of group
441 447 repo_name_full = repo_name
442 448 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
443 449
444 450 new_repo = Repository()
445 451 new_repo.repo_state = state
446 452 new_repo.enable_statistics = False
447 453 new_repo.repo_name = repo_name_full
448 454 new_repo.repo_type = repo_type
449 455 new_repo.user = owner
450 456 new_repo.group = repo_group
451 457 new_repo.description = description or repo_name
452 458 new_repo.private = private
453 459 new_repo.clone_uri = clone_uri
454 460 new_repo.landing_rev = landing_rev
455 461
456 462 new_repo.enable_statistics = enable_statistics
457 463 new_repo.enable_locking = enable_locking
458 464 new_repo.enable_downloads = enable_downloads
459 465
460 466 if repo_group:
461 467 new_repo.enable_locking = repo_group.enable_locking
462 468
463 469 if fork_of:
464 470 parent_repo = fork_of
465 471 new_repo.fork = parent_repo
466 472
467 473 self.sa.add(new_repo)
468 474
469 475 EMPTY_PERM = 'repository.none'
470 476 if fork_of and copy_fork_permissions:
471 477 repo = fork_of
472 478 user_perms = UserRepoToPerm.query() \
473 479 .filter(UserRepoToPerm.repository == repo).all()
474 480 group_perms = UserGroupRepoToPerm.query() \
475 481 .filter(UserGroupRepoToPerm.repository == repo).all()
476 482
477 483 for perm in user_perms:
478 484 UserRepoToPerm.create(
479 485 perm.user, new_repo, perm.permission)
480 486
481 487 for perm in group_perms:
482 488 UserGroupRepoToPerm.create(
483 489 perm.users_group, new_repo, perm.permission)
484 490 # in case we copy permissions and also set this repo to private
485 491 # override the default user permission to make it a private
486 492 # repo
487 493 if private:
488 494 RepoModel(self.sa).grant_user_permission(
489 495 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
490 496
491 497 elif repo_group and copy_group_permissions:
492 498 user_perms = UserRepoGroupToPerm.query() \
493 499 .filter(UserRepoGroupToPerm.group == repo_group).all()
494 500
495 501 group_perms = UserGroupRepoGroupToPerm.query() \
496 502 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
497 503
498 504 for perm in user_perms:
499 505 perm_name = perm.permission.permission_name.replace(
500 506 'group.', 'repository.')
501 507 perm_obj = Permission.get_by_key(perm_name)
502 508 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
503 509
504 510 for perm in group_perms:
505 511 perm_name = perm.permission.permission_name.replace(
506 512 'group.', 'repository.')
507 513 perm_obj = Permission.get_by_key(perm_name)
508 514 UserGroupRepoToPerm.create(
509 515 perm.users_group, new_repo, perm_obj)
510 516
511 517 if private:
512 518 RepoModel(self.sa).grant_user_permission(
513 519 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
514 520
515 521 else:
516 522 perm_obj = self._create_default_perms(new_repo, private)
517 523 self.sa.add(perm_obj)
518 524
519 525 # now automatically start following this repository as owner
520 526 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
521 527 owner.user_id)
522 528 # we need to flush here, in order to check if database won't
523 529 # throw any exceptions, create filesystem dirs at the very end
524 530 self.sa.flush()
525 531
526 532 return new_repo
527 533 except Exception:
528 534 log.error(traceback.format_exc())
529 535 raise
530 536
531 537 def create(self, form_data, cur_user):
532 538 """
533 539 Create repository using celery tasks
534 540
535 541 :param form_data:
536 542 :param cur_user:
537 543 """
538 544 from rhodecode.lib.celerylib import tasks, run_task
539 545 return run_task(tasks.create_repo, form_data, cur_user)
540 546
541 547 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
542 548 perm_deletions=None, check_perms=True,
543 549 cur_user=None):
544 550 if not perm_additions:
545 551 perm_additions = []
546 552 if not perm_updates:
547 553 perm_updates = []
548 554 if not perm_deletions:
549 555 perm_deletions = []
550 556
551 557 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
552 558
553 559 # update permissions
554 560 for member_id, perm, member_type in perm_updates:
555 561 member_id = int(member_id)
556 562 if member_type == 'user':
557 563 # this updates also current one if found
558 564 self.grant_user_permission(
559 565 repo=repo, user=member_id, perm=perm)
560 566 else: # set for user group
561 567 # check if we have permissions to alter this usergroup
562 568 member_name = UserGroup.get(member_id).users_group_name
563 569 if not check_perms or HasUserGroupPermissionAny(
564 570 *req_perms)(member_name, user=cur_user):
565 571 self.grant_user_group_permission(
566 572 repo=repo, group_name=member_id, perm=perm)
567 573
568 574 # set new permissions
569 575 for member_id, perm, member_type in perm_additions:
570 576 member_id = int(member_id)
571 577 if member_type == 'user':
572 578 self.grant_user_permission(
573 579 repo=repo, user=member_id, perm=perm)
574 580 else: # set for user group
575 581 # check if we have permissions to alter this usergroup
576 582 member_name = UserGroup.get(member_id).users_group_name
577 583 if not check_perms or HasUserGroupPermissionAny(
578 584 *req_perms)(member_name, user=cur_user):
579 585 self.grant_user_group_permission(
580 586 repo=repo, group_name=member_id, perm=perm)
581 587
582 588 # delete permissions
583 589 for member_id, perm, member_type in perm_deletions:
584 590 member_id = int(member_id)
585 591 if member_type == 'user':
586 592 self.revoke_user_permission(repo=repo, user=member_id)
587 593 else: # set for user group
588 594 # check if we have permissions to alter this usergroup
589 595 member_name = UserGroup.get(member_id).users_group_name
590 596 if not check_perms or HasUserGroupPermissionAny(
591 597 *req_perms)(member_name, user=cur_user):
592 598 self.revoke_user_group_permission(
593 599 repo=repo, group_name=member_id)
594 600
595 601 def create_fork(self, form_data, cur_user):
596 602 """
597 603 Simple wrapper into executing celery task for fork creation
598 604
599 605 :param form_data:
600 606 :param cur_user:
601 607 """
602 608 from rhodecode.lib.celerylib import tasks, run_task
603 609 return run_task(tasks.create_repo_fork, form_data, cur_user)
604 610
605 611 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
606 612 """
607 613 Delete given repository, forks parameter defines what do do with
608 614 attached forks. Throws AttachedForksError if deleted repo has attached
609 615 forks
610 616
611 617 :param repo:
612 618 :param forks: str 'delete' or 'detach'
613 619 :param fs_remove: remove(archive) repo from filesystem
614 620 """
615 621 if not cur_user:
616 622 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
617 623 repo = self._get_repo(repo)
618 624 if repo:
619 625 if forks == 'detach':
620 626 for r in repo.forks:
621 627 r.fork = None
622 628 self.sa.add(r)
623 629 elif forks == 'delete':
624 630 for r in repo.forks:
625 631 self.delete(r, forks='delete')
626 632 elif [f for f in repo.forks]:
627 633 raise AttachedForksError()
628 634
629 635 old_repo_dict = repo.get_dict()
630 636 try:
631 637 self.sa.delete(repo)
632 638 if fs_remove:
633 639 self._delete_filesystem_repo(repo)
634 640 else:
635 641 log.debug('skipping removal from filesystem')
636 642 old_repo_dict.update({
637 643 'deleted_by': cur_user,
638 644 'deleted_on': time.time(),
639 645 })
640 646 log_delete_repository(**old_repo_dict)
641 647 except Exception:
642 648 log.error(traceback.format_exc())
643 649 raise
644 650
645 651 def grant_user_permission(self, repo, user, perm):
646 652 """
647 653 Grant permission for user on given repository, or update existing one
648 654 if found
649 655
650 656 :param repo: Instance of Repository, repository_id, or repository name
651 657 :param user: Instance of User, user_id or username
652 658 :param perm: Instance of Permission, or permission_name
653 659 """
654 660 user = self._get_user(user)
655 661 repo = self._get_repo(repo)
656 662 permission = self._get_perm(perm)
657 663
658 664 # check if we have that permission already
659 665 obj = self.sa.query(UserRepoToPerm) \
660 666 .filter(UserRepoToPerm.user == user) \
661 667 .filter(UserRepoToPerm.repository == repo) \
662 668 .scalar()
663 669 if obj is None:
664 670 # create new !
665 671 obj = UserRepoToPerm()
666 672 obj.repository = repo
667 673 obj.user = user
668 674 obj.permission = permission
669 675 self.sa.add(obj)
670 676 log.debug('Granted perm %s to %s on %s', perm, user, repo)
671 677 action_logger_generic(
672 678 'granted permission: {} to user: {} on repo: {}'.format(
673 679 perm, user, repo), namespace='security.repo')
674 680 return obj
675 681
676 682 def revoke_user_permission(self, repo, user):
677 683 """
678 684 Revoke permission for user on given repository
679 685
680 686 :param repo: Instance of Repository, repository_id, or repository name
681 687 :param user: Instance of User, user_id or username
682 688 """
683 689
684 690 user = self._get_user(user)
685 691 repo = self._get_repo(repo)
686 692
687 693 obj = self.sa.query(UserRepoToPerm) \
688 694 .filter(UserRepoToPerm.repository == repo) \
689 695 .filter(UserRepoToPerm.user == user) \
690 696 .scalar()
691 697 if obj:
692 698 self.sa.delete(obj)
693 699 log.debug('Revoked perm on %s on %s', repo, user)
694 700 action_logger_generic(
695 701 'revoked permission from user: {} on repo: {}'.format(
696 702 user, repo), namespace='security.repo')
697 703
698 704 def grant_user_group_permission(self, repo, group_name, perm):
699 705 """
700 706 Grant permission for user group on given repository, or update
701 707 existing one if found
702 708
703 709 :param repo: Instance of Repository, repository_id, or repository name
704 710 :param group_name: Instance of UserGroup, users_group_id,
705 711 or user group name
706 712 :param perm: Instance of Permission, or permission_name
707 713 """
708 714 repo = self._get_repo(repo)
709 715 group_name = self._get_user_group(group_name)
710 716 permission = self._get_perm(perm)
711 717
712 718 # check if we have that permission already
713 719 obj = self.sa.query(UserGroupRepoToPerm) \
714 720 .filter(UserGroupRepoToPerm.users_group == group_name) \
715 721 .filter(UserGroupRepoToPerm.repository == repo) \
716 722 .scalar()
717 723
718 724 if obj is None:
719 725 # create new
720 726 obj = UserGroupRepoToPerm()
721 727
722 728 obj.repository = repo
723 729 obj.users_group = group_name
724 730 obj.permission = permission
725 731 self.sa.add(obj)
726 732 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
727 733 action_logger_generic(
728 734 'granted permission: {} to usergroup: {} on repo: {}'.format(
729 735 perm, group_name, repo), namespace='security.repo')
730 736
731 737 return obj
732 738
733 739 def revoke_user_group_permission(self, repo, group_name):
734 740 """
735 741 Revoke permission for user group on given repository
736 742
737 743 :param repo: Instance of Repository, repository_id, or repository name
738 744 :param group_name: Instance of UserGroup, users_group_id,
739 745 or user group name
740 746 """
741 747 repo = self._get_repo(repo)
742 748 group_name = self._get_user_group(group_name)
743 749
744 750 obj = self.sa.query(UserGroupRepoToPerm) \
745 751 .filter(UserGroupRepoToPerm.repository == repo) \
746 752 .filter(UserGroupRepoToPerm.users_group == group_name) \
747 753 .scalar()
748 754 if obj:
749 755 self.sa.delete(obj)
750 756 log.debug('Revoked perm to %s on %s', repo, group_name)
751 757 action_logger_generic(
752 758 'revoked permission from usergroup: {} on repo: {}'.format(
753 759 group_name, repo), namespace='security.repo')
754 760
755 761 def delete_stats(self, repo_name):
756 762 """
757 763 removes stats for given repo
758 764
759 765 :param repo_name:
760 766 """
761 767 repo = self._get_repo(repo_name)
762 768 try:
763 769 obj = self.sa.query(Statistics) \
764 770 .filter(Statistics.repository == repo).scalar()
765 771 if obj:
766 772 self.sa.delete(obj)
767 773 except Exception:
768 774 log.error(traceback.format_exc())
769 775 raise
770 776
771 777 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
772 778 field_type='str', field_desc=''):
773 779
774 780 repo = self._get_repo(repo_name)
775 781
776 782 new_field = RepositoryField()
777 783 new_field.repository = repo
778 784 new_field.field_key = field_key
779 785 new_field.field_type = field_type # python type
780 786 new_field.field_value = field_value
781 787 new_field.field_desc = field_desc
782 788 new_field.field_label = field_label
783 789 self.sa.add(new_field)
784 790 return new_field
785 791
786 792 def delete_repo_field(self, repo_name, field_key):
787 793 repo = self._get_repo(repo_name)
788 794 field = RepositoryField.get_by_key_name(field_key, repo)
789 795 if field:
790 796 self.sa.delete(field)
791 797
792 798 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
793 799 clone_uri=None, repo_store_location=None,
794 800 use_global_config=False):
795 801 """
796 802 makes repository on filesystem. It's group aware means it'll create
797 803 a repository within a group, and alter the paths accordingly of
798 804 group location
799 805
800 806 :param repo_name:
801 807 :param alias:
802 808 :param parent:
803 809 :param clone_uri:
804 810 :param repo_store_location:
805 811 """
806 812 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
807 813 from rhodecode.model.scm import ScmModel
808 814
809 815 if Repository.NAME_SEP in repo_name:
810 816 raise ValueError(
811 817 'repo_name must not contain groups got `%s`' % repo_name)
812 818
813 819 if isinstance(repo_group, RepoGroup):
814 820 new_parent_path = os.sep.join(repo_group.full_path_splitted)
815 821 else:
816 822 new_parent_path = repo_group or ''
817 823
818 824 if repo_store_location:
819 825 _paths = [repo_store_location]
820 826 else:
821 827 _paths = [self.repos_path, new_parent_path, repo_name]
822 828 # we need to make it str for mercurial
823 829 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
824 830
825 831 # check if this path is not a repository
826 832 if is_valid_repo(repo_path, self.repos_path):
827 833 raise Exception('This path %s is a valid repository' % repo_path)
828 834
829 835 # check if this path is a group
830 836 if is_valid_repo_group(repo_path, self.repos_path):
831 837 raise Exception('This path %s is a valid group' % repo_path)
832 838
833 839 log.info('creating repo %s in %s from url: `%s`',
834 840 repo_name, safe_unicode(repo_path),
835 841 obfuscate_url_pw(clone_uri))
836 842
837 843 backend = get_backend(repo_type)
838 844
839 845 config_repo = None if use_global_config else repo_name
840 846 if config_repo and new_parent_path:
841 847 config_repo = Repository.NAME_SEP.join(
842 848 (new_parent_path, config_repo))
843 849 config = make_db_config(clear_session=False, repo=config_repo)
844 850 config.set('extensions', 'largefiles', '')
845 851
846 852 # patch and reset hooks section of UI config to not run any
847 853 # hooks on creating remote repo
848 854 config.clear_section('hooks')
849 855
850 856 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
851 857 if repo_type == 'git':
852 858 repo = backend(
853 859 repo_path, config=config, create=True, src_url=clone_uri,
854 860 bare=True)
855 861 else:
856 862 repo = backend(
857 863 repo_path, config=config, create=True, src_url=clone_uri)
858 864
859 865 ScmModel().install_hooks(repo, repo_type=repo_type)
860 866
861 867 log.debug('Created repo %s with %s backend',
862 868 safe_unicode(repo_name), safe_unicode(repo_type))
863 869 return repo
864 870
865 871 def _rename_filesystem_repo(self, old, new):
866 872 """
867 873 renames repository on filesystem
868 874
869 875 :param old: old name
870 876 :param new: new name
871 877 """
872 878 log.info('renaming repo from %s to %s', old, new)
873 879
874 880 old_path = os.path.join(self.repos_path, old)
875 881 new_path = os.path.join(self.repos_path, new)
876 882 if os.path.isdir(new_path):
877 883 raise Exception(
878 884 'Was trying to rename to already existing dir %s' % new_path
879 885 )
880 886 shutil.move(old_path, new_path)
881 887
882 888 def _delete_filesystem_repo(self, repo):
883 889 """
884 890 removes repo from filesystem, the removal is acctually made by
885 891 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
886 892 repository is no longer valid for rhodecode, can be undeleted later on
887 893 by reverting the renames on this repository
888 894
889 895 :param repo: repo object
890 896 """
891 897 rm_path = os.path.join(self.repos_path, repo.repo_name)
892 898 repo_group = repo.group
893 899 log.info("Removing repository %s", rm_path)
894 900 # disable hg/git internal that it doesn't get detected as repo
895 901 alias = repo.repo_type
896 902
897 903 config = make_db_config(clear_session=False)
898 904 config.set('extensions', 'largefiles', '')
899 905 bare = getattr(repo.scm_instance(config=config), 'bare', False)
900 906
901 907 # skip this for bare git repos
902 908 if not bare:
903 909 # disable VCS repo
904 910 vcs_path = os.path.join(rm_path, '.%s' % alias)
905 911 if os.path.exists(vcs_path):
906 912 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
907 913
908 914 _now = datetime.now()
909 915 _ms = str(_now.microsecond).rjust(6, '0')
910 916 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
911 917 repo.just_name)
912 918 if repo_group:
913 919 # if repository is in group, prefix the removal path with the group
914 920 args = repo_group.full_path_splitted + [_d]
915 921 _d = os.path.join(*args)
916 922
917 923 if os.path.isdir(rm_path):
918 924 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,681 +1,696 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import hashlib
22 22 import logging
23 23 from collections import namedtuple
24 24 from functools import wraps
25 25
26 from rhodecode.lib import caches
26 27 from rhodecode.lib.caching_query import FromCache
27 28 from rhodecode.lib.utils2 import (
28 29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
29 30 from rhodecode.model import BaseModel
30 31 from rhodecode.model.db import (
31 32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
32 33 from rhodecode.model.meta import Session
33 34
34 35
35 36 log = logging.getLogger(__name__)
36 37
37 38
38 39 UiSetting = namedtuple(
39 40 'UiSetting', ['section', 'key', 'value', 'active'])
40 41
41 42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
42 43
43 44
44 45 class SettingNotFound(Exception):
45 46 def __init__(self):
46 47 super(SettingNotFound, self).__init__('Setting is not found')
47 48
48 49
49 50 class SettingsModel(BaseModel):
50 51 BUILTIN_HOOKS = (
51 52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
52 53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PULL,
53 54 RhodeCodeUi.HOOK_PRE_PULL)
54 55 HOOKS_SECTION = 'hooks'
55 56
56 57 def __init__(self, sa=None, repo=None):
57 58 self.repo = repo
58 59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
59 60 self.SettingsDbModel = (
60 61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
61 62 super(SettingsModel, self).__init__(sa)
62 63
63 64 def get_ui_by_key(self, key):
64 65 q = self.UiDbModel.query()
65 66 q = q.filter(self.UiDbModel.ui_key == key)
66 67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
67 68 return q.scalar()
68 69
69 70 def get_ui_by_section(self, section):
70 71 q = self.UiDbModel.query()
71 72 q = q.filter(self.UiDbModel.ui_section == section)
72 73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
73 74 return q.all()
74 75
75 76 def get_ui_by_section_and_key(self, section, key):
76 77 q = self.UiDbModel.query()
77 78 q = q.filter(self.UiDbModel.ui_section == section)
78 79 q = q.filter(self.UiDbModel.ui_key == key)
79 80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
80 81 return q.scalar()
81 82
82 83 def get_ui(self, section=None, key=None):
83 84 q = self.UiDbModel.query()
84 85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
85 86
86 87 if section:
87 88 q = q.filter(self.UiDbModel.ui_section == section)
88 89 if key:
89 90 q = q.filter(self.UiDbModel.ui_key == key)
90 91
91 92 # TODO: mikhail: add caching
92 93 result = [
93 94 UiSetting(
94 95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
95 96 value=safe_str(r.ui_value), active=r.ui_active
96 97 )
97 98 for r in q.all()
98 99 ]
99 100 return result
100 101
101 102 def get_builtin_hooks(self):
102 103 q = self.UiDbModel.query()
103 104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
104 105 return self._get_hooks(q)
105 106
106 107 def get_custom_hooks(self):
107 108 q = self.UiDbModel.query()
108 109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
109 110 return self._get_hooks(q)
110 111
111 112 def create_ui_section_value(self, section, val, key=None, active=True):
112 113 new_ui = self.UiDbModel()
113 114 new_ui.ui_section = section
114 115 new_ui.ui_value = val
115 116 new_ui.ui_active = active
116 117
117 118 if self.repo:
118 119 repo = self._get_repo(self.repo)
119 120 repository_id = repo.repo_id
120 121 new_ui.repository_id = repository_id
121 122
122 123 if not key:
123 124 # keys are unique so they need appended info
124 125 if self.repo:
125 126 key = hashlib.sha1(
126 127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
127 128 else:
128 129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
129 130
130 131 new_ui.ui_key = key
131 132
132 133 Session().add(new_ui)
133 134 return new_ui
134 135
135 136 def create_or_update_hook(self, key, value):
136 137 ui = (
137 138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
138 139 self.UiDbModel())
139 140 ui.ui_section = self.HOOKS_SECTION
140 141 ui.ui_active = True
141 142 ui.ui_key = key
142 143 ui.ui_value = value
143 144
144 145 if self.repo:
145 146 repo = self._get_repo(self.repo)
146 147 repository_id = repo.repo_id
147 148 ui.repository_id = repository_id
148 149
149 150 Session().add(ui)
150 151 return ui
151 152
152 153 def delete_ui(self, id_):
153 154 ui = self.UiDbModel.get(id_)
154 155 if not ui:
155 156 raise SettingNotFound()
156 157 Session().delete(ui)
157 158
158 159 def get_setting_by_name(self, name):
159 160 q = self._get_settings_query()
160 161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
161 162 return q.scalar()
162 163
163 164 def create_or_update_setting(
164 165 self, name, val=Optional(''), type_=Optional('unicode')):
165 166 """
166 167 Creates or updates RhodeCode setting. If updates is triggered it will
167 168 only update parameters that are explicityl set Optional instance will
168 169 be skipped
169 170
170 171 :param name:
171 172 :param val:
172 173 :param type_:
173 174 :return:
174 175 """
175 176
176 177 res = self.get_setting_by_name(name)
177 178 repo = self._get_repo(self.repo) if self.repo else None
178 179
179 180 if not res:
180 181 val = Optional.extract(val)
181 182 type_ = Optional.extract(type_)
182 183
183 184 args = (
184 185 (repo.repo_id, name, val, type_)
185 186 if repo else (name, val, type_))
186 187 res = self.SettingsDbModel(*args)
187 188
188 189 else:
189 190 if self.repo:
190 191 res.repository_id = repo.repo_id
191 192
192 193 res.app_settings_name = name
193 194 if not isinstance(type_, Optional):
194 195 # update if set
195 196 res.app_settings_type = type_
196 197 if not isinstance(val, Optional):
197 198 # update if set
198 199 res.app_settings_value = val
199 200
200 201 Session.add(res)
201 202 return res
202 203
204 def invalidate_settings_cache(self):
205 namespace = 'rhodecode_settings'
206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
207 caches.clear_cache_manager(cache_manager)
208
203 209 def get_all_settings(self, cache=False):
204 q = self._get_settings_query()
205 if cache:
206 repo = self._get_repo(self.repo) if self.repo else None
207 cache_key = (
208 "get_repo_{}_settings".format(repo.repo_id)
209 if repo else "get_hg_settings")
210 q = q.options(FromCache("sql_cache_short", cache_key))
210 def _compute():
211 q = self._get_settings_query()
212 if not q:
213 raise Exception('Could not get application settings !')
211 214
212 if not q:
213 raise Exception('Could not get application settings !')
215 settings = {
216 'rhodecode_' + result.app_settings_name: result.app_settings_value
217 for result in q
218 }
219 return settings
214 220
215 settings = {
216 'rhodecode_' + result.app_settings_name: result.app_settings_value
217 for result in q
218 }
219 return settings
221 if cache:
222 log.debug('Fetching app settings using cache')
223 repo = self._get_repo(self.repo) if self.repo else None
224 namespace = 'rhodecode_settings'
225 cache_manager = caches.get_cache_manager(
226 'sql_cache_short', namespace)
227 _cache_key = (
228 "get_repo_{}_settings".format(repo.repo_id)
229 if repo else "get_app_settings")
230
231 return cache_manager.get(_cache_key, createfunc=_compute)
232
233 else:
234 return _compute()
220 235
221 236 def get_auth_settings(self):
222 237 q = self._get_settings_query()
223 238 q = q.filter(
224 239 self.SettingsDbModel.app_settings_name.startswith('auth_'))
225 240 rows = q.all()
226 241 auth_settings = {
227 242 row.app_settings_name: row.app_settings_value for row in rows}
228 243 return auth_settings
229 244
230 245 def get_auth_plugins(self):
231 246 auth_plugins = self.get_setting_by_name("auth_plugins")
232 247 return auth_plugins.app_settings_value
233 248
234 249 def get_default_repo_settings(self, strip_prefix=False):
235 250 q = self._get_settings_query()
236 251 q = q.filter(
237 252 self.SettingsDbModel.app_settings_name.startswith('default_'))
238 253 rows = q.all()
239 254
240 255 result = {}
241 256 for row in rows:
242 257 key = row.app_settings_name
243 258 if strip_prefix:
244 259 key = remove_prefix(key, prefix='default_')
245 260 result.update({key: row.app_settings_value})
246 261 return result
247 262
248 263 def get_repo(self):
249 264 repo = self._get_repo(self.repo)
250 265 if not repo:
251 266 raise Exception(
252 267 'Repository {} cannot be found'.format(self.repo))
253 268 return repo
254 269
255 270 def _filter_by_repo(self, model, query):
256 271 if self.repo:
257 272 repo = self.get_repo()
258 273 query = query.filter(model.repository_id == repo.repo_id)
259 274 return query
260 275
261 276 def _get_hooks(self, query):
262 277 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
263 278 query = self._filter_by_repo(RepoRhodeCodeUi, query)
264 279 return query.all()
265 280
266 281 def _get_settings_query(self):
267 282 q = self.SettingsDbModel.query()
268 283 return self._filter_by_repo(RepoRhodeCodeSetting, q)
269 284
270 285 def list_enabled_social_plugins(self, settings):
271 286 enabled = []
272 287 for plug in SOCIAL_PLUGINS_LIST:
273 288 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
274 289 )):
275 290 enabled.append(plug)
276 291 return enabled
277 292
278 293
279 294 def assert_repo_settings(func):
280 295 @wraps(func)
281 296 def _wrapper(self, *args, **kwargs):
282 297 if not self.repo_settings:
283 298 raise Exception('Repository is not specified')
284 299 return func(self, *args, **kwargs)
285 300 return _wrapper
286 301
287 302
288 303 class IssueTrackerSettingsModel(object):
289 304 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
290 305 SETTINGS_PREFIX = 'issuetracker_'
291 306
292 307 def __init__(self, sa=None, repo=None):
293 308 self.global_settings = SettingsModel(sa=sa)
294 309 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
295 310
296 311 @property
297 312 def inherit_global_settings(self):
298 313 if not self.repo_settings:
299 314 return True
300 315 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
301 316 return setting.app_settings_value if setting else True
302 317
303 318 @inherit_global_settings.setter
304 319 def inherit_global_settings(self, value):
305 320 if self.repo_settings:
306 321 settings = self.repo_settings.create_or_update_setting(
307 322 self.INHERIT_SETTINGS, value, type_='bool')
308 323 Session().add(settings)
309 324
310 325 def _get_keyname(self, key, uid, prefix=''):
311 326 return '{0}{1}{2}_{3}'.format(
312 327 prefix, self.SETTINGS_PREFIX, key, uid)
313 328
314 329 def _make_dict_for_settings(self, qs):
315 330 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
316 331
317 332 issuetracker_entries = {}
318 333 # create keys
319 334 for k, v in qs.items():
320 335 if k.startswith(prefix_match):
321 336 uid = k[len(prefix_match):]
322 337 issuetracker_entries[uid] = None
323 338
324 339 # populate
325 340 for uid in issuetracker_entries:
326 341 issuetracker_entries[uid] = AttributeDict({
327 342 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
328 343 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
329 344 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
330 345 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
331 346 })
332 347 return issuetracker_entries
333 348
334 349 def get_global_settings(self, cache=False):
335 350 """
336 351 Returns list of global issue tracker settings
337 352 """
338 353 defaults = self.global_settings.get_all_settings(cache=cache)
339 354 settings = self._make_dict_for_settings(defaults)
340 355 return settings
341 356
342 357 def get_repo_settings(self, cache=False):
343 358 """
344 359 Returns list of issue tracker settings per repository
345 360 """
346 361 if not self.repo_settings:
347 362 raise Exception('Repository is not specified')
348 363 all_settings = self.repo_settings.get_all_settings(cache=cache)
349 364 settings = self._make_dict_for_settings(all_settings)
350 365 return settings
351 366
352 367 def get_settings(self, cache=False):
353 368 if self.inherit_global_settings:
354 369 return self.get_global_settings(cache=cache)
355 370 else:
356 371 return self.get_repo_settings(cache=cache)
357 372
358 373 def delete_entries(self, uid):
359 374 if self.repo_settings:
360 375 all_patterns = self.get_repo_settings()
361 376 settings_model = self.repo_settings
362 377 else:
363 378 all_patterns = self.get_global_settings()
364 379 settings_model = self.global_settings
365 380 entries = all_patterns.get(uid)
366 381
367 382 for del_key in entries:
368 383 setting_name = self._get_keyname(del_key, uid)
369 384 entry = settings_model.get_setting_by_name(setting_name)
370 385 if entry:
371 386 Session().delete(entry)
372 387
373 388 Session().commit()
374 389
375 390 def create_or_update_setting(
376 391 self, name, val=Optional(''), type_=Optional('unicode')):
377 392 if self.repo_settings:
378 393 setting = self.repo_settings.create_or_update_setting(
379 394 name, val, type_)
380 395 else:
381 396 setting = self.global_settings.create_or_update_setting(
382 397 name, val, type_)
383 398 return setting
384 399
385 400
386 401 class VcsSettingsModel(object):
387 402
388 403 INHERIT_SETTINGS = 'inherit_vcs_settings'
389 404 GENERAL_SETTINGS = ('use_outdated_comments', 'pr_merge_enabled')
390 405 HOOKS_SETTINGS = (
391 406 ('hooks', 'changegroup.repo_size'),
392 407 ('hooks', 'changegroup.push_logger'),
393 408 ('hooks', 'outgoing.pull_logger'))
394 409 HG_SETTINGS = (
395 410 ('extensions', 'largefiles'), ('phases', 'publish'))
396 411 GLOBAL_HG_SETTINGS = HG_SETTINGS + (('extensions', 'hgsubversion'), )
397 412 SVN_BRANCH_SECTION = 'vcs_svn_branch'
398 413 SVN_TAG_SECTION = 'vcs_svn_tag'
399 414 SSL_SETTING = ('web', 'push_ssl')
400 415 PATH_SETTING = ('paths', '/')
401 416
402 417 def __init__(self, sa=None, repo=None):
403 418 self.global_settings = SettingsModel(sa=sa)
404 419 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
405 420 self._ui_settings = self.HG_SETTINGS + self.HOOKS_SETTINGS
406 421 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
407 422
408 423 @property
409 424 @assert_repo_settings
410 425 def inherit_global_settings(self):
411 426 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
412 427 return setting.app_settings_value if setting else True
413 428
414 429 @inherit_global_settings.setter
415 430 @assert_repo_settings
416 431 def inherit_global_settings(self, value):
417 432 self.repo_settings.create_or_update_setting(
418 433 self.INHERIT_SETTINGS, value, type_='bool')
419 434
420 435 def get_global_svn_branch_patterns(self):
421 436 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
422 437
423 438 @assert_repo_settings
424 439 def get_repo_svn_branch_patterns(self):
425 440 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
426 441
427 442 def get_global_svn_tag_patterns(self):
428 443 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
429 444
430 445 @assert_repo_settings
431 446 def get_repo_svn_tag_patterns(self):
432 447 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
433 448
434 449 def get_global_settings(self):
435 450 return self._collect_all_settings(global_=True)
436 451
437 452 @assert_repo_settings
438 453 def get_repo_settings(self):
439 454 return self._collect_all_settings(global_=False)
440 455
441 456 @assert_repo_settings
442 457 def create_or_update_repo_settings(
443 458 self, data, inherit_global_settings=False):
444 459 from rhodecode.model.scm import ScmModel
445 460
446 461 self.inherit_global_settings = inherit_global_settings
447 462
448 463 repo = self.repo_settings.get_repo()
449 464 if not inherit_global_settings:
450 465 if repo.repo_type == 'svn':
451 466 self.create_repo_svn_settings(data)
452 467 else:
453 468 self.create_or_update_repo_hook_settings(data)
454 469 self.create_or_update_repo_pr_settings(data)
455 470
456 471 if repo.repo_type == 'hg':
457 472 self.create_or_update_repo_hg_settings(data)
458 473
459 474 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
460 475
461 476 @assert_repo_settings
462 477 def create_or_update_repo_hook_settings(self, data):
463 478 for section, key in self.HOOKS_SETTINGS:
464 479 data_key = self._get_form_ui_key(section, key)
465 480 if data_key not in data:
466 481 raise ValueError(
467 482 'The given data does not contain {} key'.format(data_key))
468 483
469 484 active = data.get(data_key)
470 485 repo_setting = self.repo_settings.get_ui_by_section_and_key(
471 486 section, key)
472 487 if not repo_setting:
473 488 global_setting = self.global_settings.\
474 489 get_ui_by_section_and_key(section, key)
475 490 self.repo_settings.create_ui_section_value(
476 491 section, global_setting.ui_value, key=key, active=active)
477 492 else:
478 493 repo_setting.ui_active = active
479 494 Session().add(repo_setting)
480 495
481 496 def update_global_hook_settings(self, data):
482 497 for section, key in self.HOOKS_SETTINGS:
483 498 data_key = self._get_form_ui_key(section, key)
484 499 if data_key not in data:
485 500 raise ValueError(
486 501 'The given data does not contain {} key'.format(data_key))
487 502 active = data.get(data_key)
488 503 repo_setting = self.global_settings.get_ui_by_section_and_key(
489 504 section, key)
490 505 repo_setting.ui_active = active
491 506 Session().add(repo_setting)
492 507
493 508 @assert_repo_settings
494 509 def create_or_update_repo_pr_settings(self, data):
495 510 return self._create_or_update_general_settings(
496 511 self.repo_settings, data)
497 512
498 513 def create_or_update_global_pr_settings(self, data):
499 514 return self._create_or_update_general_settings(
500 515 self.global_settings, data)
501 516
502 517 @assert_repo_settings
503 518 def create_repo_svn_settings(self, data):
504 519 return self._create_svn_settings(self.repo_settings, data)
505 520
506 521 def create_global_svn_settings(self, data):
507 522 return self._create_svn_settings(self.global_settings, data)
508 523
509 524 @assert_repo_settings
510 525 def create_or_update_repo_hg_settings(self, data):
511 526 largefiles, phases = self.HG_SETTINGS
512 527 largefiles_key, phases_key = self._get_hg_settings(
513 528 self.HG_SETTINGS, data)
514 529 self._create_or_update_ui(
515 530 self.repo_settings, *largefiles, value='',
516 531 active=data[largefiles_key])
517 532 self._create_or_update_ui(
518 533 self.repo_settings, *phases, value=safe_str(data[phases_key]))
519 534
520 535 def create_or_update_global_hg_settings(self, data):
521 536 largefiles, phases, subversion = self.GLOBAL_HG_SETTINGS
522 537 largefiles_key, phases_key, subversion_key = self._get_hg_settings(
523 538 self.GLOBAL_HG_SETTINGS, data)
524 539 self._create_or_update_ui(
525 540 self.global_settings, *largefiles, value='',
526 541 active=data[largefiles_key])
527 542 self._create_or_update_ui(
528 543 self.global_settings, *phases, value=safe_str(data[phases_key]))
529 544 self._create_or_update_ui(
530 545 self.global_settings, *subversion, active=data[subversion_key])
531 546
532 547 def update_global_ssl_setting(self, value):
533 548 self._create_or_update_ui(
534 549 self.global_settings, *self.SSL_SETTING, value=value)
535 550
536 551 def update_global_path_setting(self, value):
537 552 self._create_or_update_ui(
538 553 self.global_settings, *self.PATH_SETTING, value=value)
539 554
540 555 @assert_repo_settings
541 556 def delete_repo_svn_pattern(self, id_):
542 557 self.repo_settings.delete_ui(id_)
543 558
544 559 def delete_global_svn_pattern(self, id_):
545 560 self.global_settings.delete_ui(id_)
546 561
547 562 @assert_repo_settings
548 563 def get_repo_ui_settings(self, section=None, key=None):
549 564 global_uis = self.global_settings.get_ui(section, key)
550 565 repo_uis = self.repo_settings.get_ui(section, key)
551 566 filtered_repo_uis = self._filter_ui_settings(repo_uis)
552 567 filtered_repo_uis_keys = [
553 568 (s.section, s.key) for s in filtered_repo_uis]
554 569
555 570 def _is_global_ui_filtered(ui):
556 571 return (
557 572 (ui.section, ui.key) in filtered_repo_uis_keys
558 573 or ui.section in self._svn_sections)
559 574
560 575 filtered_global_uis = [
561 576 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
562 577
563 578 return filtered_global_uis + filtered_repo_uis
564 579
565 580 def get_global_ui_settings(self, section=None, key=None):
566 581 return self.global_settings.get_ui(section, key)
567 582
568 583 def get_ui_settings(self, section=None, key=None):
569 584 if not self.repo_settings or self.inherit_global_settings:
570 585 return self.get_global_ui_settings(section, key)
571 586 else:
572 587 return self.get_repo_ui_settings(section, key)
573 588
574 589 def get_svn_patterns(self, section=None):
575 590 if not self.repo_settings:
576 591 return self.get_global_ui_settings(section)
577 592 else:
578 593 return self.get_repo_ui_settings(section)
579 594
580 595 @assert_repo_settings
581 596 def get_repo_general_settings(self):
582 597 global_settings = self.global_settings.get_all_settings()
583 598 repo_settings = self.repo_settings.get_all_settings()
584 599 filtered_repo_settings = self._filter_general_settings(repo_settings)
585 600 global_settings.update(filtered_repo_settings)
586 601 return global_settings
587 602
588 603 def get_global_general_settings(self):
589 604 return self.global_settings.get_all_settings()
590 605
591 606 def get_general_settings(self):
592 607 if not self.repo_settings or self.inherit_global_settings:
593 608 return self.get_global_general_settings()
594 609 else:
595 610 return self.get_repo_general_settings()
596 611
597 612 def get_repos_location(self):
598 613 return self.global_settings.get_ui_by_key('/').ui_value
599 614
600 615 def _filter_ui_settings(self, settings):
601 616 filtered_settings = [
602 617 s for s in settings if self._should_keep_setting(s)]
603 618 return filtered_settings
604 619
605 620 def _should_keep_setting(self, setting):
606 621 keep = (
607 622 (setting.section, setting.key) in self._ui_settings or
608 623 setting.section in self._svn_sections)
609 624 return keep
610 625
611 626 def _filter_general_settings(self, settings):
612 627 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
613 628 return {
614 629 k: settings[k]
615 630 for k in settings if k in keys}
616 631
617 632 def _collect_all_settings(self, global_=False):
618 633 settings = self.global_settings if global_ else self.repo_settings
619 634 result = {}
620 635
621 636 for section, key in self._ui_settings:
622 637 ui = settings.get_ui_by_section_and_key(section, key)
623 638 result_key = self._get_form_ui_key(section, key)
624 639 if ui:
625 640 if section in ('hooks', 'extensions'):
626 641 result[result_key] = ui.ui_active
627 642 else:
628 643 result[result_key] = ui.ui_value
629 644
630 645 for name in self.GENERAL_SETTINGS:
631 646 setting = settings.get_setting_by_name(name)
632 647 if setting:
633 648 result_key = 'rhodecode_{}'.format(name)
634 649 result[result_key] = setting.app_settings_value
635 650
636 651 return result
637 652
638 653 def _get_form_ui_key(self, section, key):
639 654 return '{section}_{key}'.format(
640 655 section=section, key=key.replace('.', '_'))
641 656
642 657 def _create_or_update_ui(
643 658 self, settings, section, key, value=None, active=None):
644 659 ui = settings.get_ui_by_section_and_key(section, key)
645 660 if not ui:
646 661 active = True if active is None else active
647 662 settings.create_ui_section_value(
648 663 section, value, key=key, active=active)
649 664 else:
650 665 if active is not None:
651 666 ui.ui_active = active
652 667 if value is not None:
653 668 ui.ui_value = value
654 669 Session().add(ui)
655 670
656 671 def _create_svn_settings(self, settings, data):
657 672 svn_settings = {
658 673 'new_svn_branch': self.SVN_BRANCH_SECTION,
659 674 'new_svn_tag': self.SVN_TAG_SECTION
660 675 }
661 676 for key in svn_settings:
662 677 if data.get(key):
663 678 settings.create_ui_section_value(svn_settings[key], data[key])
664 679
665 680 def _create_or_update_general_settings(self, settings, data):
666 681 for name in self.GENERAL_SETTINGS:
667 682 data_key = 'rhodecode_{}'.format(name)
668 683 if data_key not in data:
669 684 raise ValueError(
670 685 'The given data does not contain {} key'.format(data_key))
671 686 setting = settings.create_or_update_setting(
672 687 name, data[data_key], 'bool')
673 688 Session().add(setting)
674 689
675 690 def _get_hg_settings(self, settings, data):
676 691 data_keys = [self._get_form_ui_key(*s) for s in settings]
677 692 for data_key in data_keys:
678 693 if data_key not in data:
679 694 raise ValueError(
680 695 'The given data does not contain {} key'.format(data_key))
681 696 return data_keys
@@ -1,836 +1,838 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 users model for RhodeCode
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27
28 28 import datetime
29 from pylons import url
30 29 from pylons.i18n.translation import _
31 30
32 31 import ipaddress
33 32 from sqlalchemy.exc import DatabaseError
34 33 from sqlalchemy.sql.expression import true, false
35 34
35 from rhodecode.events import UserPreCreate, UserPreUpdate
36 36 from rhodecode.lib.utils2 import (
37 37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
38 38 AttributeDict)
39 39 from rhodecode.lib.caching_query import FromCache
40 40 from rhodecode.model import BaseModel
41 41 from rhodecode.model.auth_token import AuthTokenModel
42 42 from rhodecode.model.db import (
43 43 User, UserToPerm, UserEmailMap, UserIpMap)
44 44 from rhodecode.lib.exceptions import (
45 45 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
46 46 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.repo_group import RepoGroupModel
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class UserModel(BaseModel):
55 55 cls = User
56 56
57 57 def get(self, user_id, cache=False):
58 58 user = self.sa.query(User)
59 59 if cache:
60 60 user = user.options(FromCache("sql_cache_short",
61 61 "get_user_%s" % user_id))
62 62 return user.get(user_id)
63 63
64 64 def get_user(self, user):
65 65 return self._get_user(user)
66 66
67 67 def get_by_username(self, username, cache=False, case_insensitive=False):
68 68
69 69 if case_insensitive:
70 70 user = self.sa.query(User).filter(User.username.ilike(username))
71 71 else:
72 72 user = self.sa.query(User)\
73 73 .filter(User.username == username)
74 74 if cache:
75 75 user = user.options(FromCache("sql_cache_short",
76 76 "get_user_%s" % username))
77 77 return user.scalar()
78 78
79 79 def get_by_email(self, email, cache=False, case_insensitive=False):
80 80 return User.get_by_email(email, case_insensitive, cache)
81 81
82 82 def get_by_auth_token(self, auth_token, cache=False):
83 83 return User.get_by_auth_token(auth_token, cache)
84 84
85 85 def get_active_user_count(self, cache=False):
86 86 return User.query().filter(
87 87 User.active == True).filter(
88 88 User.username != User.DEFAULT_USER).count()
89 89
90 90 def create(self, form_data, cur_user=None):
91 91 if not cur_user:
92 92 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
93 93
94 94 user_data = {
95 95 'username': form_data['username'],
96 96 'password': form_data['password'],
97 97 'email': form_data['email'],
98 98 'firstname': form_data['firstname'],
99 99 'lastname': form_data['lastname'],
100 100 'active': form_data['active'],
101 101 'extern_type': form_data['extern_type'],
102 102 'extern_name': form_data['extern_name'],
103 103 'admin': False,
104 104 'cur_user': cur_user
105 105 }
106 106
107 107 try:
108 108 if form_data.get('create_repo_group'):
109 109 user_data['create_repo_group'] = True
110 110 if form_data.get('password_change'):
111 111 user_data['force_password_change'] = True
112 112
113 113 return UserModel().create_or_update(**user_data)
114 114 except Exception:
115 115 log.error(traceback.format_exc())
116 116 raise
117 117
118 118 def update_user(self, user, skip_attrs=None, **kwargs):
119 119 from rhodecode.lib.auth import get_crypt_password
120 120
121 121 user = self._get_user(user)
122 122 if user.username == User.DEFAULT_USER:
123 123 raise DefaultUserException(
124 124 _("You can't Edit this user since it's"
125 125 " crucial for entire application"))
126 126
127 127 # first store only defaults
128 128 user_attrs = {
129 129 'updating_user_id': user.user_id,
130 130 'username': user.username,
131 131 'password': user.password,
132 132 'email': user.email,
133 133 'firstname': user.name,
134 134 'lastname': user.lastname,
135 135 'active': user.active,
136 136 'admin': user.admin,
137 137 'extern_name': user.extern_name,
138 138 'extern_type': user.extern_type,
139 139 'language': user.user_data.get('language')
140 140 }
141 141
142 142 # in case there's new_password, that comes from form, use it to
143 143 # store password
144 144 if kwargs.get('new_password'):
145 145 kwargs['password'] = kwargs['new_password']
146 146
147 147 # cleanups, my_account password change form
148 148 kwargs.pop('current_password', None)
149 149 kwargs.pop('new_password', None)
150 150 kwargs.pop('new_password_confirmation', None)
151 151
152 152 # cleanups, user edit password change form
153 153 kwargs.pop('password_confirmation', None)
154 154 kwargs.pop('password_change', None)
155 155
156 156 # create repo group on user creation
157 157 kwargs.pop('create_repo_group', None)
158 158
159 159 # legacy forms send name, which is the firstname
160 160 firstname = kwargs.pop('name', None)
161 161 if firstname:
162 162 kwargs['firstname'] = firstname
163 163
164 164 for k, v in kwargs.items():
165 165 # skip if we don't want to update this
166 166 if skip_attrs and k in skip_attrs:
167 167 continue
168 168
169 169 user_attrs[k] = v
170 170
171 171 try:
172 172 return self.create_or_update(**user_attrs)
173 173 except Exception:
174 174 log.error(traceback.format_exc())
175 175 raise
176 176
177 177 def create_or_update(
178 178 self, username, password, email, firstname='', lastname='',
179 179 active=True, admin=False, extern_type=None, extern_name=None,
180 180 cur_user=None, plugin=None, force_password_change=False,
181 181 allow_to_create_user=True, create_repo_group=False,
182 182 updating_user_id=None, language=None, strict_creation_check=True):
183 183 """
184 184 Creates a new instance if not found, or updates current one
185 185
186 186 :param username:
187 187 :param password:
188 188 :param email:
189 189 :param firstname:
190 190 :param lastname:
191 191 :param active:
192 192 :param admin:
193 193 :param extern_type:
194 194 :param extern_name:
195 195 :param cur_user:
196 196 :param plugin: optional plugin this method was called from
197 197 :param force_password_change: toggles new or existing user flag
198 198 for password change
199 199 :param allow_to_create_user: Defines if the method can actually create
200 200 new users
201 201 :param create_repo_group: Defines if the method should also
202 202 create an repo group with user name, and owner
203 203 :param updating_user_id: if we set it up this is the user we want to
204 204 update this allows to editing username.
205 205 :param language: language of user from interface.
206 206
207 207 :returns: new User object with injected `is_new_user` attribute.
208 208 """
209 209 if not cur_user:
210 210 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
211 211
212 212 from rhodecode.lib.auth import (
213 213 get_crypt_password, check_password, generate_auth_token)
214 214 from rhodecode.lib.hooks_base import (
215 215 log_create_user, check_allowed_create_user)
216 216
217 217 def _password_change(new_user, password):
218 218 # empty password
219 219 if not new_user.password:
220 220 return False
221 221
222 222 # password check is only needed for RhodeCode internal auth calls
223 223 # in case it's a plugin we don't care
224 224 if not plugin:
225 225
226 226 # first check if we gave crypted password back, and if it matches
227 227 # it's not password change
228 228 if new_user.password == password:
229 229 return False
230 230
231 231 password_match = check_password(password, new_user.password)
232 232 if not password_match:
233 233 return True
234 234
235 235 return False
236 236
237 237 user_data = {
238 238 'username': username,
239 239 'password': password,
240 240 'email': email,
241 241 'firstname': firstname,
242 242 'lastname': lastname,
243 243 'active': active,
244 244 'admin': admin
245 245 }
246 246
247 247 if updating_user_id:
248 248 log.debug('Checking for existing account in RhodeCode '
249 249 'database with user_id `%s` ' % (updating_user_id,))
250 250 user = User.get(updating_user_id)
251 251 else:
252 252 log.debug('Checking for existing account in RhodeCode '
253 253 'database with username `%s` ' % (username,))
254 254 user = User.get_by_username(username, case_insensitive=True)
255 255
256 256 if user is None:
257 257 # we check internal flag if this method is actually allowed to
258 258 # create new user
259 259 if not allow_to_create_user:
260 260 msg = ('Method wants to create new user, but it is not '
261 261 'allowed to do so')
262 262 log.warning(msg)
263 263 raise NotAllowedToCreateUserError(msg)
264 264
265 265 log.debug('Creating new user %s', username)
266 266
267 267 # only if we create user that is active
268 268 new_active_user = active
269 269 if new_active_user and strict_creation_check:
270 270 # raises UserCreationError if it's not allowed for any reason to
271 271 # create new active user, this also executes pre-create hooks
272 272 check_allowed_create_user(user_data, cur_user, strict_check=True)
273 self.send_event(UserPreCreate(user_data))
273 274 new_user = User()
274 275 edit = False
275 276 else:
276 277 log.debug('updating user %s', username)
278 self.send_event(UserPreUpdate(user, user_data))
277 279 new_user = user
278 280 edit = True
279 281
280 282 # we're not allowed to edit default user
281 283 if user.username == User.DEFAULT_USER:
282 284 raise DefaultUserException(
283 285 _("You can't edit this user (`%(username)s`) since it's "
284 286 "crucial for entire application") % {'username': user.username})
285 287
286 288 # inject special attribute that will tell us if User is new or old
287 289 new_user.is_new_user = not edit
288 290 # for users that didn's specify auth type, we use RhodeCode built in
289 291 from rhodecode.authentication.plugins import auth_rhodecode
290 292 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
291 293 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
292 294
293 295 try:
294 296 new_user.username = username
295 297 new_user.admin = admin
296 298 new_user.email = email
297 299 new_user.active = active
298 300 new_user.extern_name = safe_unicode(extern_name)
299 301 new_user.extern_type = safe_unicode(extern_type)
300 302 new_user.name = firstname
301 303 new_user.lastname = lastname
302 304
303 305 if not edit:
304 306 new_user.api_key = generate_auth_token(username)
305 307
306 308 # set password only if creating an user or password is changed
307 309 if not edit or _password_change(new_user, password):
308 310 reason = 'new password' if edit else 'new user'
309 311 log.debug('Updating password reason=>%s', reason)
310 312 new_user.password = get_crypt_password(password) if password else None
311 313
312 314 if force_password_change:
313 315 new_user.update_userdata(force_password_change=True)
314 316 if language:
315 317 new_user.update_userdata(language=language)
316 318
317 319 self.sa.add(new_user)
318 320
319 321 if not edit and create_repo_group:
320 322 # create new group same as username, and make this user an owner
321 323 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
322 324 RepoGroupModel().create(group_name=username,
323 325 group_description=desc,
324 326 owner=username, commit_early=False)
325 327 if not edit:
326 328 # add the RSS token
327 329 AuthTokenModel().create(username,
328 330 description='Generated feed token',
329 331 role=AuthTokenModel.cls.ROLE_FEED)
330 332 log_create_user(created_by=cur_user, **new_user.get_dict())
331 333 return new_user
332 334 except (DatabaseError,):
333 335 log.error(traceback.format_exc())
334 336 raise
335 337
336 338 def create_registration(self, form_data):
337 339 from rhodecode.model.notification import NotificationModel
338 340 from rhodecode.model.notification import EmailNotificationModel
339 341
340 342 try:
341 343 form_data['admin'] = False
342 344 form_data['extern_name'] = 'rhodecode'
343 345 form_data['extern_type'] = 'rhodecode'
344 346 new_user = self.create(form_data)
345 347
346 348 self.sa.add(new_user)
347 349 self.sa.flush()
348 350
349 351 user_data = new_user.get_dict()
350 352 kwargs = {
351 353 # use SQLALCHEMY safe dump of user data
352 354 'user': AttributeDict(user_data),
353 355 'date': datetime.datetime.now()
354 356 }
355 357 notification_type = EmailNotificationModel.TYPE_REGISTRATION
356 358 # pre-generate the subject for notification itself
357 359 (subject,
358 360 _h, _e, # we don't care about those
359 361 body_plaintext) = EmailNotificationModel().render_email(
360 362 notification_type, **kwargs)
361 363
362 364 # create notification objects, and emails
363 365 NotificationModel().create(
364 366 created_by=new_user,
365 367 notification_subject=subject,
366 368 notification_body=body_plaintext,
367 369 notification_type=notification_type,
368 370 recipients=None, # all admins
369 371 email_kwargs=kwargs,
370 372 )
371 373
372 374 return new_user
373 375 except Exception:
374 376 log.error(traceback.format_exc())
375 377 raise
376 378
377 379 def _handle_user_repos(self, username, repositories, handle_mode=None):
378 _superadmin = self.cls.get_first_admin()
380 _superadmin = self.cls.get_first_super_admin()
379 381 left_overs = True
380 382
381 383 from rhodecode.model.repo import RepoModel
382 384
383 385 if handle_mode == 'detach':
384 386 for obj in repositories:
385 387 obj.user = _superadmin
386 388 # set description we know why we super admin now owns
387 389 # additional repositories that were orphaned !
388 390 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
389 391 self.sa.add(obj)
390 392 left_overs = False
391 393 elif handle_mode == 'delete':
392 394 for obj in repositories:
393 395 RepoModel().delete(obj, forks='detach')
394 396 left_overs = False
395 397
396 398 # if nothing is done we have left overs left
397 399 return left_overs
398 400
399 401 def _handle_user_repo_groups(self, username, repository_groups,
400 402 handle_mode=None):
401 _superadmin = self.cls.get_first_admin()
403 _superadmin = self.cls.get_first_super_admin()
402 404 left_overs = True
403 405
404 406 from rhodecode.model.repo_group import RepoGroupModel
405 407
406 408 if handle_mode == 'detach':
407 409 for r in repository_groups:
408 410 r.user = _superadmin
409 411 # set description we know why we super admin now owns
410 412 # additional repositories that were orphaned !
411 413 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
412 414 self.sa.add(r)
413 415 left_overs = False
414 416 elif handle_mode == 'delete':
415 417 for r in repository_groups:
416 418 RepoGroupModel().delete(r)
417 419 left_overs = False
418 420
419 421 # if nothing is done we have left overs left
420 422 return left_overs
421 423
422 424 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
423 _superadmin = self.cls.get_first_admin()
425 _superadmin = self.cls.get_first_super_admin()
424 426 left_overs = True
425 427
426 428 from rhodecode.model.user_group import UserGroupModel
427 429
428 430 if handle_mode == 'detach':
429 431 for r in user_groups:
430 432 for user_user_group_to_perm in r.user_user_group_to_perm:
431 433 if user_user_group_to_perm.user.username == username:
432 434 user_user_group_to_perm.user = _superadmin
433 435 r.user = _superadmin
434 436 # set description we know why we super admin now owns
435 437 # additional repositories that were orphaned !
436 438 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
437 439 self.sa.add(r)
438 440 left_overs = False
439 441 elif handle_mode == 'delete':
440 442 for r in user_groups:
441 443 UserGroupModel().delete(r)
442 444 left_overs = False
443 445
444 446 # if nothing is done we have left overs left
445 447 return left_overs
446 448
447 449 def delete(self, user, cur_user=None, handle_repos=None,
448 450 handle_repo_groups=None, handle_user_groups=None):
449 451 if not cur_user:
450 452 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
451 453 user = self._get_user(user)
452 454
453 455 try:
454 456 if user.username == User.DEFAULT_USER:
455 457 raise DefaultUserException(
456 458 _(u"You can't remove this user since it's"
457 459 u" crucial for entire application"))
458 460
459 461 left_overs = self._handle_user_repos(
460 462 user.username, user.repositories, handle_repos)
461 463 if left_overs and user.repositories:
462 464 repos = [x.repo_name for x in user.repositories]
463 465 raise UserOwnsReposException(
464 466 _(u'user "%s" still owns %s repositories and cannot be '
465 467 u'removed. Switch owners or remove those repositories:%s')
466 468 % (user.username, len(repos), ', '.join(repos)))
467 469
468 470 left_overs = self._handle_user_repo_groups(
469 471 user.username, user.repository_groups, handle_repo_groups)
470 472 if left_overs and user.repository_groups:
471 473 repo_groups = [x.group_name for x in user.repository_groups]
472 474 raise UserOwnsRepoGroupsException(
473 475 _(u'user "%s" still owns %s repository groups and cannot be '
474 476 u'removed. Switch owners or remove those repository groups:%s')
475 477 % (user.username, len(repo_groups), ', '.join(repo_groups)))
476 478
477 479 left_overs = self._handle_user_user_groups(
478 480 user.username, user.user_groups, handle_user_groups)
479 481 if left_overs and user.user_groups:
480 482 user_groups = [x.users_group_name for x in user.user_groups]
481 483 raise UserOwnsUserGroupsException(
482 484 _(u'user "%s" still owns %s user groups and cannot be '
483 485 u'removed. Switch owners or remove those user groups:%s')
484 486 % (user.username, len(user_groups), ', '.join(user_groups)))
485 487
486 488 # we might change the user data with detach/delete, make sure
487 489 # the object is marked as expired before actually deleting !
488 490 self.sa.expire(user)
489 491 self.sa.delete(user)
490 492 from rhodecode.lib.hooks_base import log_delete_user
491 493 log_delete_user(deleted_by=cur_user, **user.get_dict())
492 494 except Exception:
493 495 log.error(traceback.format_exc())
494 496 raise
495 497
496 498 def reset_password_link(self, data, pwd_reset_url):
497 499 from rhodecode.lib.celerylib import tasks, run_task
498 500 from rhodecode.model.notification import EmailNotificationModel
499 501 user_email = data['email']
500 502 try:
501 503 user = User.get_by_email(user_email)
502 504 if user:
503 505 log.debug('password reset user found %s', user)
504 506
505 507 email_kwargs = {
506 508 'password_reset_url': pwd_reset_url,
507 509 'user': user,
508 510 'email': user_email,
509 511 'date': datetime.datetime.now()
510 512 }
511 513
512 514 (subject, headers, email_body,
513 515 email_body_plaintext) = EmailNotificationModel().render_email(
514 516 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
515 517
516 518 recipients = [user_email]
517 519
518 520 action_logger_generic(
519 521 'sending password reset email to user: {}'.format(
520 522 user), namespace='security.password_reset')
521 523
522 524 run_task(tasks.send_email, recipients, subject,
523 525 email_body_plaintext, email_body)
524 526
525 527 else:
526 528 log.debug("password reset email %s not found", user_email)
527 529 except Exception:
528 530 log.error(traceback.format_exc())
529 531 return False
530 532
531 533 return True
532 534
533 535 def reset_password(self, data):
534 536 from rhodecode.lib.celerylib import tasks, run_task
535 537 from rhodecode.model.notification import EmailNotificationModel
536 538 from rhodecode.lib import auth
537 539 user_email = data['email']
538 540 pre_db = True
539 541 try:
540 542 user = User.get_by_email(user_email)
541 543 new_passwd = auth.PasswordGenerator().gen_password(
542 544 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
543 545 if user:
544 546 user.password = auth.get_crypt_password(new_passwd)
545 547 # also force this user to reset his password !
546 548 user.update_userdata(force_password_change=True)
547 549
548 550 Session().add(user)
549 551 Session().commit()
550 552 log.info('change password for %s', user_email)
551 553 if new_passwd is None:
552 554 raise Exception('unable to generate new password')
553 555
554 556 pre_db = False
555 557
556 558 email_kwargs = {
557 559 'new_password': new_passwd,
558 560 'user': user,
559 561 'email': user_email,
560 562 'date': datetime.datetime.now()
561 563 }
562 564
563 565 (subject, headers, email_body,
564 566 email_body_plaintext) = EmailNotificationModel().render_email(
565 567 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
566 568
567 569 recipients = [user_email]
568 570
569 571 action_logger_generic(
570 572 'sent new password to user: {} with email: {}'.format(
571 573 user, user_email), namespace='security.password_reset')
572 574
573 575 run_task(tasks.send_email, recipients, subject,
574 576 email_body_plaintext, email_body)
575 577
576 578 except Exception:
577 579 log.error('Failed to update user password')
578 580 log.error(traceback.format_exc())
579 581 if pre_db:
580 582 # we rollback only if local db stuff fails. If it goes into
581 583 # run_task, we're pass rollback state this wouldn't work then
582 584 Session().rollback()
583 585
584 586 return True
585 587
586 588 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
587 589 """
588 590 Fetches auth_user by user_id,or api_key if present.
589 591 Fills auth_user attributes with those taken from database.
590 592 Additionally set's is_authenitated if lookup fails
591 593 present in database
592 594
593 595 :param auth_user: instance of user to set attributes
594 596 :param user_id: user id to fetch by
595 597 :param api_key: api key to fetch by
596 598 :param username: username to fetch by
597 599 """
598 600 if user_id is None and api_key is None and username is None:
599 601 raise Exception('You need to pass user_id, api_key or username')
600 602
601 603 log.debug(
602 604 'doing fill data based on: user_id:%s api_key:%s username:%s',
603 605 user_id, api_key, username)
604 606 try:
605 607 dbuser = None
606 608 if user_id:
607 609 dbuser = self.get(user_id)
608 610 elif api_key:
609 611 dbuser = self.get_by_auth_token(api_key)
610 612 elif username:
611 613 dbuser = self.get_by_username(username)
612 614
613 615 if not dbuser:
614 616 log.warning(
615 617 'Unable to lookup user by id:%s api_key:%s username:%s',
616 618 user_id, api_key, username)
617 619 return False
618 620 if not dbuser.active:
619 621 log.debug('User `%s` is inactive, skipping fill data', username)
620 622 return False
621 623
622 624 log.debug('filling user:%s data', dbuser)
623 625
624 626 # TODO: johbo: Think about this and find a clean solution
625 627 user_data = dbuser.get_dict()
626 628 user_data.update(dbuser.get_api_data(include_secrets=True))
627 629
628 630 for k, v in user_data.iteritems():
629 631 # properties of auth user we dont update
630 632 if k not in ['auth_tokens', 'permissions']:
631 633 setattr(auth_user, k, v)
632 634
633 635 # few extras
634 636 setattr(auth_user, 'feed_token', dbuser.feed_token)
635 637 except Exception:
636 638 log.error(traceback.format_exc())
637 639 auth_user.is_authenticated = False
638 640 return False
639 641
640 642 return True
641 643
642 644 def has_perm(self, user, perm):
643 645 perm = self._get_perm(perm)
644 646 user = self._get_user(user)
645 647
646 648 return UserToPerm.query().filter(UserToPerm.user == user)\
647 649 .filter(UserToPerm.permission == perm).scalar() is not None
648 650
649 651 def grant_perm(self, user, perm):
650 652 """
651 653 Grant user global permissions
652 654
653 655 :param user:
654 656 :param perm:
655 657 """
656 658 user = self._get_user(user)
657 659 perm = self._get_perm(perm)
658 660 # if this permission is already granted skip it
659 661 _perm = UserToPerm.query()\
660 662 .filter(UserToPerm.user == user)\
661 663 .filter(UserToPerm.permission == perm)\
662 664 .scalar()
663 665 if _perm:
664 666 return
665 667 new = UserToPerm()
666 668 new.user = user
667 669 new.permission = perm
668 670 self.sa.add(new)
669 671 return new
670 672
671 673 def revoke_perm(self, user, perm):
672 674 """
673 675 Revoke users global permissions
674 676
675 677 :param user:
676 678 :param perm:
677 679 """
678 680 user = self._get_user(user)
679 681 perm = self._get_perm(perm)
680 682
681 683 obj = UserToPerm.query()\
682 684 .filter(UserToPerm.user == user)\
683 685 .filter(UserToPerm.permission == perm)\
684 686 .scalar()
685 687 if obj:
686 688 self.sa.delete(obj)
687 689
688 690 def add_extra_email(self, user, email):
689 691 """
690 692 Adds email address to UserEmailMap
691 693
692 694 :param user:
693 695 :param email:
694 696 """
695 697 from rhodecode.model import forms
696 698 form = forms.UserExtraEmailForm()()
697 699 data = form.to_python({'email': email})
698 700 user = self._get_user(user)
699 701
700 702 obj = UserEmailMap()
701 703 obj.user = user
702 704 obj.email = data['email']
703 705 self.sa.add(obj)
704 706 return obj
705 707
706 708 def delete_extra_email(self, user, email_id):
707 709 """
708 710 Removes email address from UserEmailMap
709 711
710 712 :param user:
711 713 :param email_id:
712 714 """
713 715 user = self._get_user(user)
714 716 obj = UserEmailMap.query().get(email_id)
715 717 if obj:
716 718 self.sa.delete(obj)
717 719
718 720 def parse_ip_range(self, ip_range):
719 721 ip_list = []
720 722 def make_unique(value):
721 723 seen = []
722 724 return [c for c in value if not (c in seen or seen.append(c))]
723 725
724 726 # firsts split by commas
725 727 for ip_range in ip_range.split(','):
726 728 if not ip_range:
727 729 continue
728 730 ip_range = ip_range.strip()
729 731 if '-' in ip_range:
730 732 start_ip, end_ip = ip_range.split('-', 1)
731 733 start_ip = ipaddress.ip_address(start_ip.strip())
732 734 end_ip = ipaddress.ip_address(end_ip.strip())
733 735 parsed_ip_range = []
734 736
735 737 for index in xrange(int(start_ip), int(end_ip) + 1):
736 738 new_ip = ipaddress.ip_address(index)
737 739 parsed_ip_range.append(str(new_ip))
738 740 ip_list.extend(parsed_ip_range)
739 741 else:
740 742 ip_list.append(ip_range)
741 743
742 744 return make_unique(ip_list)
743 745
744 746 def add_extra_ip(self, user, ip, description=None):
745 747 """
746 748 Adds ip address to UserIpMap
747 749
748 750 :param user:
749 751 :param ip:
750 752 """
751 753 from rhodecode.model import forms
752 754 form = forms.UserExtraIpForm()()
753 755 data = form.to_python({'ip': ip})
754 756 user = self._get_user(user)
755 757
756 758 obj = UserIpMap()
757 759 obj.user = user
758 760 obj.ip_addr = data['ip']
759 761 obj.description = description
760 762 self.sa.add(obj)
761 763 return obj
762 764
763 765 def delete_extra_ip(self, user, ip_id):
764 766 """
765 767 Removes ip address from UserIpMap
766 768
767 769 :param user:
768 770 :param ip_id:
769 771 """
770 772 user = self._get_user(user)
771 773 obj = UserIpMap.query().get(ip_id)
772 774 if obj:
773 775 self.sa.delete(obj)
774 776
775 777 def get_accounts_in_creation_order(self, current_user=None):
776 778 """
777 779 Get accounts in order of creation for deactivation for license limits
778 780
779 781 pick currently logged in user, and append to the list in position 0
780 782 pick all super-admins in order of creation date and add it to the list
781 783 pick all other accounts in order of creation and add it to the list.
782 784
783 785 Based on that list, the last accounts can be disabled as they are
784 786 created at the end and don't include any of the super admins as well
785 787 as the current user.
786 788
787 789 :param current_user: optionally current user running this operation
788 790 """
789 791
790 792 if not current_user:
791 793 current_user = get_current_rhodecode_user()
792 794 active_super_admins = [
793 795 x.user_id for x in User.query()
794 796 .filter(User.user_id != current_user.user_id)
795 797 .filter(User.active == true())
796 798 .filter(User.admin == true())
797 799 .order_by(User.created_on.asc())]
798 800
799 801 active_regular_users = [
800 802 x.user_id for x in User.query()
801 803 .filter(User.user_id != current_user.user_id)
802 804 .filter(User.active == true())
803 805 .filter(User.admin == false())
804 806 .order_by(User.created_on.asc())]
805 807
806 808 list_of_accounts = [current_user.user_id]
807 809 list_of_accounts += active_super_admins
808 810 list_of_accounts += active_regular_users
809 811
810 812 return list_of_accounts
811 813
812 814 def deactivate_last_users(self, expected_users):
813 815 """
814 816 Deactivate accounts that are over the license limits.
815 817 Algorithm of which accounts to disabled is based on the formula:
816 818
817 819 Get current user, then super admins in creation order, then regular
818 820 active users in creation order.
819 821
820 822 Using that list we mark all accounts from the end of it as inactive.
821 823 This way we block only latest created accounts.
822 824
823 825 :param expected_users: list of users in special order, we deactivate
824 826 the end N ammoun of users from that list
825 827 """
826 828
827 829 list_of_accounts = self.get_accounts_in_creation_order()
828 830
829 831 for acc_id in list_of_accounts[expected_users + 1:]:
830 832 user = User.get(acc_id)
831 833 log.info('Deactivating account %s for license unlock', user)
832 834 user.active = False
833 835 Session().add(user)
834 836 Session().commit()
835 837
836 838 return
@@ -1,517 +1,517 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 user group model for RhodeCode
24 24 """
25 25
26 26
27 27 import logging
28 28 import traceback
29 29
30 30 from rhodecode.lib.utils2 import safe_str
31 31 from rhodecode.model import BaseModel
32 32 from rhodecode.model.db import UserGroupMember, UserGroup,\
33 33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
34 34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
35 35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
36 36 RepoGroupAssignmentError
37 37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class UserGroupModel(BaseModel):
43 43
44 44 cls = UserGroup
45 45
46 46 def _get_user_group(self, user_group):
47 47 return self._get_instance(UserGroup, user_group,
48 48 callback=UserGroup.get_by_group_name)
49 49
50 50 def _create_default_perms(self, user_group):
51 51 # create default permission
52 52 default_perm = 'usergroup.read'
53 53 def_user = User.get_default_user()
54 54 for p in def_user.user_perms:
55 55 if p.permission.permission_name.startswith('usergroup.'):
56 56 default_perm = p.permission.permission_name
57 57 break
58 58
59 59 user_group_to_perm = UserUserGroupToPerm()
60 60 user_group_to_perm.permission = Permission.get_by_key(default_perm)
61 61
62 62 user_group_to_perm.user_group = user_group
63 63 user_group_to_perm.user_id = def_user.user_id
64 64 return user_group_to_perm
65 65
66 66 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
67 67 perm_deletions=None, check_perms=True, cur_user=None):
68 68 from rhodecode.lib.auth import HasUserGroupPermissionAny
69 69 if not perm_additions:
70 70 perm_additions = []
71 71 if not perm_updates:
72 72 perm_updates = []
73 73 if not perm_deletions:
74 74 perm_deletions = []
75 75
76 76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
77 77
78 78 # update permissions
79 79 for member_id, perm, member_type in perm_updates:
80 80 member_id = int(member_id)
81 81 if member_type == 'user':
82 82 # this updates existing one
83 83 self.grant_user_permission(
84 84 user_group=user_group, user=member_id, perm=perm
85 85 )
86 86 else:
87 87 # check if we have permissions to alter this usergroup
88 88 member_name = UserGroup.get(member_id).users_group_name
89 89 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
90 90 self.grant_user_group_permission(
91 91 target_user_group=user_group, user_group=member_id, perm=perm
92 92 )
93 93
94 94 # set new permissions
95 95 for member_id, perm, member_type in perm_additions:
96 96 member_id = int(member_id)
97 97 if member_type == 'user':
98 98 self.grant_user_permission(
99 99 user_group=user_group, user=member_id, perm=perm
100 100 )
101 101 else:
102 102 # check if we have permissions to alter this usergroup
103 103 member_name = UserGroup.get(member_id).users_group_name
104 104 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
105 105 self.grant_user_group_permission(
106 106 target_user_group=user_group, user_group=member_id, perm=perm
107 107 )
108 108
109 109 # delete permissions
110 110 for member_id, perm, member_type in perm_deletions:
111 111 member_id = int(member_id)
112 112 if member_type == 'user':
113 113 self.revoke_user_permission(user_group=user_group, user=member_id)
114 114 else:
115 115 #check if we have permissions to alter this usergroup
116 116 member_name = UserGroup.get(member_id).users_group_name
117 117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
118 118 self.revoke_user_group_permission(
119 119 target_user_group=user_group, user_group=member_id
120 120 )
121 121
122 122 def get(self, user_group_id, cache=False):
123 123 return UserGroup.get(user_group_id)
124 124
125 125 def get_group(self, user_group):
126 126 return self._get_user_group(user_group)
127 127
128 128 def get_by_name(self, name, cache=False, case_insensitive=False):
129 129 return UserGroup.get_by_group_name(name, cache, case_insensitive)
130 130
131 131 def create(self, name, description, owner, active=True, group_data=None):
132 132 try:
133 133 new_user_group = UserGroup()
134 134 new_user_group.user = self._get_user(owner)
135 135 new_user_group.users_group_name = name
136 136 new_user_group.user_group_description = description
137 137 new_user_group.users_group_active = active
138 138 if group_data:
139 139 new_user_group.group_data = group_data
140 140 self.sa.add(new_user_group)
141 141 perm_obj = self._create_default_perms(new_user_group)
142 142 self.sa.add(perm_obj)
143 143
144 144 self.grant_user_permission(user_group=new_user_group,
145 145 user=owner, perm='usergroup.admin')
146 146
147 147 return new_user_group
148 148 except Exception:
149 149 log.error(traceback.format_exc())
150 150 raise
151 151
152 152 def _get_memberships_for_user_ids(self, user_group, user_id_list):
153 153 members = []
154 154 for user_id in user_id_list:
155 155 member = self._get_membership(user_group.users_group_id, user_id)
156 156 members.append(member)
157 157 return members
158 158
159 159 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
160 160 current_members = user_group.members or []
161 161 current_members_ids = [m.user.user_id for m in current_members]
162 162
163 163 added_members = [
164 164 user_id for user_id in user_id_list
165 165 if user_id not in current_members_ids]
166 166 if user_id_list == []:
167 167 # all members were deleted
168 168 deleted_members = current_members_ids
169 169 else:
170 170 deleted_members = [
171 171 user_id for user_id in current_members_ids
172 172 if user_id not in user_id_list]
173 173
174 174 return (added_members, deleted_members)
175 175
176 176 def _set_users_as_members(self, user_group, user_ids):
177 177 user_group.members = []
178 178 self.sa.flush()
179 179 members = self._get_memberships_for_user_ids(
180 180 user_group, user_ids)
181 181 user_group.members = members
182 182 self.sa.add(user_group)
183 183
184 184 def _update_members_from_user_ids(self, user_group, user_ids):
185 185 added, removed = self._get_added_and_removed_user_ids(
186 186 user_group, user_ids)
187 187 self._set_users_as_members(user_group, user_ids)
188 188 self._log_user_changes('added to', user_group, added)
189 189 self._log_user_changes('removed from', user_group, removed)
190 190
191 191 def _clean_members_data(self, members_data):
192 192 # TODO: anderson: this should be in the form validation but I couldn't
193 193 # make it work there as it conflicts with the other validator
194 194 if not members_data:
195 195 members_data = []
196 196
197 197 if isinstance(members_data, basestring):
198 198 new_members = [members_data]
199 199 else:
200 200 new_members = members_data
201 201
202 202 new_members = [int(uid) for uid in new_members]
203 203 return new_members
204 204
205 205 def update(self, user_group, form_data):
206 206 user_group = self._get_user_group(user_group)
207 207 if 'users_group_name' in form_data:
208 208 user_group.users_group_name = form_data['users_group_name']
209 209 if 'users_group_active' in form_data:
210 210 user_group.users_group_active = form_data['users_group_active']
211 211 if 'user_group_description' in form_data:
212 212 user_group.user_group_description = form_data[
213 213 'user_group_description']
214 214
215 215 # handle owner change
216 216 if 'user' in form_data:
217 217 owner = form_data['user']
218 218 if isinstance(owner, basestring):
219 219 owner = User.get_by_username(form_data['user'])
220 220
221 221 if not isinstance(owner, User):
222 222 raise ValueError(
223 223 'invalid owner for user group: %s' % form_data['user'])
224 224
225 225 user_group.user = owner
226 226
227 227 if 'users_group_members' in form_data:
228 228 members_id_list = self._clean_members_data(
229 229 form_data['users_group_members'])
230 230 self._update_members_from_user_ids(user_group, members_id_list)
231 231
232 232 self.sa.add(user_group)
233 233
234 234 def delete(self, user_group, force=False):
235 235 """
236 236 Deletes repository group, unless force flag is used
237 237 raises exception if there are members in that group, else deletes
238 238 group and users
239 239
240 240 :param user_group:
241 241 :param force:
242 242 """
243 243 user_group = self._get_user_group(user_group)
244 244 try:
245 245 # check if this group is not assigned to repo
246 246 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
247 247 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
248 248 # check if this group is not assigned to repo
249 249 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
250 250 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
251 251
252 252 if (assigned_to_repo or assigned_to_repo_group) and not force:
253 253 assigned = ','.join(map(safe_str,
254 254 assigned_to_repo+assigned_to_repo_group))
255 255
256 256 raise UserGroupAssignedException(
257 257 'UserGroup assigned to %s' % (assigned,))
258 258 self.sa.delete(user_group)
259 259 except Exception:
260 260 log.error(traceback.format_exc())
261 261 raise
262 262
263 263 def _log_user_changes(self, action, user_group, user_or_users):
264 264 users = user_or_users
265 265 if not isinstance(users, (list, tuple)):
266 266 users = [users]
267 267 rhodecode_user = get_current_rhodecode_user()
268 268 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
269 269 group_name = user_group.users_group_name
270 270
271 271 for user_or_user_id in users:
272 272 user = self._get_user(user_or_user_id)
273 273 log_text = 'User {user} {action} {group}'.format(
274 274 action=action, user=user.username, group=group_name)
275 275 log.info('Logging action: {0} by {1} ip:{2}'.format(
276 276 log_text, rhodecode_user, ipaddr))
277 277
278 278 def _find_user_in_group(self, user, user_group):
279 279 user_group_member = None
280 280 for m in user_group.members:
281 281 if m.user_id == user.user_id:
282 282 # Found this user's membership row
283 283 user_group_member = m
284 284 break
285 285
286 286 return user_group_member
287 287
288 288 def _get_membership(self, user_group_id, user_id):
289 289 user_group_member = UserGroupMember(user_group_id, user_id)
290 290 return user_group_member
291 291
292 292 def add_user_to_group(self, user_group, user):
293 293 user_group = self._get_user_group(user_group)
294 294 user = self._get_user(user)
295 295 user_member = self._find_user_in_group(user, user_group)
296 296 if user_member:
297 297 # user already in the group, skip
298 298 return True
299 299
300 300 member = self._get_membership(
301 301 user_group.users_group_id, user.user_id)
302 302 user_group.members.append(member)
303 303
304 304 try:
305 305 self.sa.add(member)
306 306 except Exception:
307 307 # what could go wrong here?
308 308 log.error(traceback.format_exc())
309 309 raise
310 310
311 311 self._log_user_changes('added to', user_group, user)
312 312 return member
313 313
314 314 def remove_user_from_group(self, user_group, user):
315 315 user_group = self._get_user_group(user_group)
316 316 user = self._get_user(user)
317 317 user_group_member = self._find_user_in_group(user, user_group)
318 318
319 319 if not user_group_member:
320 320 # User isn't in that group
321 321 return False
322 322
323 323 try:
324 324 self.sa.delete(user_group_member)
325 325 except Exception:
326 326 log.error(traceback.format_exc())
327 327 raise
328 328
329 329 self._log_user_changes('removed from', user_group, user)
330 330 return True
331 331
332 332 def has_perm(self, user_group, perm):
333 333 user_group = self._get_user_group(user_group)
334 334 perm = self._get_perm(perm)
335 335
336 336 return UserGroupToPerm.query()\
337 337 .filter(UserGroupToPerm.users_group == user_group)\
338 338 .filter(UserGroupToPerm.permission == perm).scalar() is not None
339 339
340 340 def grant_perm(self, user_group, perm):
341 341 user_group = self._get_user_group(user_group)
342 342 perm = self._get_perm(perm)
343 343
344 344 # if this permission is already granted skip it
345 345 _perm = UserGroupToPerm.query()\
346 346 .filter(UserGroupToPerm.users_group == user_group)\
347 347 .filter(UserGroupToPerm.permission == perm)\
348 348 .scalar()
349 349 if _perm:
350 350 return
351 351
352 352 new = UserGroupToPerm()
353 353 new.users_group = user_group
354 354 new.permission = perm
355 355 self.sa.add(new)
356 356 return new
357 357
358 358 def revoke_perm(self, user_group, perm):
359 359 user_group = self._get_user_group(user_group)
360 360 perm = self._get_perm(perm)
361 361
362 362 obj = UserGroupToPerm.query()\
363 363 .filter(UserGroupToPerm.users_group == user_group)\
364 364 .filter(UserGroupToPerm.permission == perm).scalar()
365 365 if obj:
366 366 self.sa.delete(obj)
367 367
368 368 def grant_user_permission(self, user_group, user, perm):
369 369 """
370 370 Grant permission for user on given user group, or update
371 371 existing one if found
372 372
373 373 :param user_group: Instance of UserGroup, users_group_id,
374 374 or users_group_name
375 375 :param user: Instance of User, user_id or username
376 376 :param perm: Instance of Permission, or permission_name
377 377 """
378 378
379 379 user_group = self._get_user_group(user_group)
380 380 user = self._get_user(user)
381 381 permission = self._get_perm(perm)
382 382
383 383 # check if we have that permission already
384 384 obj = self.sa.query(UserUserGroupToPerm)\
385 385 .filter(UserUserGroupToPerm.user == user)\
386 386 .filter(UserUserGroupToPerm.user_group == user_group)\
387 387 .scalar()
388 388 if obj is None:
389 389 # create new !
390 390 obj = UserUserGroupToPerm()
391 391 obj.user_group = user_group
392 392 obj.user = user
393 393 obj.permission = permission
394 394 self.sa.add(obj)
395 395 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
396 396 action_logger_generic(
397 397 'granted permission: {} to user: {} on usergroup: {}'.format(
398 398 perm, user, user_group), namespace='security.usergroup')
399 399
400 400 return obj
401 401
402 402 def revoke_user_permission(self, user_group, user):
403 403 """
404 404 Revoke permission for user on given user group
405 405
406 406 :param user_group: Instance of UserGroup, users_group_id,
407 407 or users_group name
408 408 :param user: Instance of User, user_id or username
409 409 """
410 410
411 411 user_group = self._get_user_group(user_group)
412 412 user = self._get_user(user)
413 413
414 414 obj = self.sa.query(UserUserGroupToPerm)\
415 415 .filter(UserUserGroupToPerm.user == user)\
416 416 .filter(UserUserGroupToPerm.user_group == user_group)\
417 417 .scalar()
418 418 if obj:
419 419 self.sa.delete(obj)
420 420 log.debug('Revoked perm on %s on %s', user_group, user)
421 421 action_logger_generic(
422 422 'revoked permission from user: {} on usergroup: {}'.format(
423 423 user, user_group), namespace='security.usergroup')
424 424
425 425 def grant_user_group_permission(self, target_user_group, user_group, perm):
426 426 """
427 427 Grant user group permission for given target_user_group
428 428
429 429 :param target_user_group:
430 430 :param user_group:
431 431 :param perm:
432 432 """
433 433 target_user_group = self._get_user_group(target_user_group)
434 434 user_group = self._get_user_group(user_group)
435 435 permission = self._get_perm(perm)
436 436 # forbid assigning same user group to itself
437 437 if target_user_group == user_group:
438 438 raise RepoGroupAssignmentError('target repo:%s cannot be '
439 439 'assigned to itself' % target_user_group)
440 440
441 441 # check if we have that permission already
442 442 obj = self.sa.query(UserGroupUserGroupToPerm)\
443 443 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
444 444 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
445 445 .scalar()
446 446 if obj is None:
447 447 # create new !
448 448 obj = UserGroupUserGroupToPerm()
449 449 obj.user_group = user_group
450 450 obj.target_user_group = target_user_group
451 451 obj.permission = permission
452 452 self.sa.add(obj)
453 453 log.debug(
454 454 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
455 455 action_logger_generic(
456 456 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
457 457 perm, user_group, target_user_group),
458 458 namespace='security.usergroup')
459 459
460 460 return obj
461 461
462 462 def revoke_user_group_permission(self, target_user_group, user_group):
463 463 """
464 464 Revoke user group permission for given target_user_group
465 465
466 466 :param target_user_group:
467 467 :param user_group:
468 468 """
469 469 target_user_group = self._get_user_group(target_user_group)
470 470 user_group = self._get_user_group(user_group)
471 471
472 472 obj = self.sa.query(UserGroupUserGroupToPerm)\
473 473 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
474 474 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
475 475 .scalar()
476 476 if obj:
477 477 self.sa.delete(obj)
478 478 log.debug(
479 479 'Revoked perm on %s on %s', target_user_group, user_group)
480 480 action_logger_generic(
481 481 'revoked permission from usergroup: {} on usergroup: {}'.format(
482 482 user_group, target_user_group),
483 483 namespace='security.repogroup')
484 484
485 485 def enforce_groups(self, user, groups, extern_type=None):
486 486 user = self._get_user(user)
487 487 log.debug('Enforcing groups %s on user %s', groups, user)
488 488 current_groups = user.group_member
489 489 # find the external created groups
490 490 externals = [x.users_group for x in current_groups
491 491 if 'extern_type' in x.users_group.group_data]
492 492
493 493 # calculate from what groups user should be removed
494 494 # externals that are not in groups
495 495 for gr in externals:
496 496 if gr.users_group_name not in groups:
497 497 log.debug('Removing user %s from user group %s', user, gr)
498 498 self.remove_user_from_group(gr, user)
499 499
500 500 # now we calculate in which groups user should be == groups params
501 owner = User.get_first_admin().username
501 owner = User.get_first_super_admin().username
502 502 for gr in set(groups):
503 503 existing_group = UserGroup.get_by_group_name(gr)
504 504 if not existing_group:
505 505 desc = 'Automatically created from plugin:%s' % extern_type
506 506 # we use first admin account to set the owner of the group
507 507 existing_group = UserGroupModel().create(gr, desc, owner,
508 508 group_data={'extern_type': extern_type})
509 509
510 510 # we can only add users to special groups created via plugins
511 511 managed = 'extern_type' in existing_group.group_data
512 512 if managed:
513 513 log.debug('Adding user %s to user group %s', user, gr)
514 514 UserGroupModel().add_user_to_group(existing_group, user)
515 515 else:
516 516 log.debug('Skipping addition to group %s since it is '
517 517 'not managed by auth plugins' % gr)
@@ -1,1120 +1,1125 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Set of generic validators
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 from collections import defaultdict
29 29
30 30 import formencode
31 31 import ipaddress
32 32 from formencode.validators import (
33 33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 35 )
36 36 from pylons.i18n.translation import _
37 37 from sqlalchemy.sql.expression import true
38 38 from sqlalchemy.util import OrderedSet
39 39 from webhelpers.pylonslib.secure_form import authentication_token
40 40
41 41 from rhodecode.authentication import (
42 42 legacy_plugin_prefix, _import_legacy_plugin)
43 43 from rhodecode.authentication.base import loadplugin
44 44 from rhodecode.config.routing import ADMIN_PREFIX
45 45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
46 46 from rhodecode.lib.utils import repo_name_slug, make_db_config
47 47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5
48 48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
49 49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
50 50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
51 51 from rhodecode.model.db import (
52 52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54
55 55 # silence warnings and pylint
56 56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
57 57 NotEmpty, IPAddress, CIDR, String, FancyValidator
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class _Missing(object):
63 63 pass
64 64
65 65 Missing = _Missing()
66 66
67 67
68 68 class StateObj(object):
69 69 """
70 70 this is needed to translate the messages using _() in validators
71 71 """
72 72 _ = staticmethod(_)
73 73
74 74
75 75 def M(self, key, state=None, **kwargs):
76 76 """
77 77 returns string from self.message based on given key,
78 78 passed kw params are used to substitute %(named)s params inside
79 79 translated strings
80 80
81 81 :param msg:
82 82 :param state:
83 83 """
84 84 if state is None:
85 85 state = StateObj()
86 86 else:
87 87 state._ = staticmethod(_)
88 88 # inject validator into state object
89 89 return self.message(key, state, **kwargs)
90 90
91 91
92 92 def UniqueList(convert=None):
93 93 class _UniqueList(formencode.FancyValidator):
94 94 """
95 95 Unique List !
96 96 """
97 97 messages = {
98 98 'empty': _(u'Value cannot be an empty list'),
99 99 'missing_value': _(u'Value cannot be an empty list'),
100 100 }
101 101
102 102 def _to_python(self, value, state):
103 103 ret_val = []
104 104
105 105 def make_unique(value):
106 106 seen = []
107 107 return [c for c in value if not (c in seen or seen.append(c))]
108 108
109 109 if isinstance(value, list):
110 110 ret_val = make_unique(value)
111 111 elif isinstance(value, set):
112 112 ret_val = make_unique(list(value))
113 113 elif isinstance(value, tuple):
114 114 ret_val = make_unique(list(value))
115 115 elif value is None:
116 116 ret_val = []
117 117 else:
118 118 ret_val = [value]
119 119
120 120 if convert:
121 121 ret_val = map(convert, ret_val)
122 122 return ret_val
123 123
124 124 def empty_value(self, value):
125 125 return []
126 126
127 127 return _UniqueList
128 128
129 129
130 130 def UniqueListFromString():
131 131 class _UniqueListFromString(UniqueList()):
132 132 def _to_python(self, value, state):
133 133 if isinstance(value, basestring):
134 134 value = aslist(value, ',')
135 135 return super(_UniqueListFromString, self)._to_python(value, state)
136 136 return _UniqueListFromString
137 137
138 138
139 139 def ValidSvnPattern(section, repo_name=None):
140 140 class _validator(formencode.validators.FancyValidator):
141 141 messages = {
142 142 'pattern_exists': _(u'Pattern already exists'),
143 143 }
144 144
145 145 def validate_python(self, value, state):
146 146 if not value:
147 147 return
148 148 model = VcsSettingsModel(repo=repo_name)
149 149 ui_settings = model.get_svn_patterns(section=section)
150 150 for entry in ui_settings:
151 151 if value == entry.value:
152 152 msg = M(self, 'pattern_exists', state)
153 153 raise formencode.Invalid(msg, value, state)
154 154 return _validator
155 155
156 156
157 157 def ValidUsername(edit=False, old_data={}):
158 158 class _validator(formencode.validators.FancyValidator):
159 159 messages = {
160 160 'username_exists': _(u'Username "%(username)s" already exists'),
161 161 'system_invalid_username':
162 162 _(u'Username "%(username)s" is forbidden'),
163 163 'invalid_username':
164 164 _(u'Username may only contain alphanumeric characters '
165 165 u'underscores, periods or dashes and must begin with '
166 166 u'alphanumeric character or underscore')
167 167 }
168 168
169 169 def validate_python(self, value, state):
170 170 if value in ['default', 'new_user']:
171 171 msg = M(self, 'system_invalid_username', state, username=value)
172 172 raise formencode.Invalid(msg, value, state)
173 173 # check if user is unique
174 174 old_un = None
175 175 if edit:
176 176 old_un = User.get(old_data.get('user_id')).username
177 177
178 178 if old_un != value or not edit:
179 179 if User.get_by_username(value, case_insensitive=True):
180 180 msg = M(self, 'username_exists', state, username=value)
181 181 raise formencode.Invalid(msg, value, state)
182 182
183 183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
184 184 is None):
185 185 msg = M(self, 'invalid_username', state)
186 186 raise formencode.Invalid(msg, value, state)
187 187 return _validator
188 188
189 189
190 190 def ValidRegex(msg=None):
191 191 class _validator(formencode.validators.Regex):
192 192 messages = {'invalid': msg or _(u'The input is not valid')}
193 193 return _validator
194 194
195 195
196 def ValidRepoUser():
196 def ValidRepoUser(allow_disabled=False):
197 197 class _validator(formencode.validators.FancyValidator):
198 198 messages = {
199 'invalid_username': _(u'Username %(username)s is not valid')
199 'invalid_username': _(u'Username %(username)s is not valid'),
200 'disabled_username': _(u'Username %(username)s is disabled')
200 201 }
201 202
202 203 def validate_python(self, value, state):
203 204 try:
204 User.query().filter(User.active == true())\
205 .filter(User.username == value).one()
205 user = User.query().filter(User.username == value).one()
206 206 except Exception:
207 207 msg = M(self, 'invalid_username', state, username=value)
208 208 raise formencode.Invalid(
209 209 msg, value, state, error_dict={'username': msg}
210 210 )
211 if user and (not allow_disabled and not user.active):
212 msg = M(self, 'disabled_username', state, username=value)
213 raise formencode.Invalid(
214 msg, value, state, error_dict={'username': msg}
215 )
211 216
212 217 return _validator
213 218
214 219
215 220 def ValidUserGroup(edit=False, old_data={}):
216 221 class _validator(formencode.validators.FancyValidator):
217 222 messages = {
218 223 'invalid_group': _(u'Invalid user group name'),
219 224 'group_exist': _(u'User group "%(usergroup)s" already exists'),
220 225 'invalid_usergroup_name':
221 226 _(u'user group name may only contain alphanumeric '
222 227 u'characters underscores, periods or dashes and must begin '
223 228 u'with alphanumeric character')
224 229 }
225 230
226 231 def validate_python(self, value, state):
227 232 if value in ['default']:
228 233 msg = M(self, 'invalid_group', state)
229 234 raise formencode.Invalid(
230 235 msg, value, state, error_dict={'users_group_name': msg}
231 236 )
232 237 # check if group is unique
233 238 old_ugname = None
234 239 if edit:
235 240 old_id = old_data.get('users_group_id')
236 241 old_ugname = UserGroup.get(old_id).users_group_name
237 242
238 243 if old_ugname != value or not edit:
239 244 is_existing_group = UserGroup.get_by_group_name(
240 245 value, case_insensitive=True)
241 246 if is_existing_group:
242 247 msg = M(self, 'group_exist', state, usergroup=value)
243 248 raise formencode.Invalid(
244 249 msg, value, state, error_dict={'users_group_name': msg}
245 250 )
246 251
247 252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
248 253 msg = M(self, 'invalid_usergroup_name', state)
249 254 raise formencode.Invalid(
250 255 msg, value, state, error_dict={'users_group_name': msg}
251 256 )
252 257
253 258 return _validator
254 259
255 260
256 261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
257 262 class _validator(formencode.validators.FancyValidator):
258 263 messages = {
259 264 'group_parent_id': _(u'Cannot assign this group as parent'),
260 265 'group_exists': _(u'Group "%(group_name)s" already exists'),
261 266 'repo_exists': _(u'Repository with name "%(group_name)s" '
262 267 u'already exists'),
263 268 'permission_denied': _(u"no permission to store repository group"
264 269 u"in this location"),
265 270 'permission_denied_root': _(
266 271 u"no permission to store repository group "
267 272 u"in root location")
268 273 }
269 274
270 275 def _to_python(self, value, state):
271 276 group_name = repo_name_slug(value.get('group_name', ''))
272 277 group_parent_id = safe_int(value.get('group_parent_id'))
273 278 gr = RepoGroup.get(group_parent_id)
274 279 if gr:
275 280 parent_group_path = gr.full_path
276 281 # value needs to be aware of group name in order to check
277 282 # db key This is an actual just the name to store in the
278 283 # database
279 284 group_name_full = (
280 285 parent_group_path + RepoGroup.url_sep() + group_name)
281 286 else:
282 287 group_name_full = group_name
283 288
284 289 value['group_name'] = group_name
285 290 value['group_name_full'] = group_name_full
286 291 value['group_parent_id'] = group_parent_id
287 292 return value
288 293
289 294 def validate_python(self, value, state):
290 295
291 296 old_group_name = None
292 297 group_name = value.get('group_name')
293 298 group_name_full = value.get('group_name_full')
294 299 group_parent_id = safe_int(value.get('group_parent_id'))
295 300 if group_parent_id == -1:
296 301 group_parent_id = None
297 302
298 303 group_obj = RepoGroup.get(old_data.get('group_id'))
299 304 parent_group_changed = False
300 305 if edit:
301 306 old_group_name = group_obj.group_name
302 307 old_group_parent_id = group_obj.group_parent_id
303 308
304 309 if group_parent_id != old_group_parent_id:
305 310 parent_group_changed = True
306 311
307 312 # TODO: mikhail: the following if statement is not reached
308 313 # since group_parent_id's OneOf validation fails before.
309 314 # Can be removed.
310 315
311 316 # check against setting a parent of self
312 317 parent_of_self = (
313 318 old_data['group_id'] == group_parent_id
314 319 if group_parent_id else False
315 320 )
316 321 if parent_of_self:
317 322 msg = M(self, 'group_parent_id', state)
318 323 raise formencode.Invalid(
319 324 msg, value, state, error_dict={'group_parent_id': msg}
320 325 )
321 326
322 327 # group we're moving current group inside
323 328 child_group = None
324 329 if group_parent_id:
325 330 child_group = RepoGroup.query().filter(
326 331 RepoGroup.group_id == group_parent_id).scalar()
327 332
328 333 # do a special check that we cannot move a group to one of
329 334 # it's children
330 335 if edit and child_group:
331 336 parents = [x.group_id for x in child_group.parents]
332 337 move_to_children = old_data['group_id'] in parents
333 338 if move_to_children:
334 339 msg = M(self, 'group_parent_id', state)
335 340 raise formencode.Invalid(
336 341 msg, value, state, error_dict={'group_parent_id': msg})
337 342
338 343 # Check if we have permission to store in the parent.
339 344 # Only check if the parent group changed.
340 345 if parent_group_changed:
341 346 if child_group is None:
342 347 if not can_create_in_root:
343 348 msg = M(self, 'permission_denied_root', state)
344 349 raise formencode.Invalid(
345 350 msg, value, state,
346 351 error_dict={'group_parent_id': msg})
347 352 else:
348 353 valid = HasRepoGroupPermissionAny('group.admin')
349 354 forbidden = not valid(
350 355 child_group.group_name, 'can create group validator')
351 356 if forbidden:
352 357 msg = M(self, 'permission_denied', state)
353 358 raise formencode.Invalid(
354 359 msg, value, state,
355 360 error_dict={'group_parent_id': msg})
356 361
357 362 # if we change the name or it's new group, check for existing names
358 363 # or repositories with the same name
359 364 if old_group_name != group_name_full or not edit:
360 365 # check group
361 366 gr = RepoGroup.get_by_group_name(group_name_full)
362 367 if gr:
363 368 msg = M(self, 'group_exists', state, group_name=group_name)
364 369 raise formencode.Invalid(
365 370 msg, value, state, error_dict={'group_name': msg})
366 371
367 372 # check for same repo
368 373 repo = Repository.get_by_repo_name(group_name_full)
369 374 if repo:
370 375 msg = M(self, 'repo_exists', state, group_name=group_name)
371 376 raise formencode.Invalid(
372 377 msg, value, state, error_dict={'group_name': msg})
373 378
374 379 return _validator
375 380
376 381
377 382 def ValidPassword():
378 383 class _validator(formencode.validators.FancyValidator):
379 384 messages = {
380 385 'invalid_password':
381 386 _(u'Invalid characters (non-ascii) in password')
382 387 }
383 388
384 389 def validate_python(self, value, state):
385 390 try:
386 391 (value or '').decode('ascii')
387 392 except UnicodeError:
388 393 msg = M(self, 'invalid_password', state)
389 394 raise formencode.Invalid(msg, value, state,)
390 395 return _validator
391 396
392 397
393 398 def ValidOldPassword(username):
394 399 class _validator(formencode.validators.FancyValidator):
395 400 messages = {
396 401 'invalid_password': _(u'Invalid old password')
397 402 }
398 403
399 404 def validate_python(self, value, state):
400 405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
401 406 if not authenticate(username, value, '', HTTP_TYPE):
402 407 msg = M(self, 'invalid_password', state)
403 408 raise formencode.Invalid(
404 409 msg, value, state, error_dict={'current_password': msg}
405 410 )
406 411 return _validator
407 412
408 413
409 414 def ValidPasswordsMatch(
410 415 passwd='new_password', passwd_confirmation='password_confirmation'):
411 416 class _validator(formencode.validators.FancyValidator):
412 417 messages = {
413 418 'password_mismatch': _(u'Passwords do not match'),
414 419 }
415 420
416 421 def validate_python(self, value, state):
417 422
418 423 pass_val = value.get('password') or value.get(passwd)
419 424 if pass_val != value[passwd_confirmation]:
420 425 msg = M(self, 'password_mismatch', state)
421 426 raise formencode.Invalid(
422 427 msg, value, state,
423 428 error_dict={passwd: msg, passwd_confirmation: msg}
424 429 )
425 430 return _validator
426 431
427 432
428 433 def ValidAuth():
429 434 class _validator(formencode.validators.FancyValidator):
430 435 messages = {
431 436 'invalid_password': _(u'invalid password'),
432 437 'invalid_username': _(u'invalid user name'),
433 438 'disabled_account': _(u'Your account is disabled')
434 439 }
435 440
436 441 def validate_python(self, value, state):
437 442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
438 443
439 444 password = value['password']
440 445 username = value['username']
441 446
442 447 if not authenticate(username, password, '', HTTP_TYPE,
443 448 skip_missing=True):
444 449 user = User.get_by_username(username)
445 450 if user and not user.active:
446 451 log.warning('user %s is disabled', username)
447 452 msg = M(self, 'disabled_account', state)
448 453 raise formencode.Invalid(
449 454 msg, value, state, error_dict={'username': msg}
450 455 )
451 456 else:
452 457 log.warning('user `%s` failed to authenticate', username)
453 458 msg = M(self, 'invalid_username', state)
454 459 msg2 = M(self, 'invalid_password', state)
455 460 raise formencode.Invalid(
456 461 msg, value, state,
457 462 error_dict={'username': msg, 'password': msg2}
458 463 )
459 464 return _validator
460 465
461 466
462 467 def ValidAuthToken():
463 468 class _validator(formencode.validators.FancyValidator):
464 469 messages = {
465 470 'invalid_token': _(u'Token mismatch')
466 471 }
467 472
468 473 def validate_python(self, value, state):
469 474 if value != authentication_token():
470 475 msg = M(self, 'invalid_token', state)
471 476 raise formencode.Invalid(msg, value, state)
472 477 return _validator
473 478
474 479
475 480 def ValidRepoName(edit=False, old_data={}):
476 481 class _validator(formencode.validators.FancyValidator):
477 482 messages = {
478 483 'invalid_repo_name':
479 484 _(u'Repository name %(repo)s is disallowed'),
480 485 # top level
481 486 'repository_exists': _(u'Repository with name %(repo)s '
482 487 u'already exists'),
483 488 'group_exists': _(u'Repository group with name "%(repo)s" '
484 489 u'already exists'),
485 490 # inside a group
486 491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
487 492 u'exists in group "%(group)s"'),
488 493 'group_in_group_exists': _(
489 494 u'Repository group with name "%(repo)s" '
490 495 u'exists in group "%(group)s"'),
491 496 }
492 497
493 498 def _to_python(self, value, state):
494 499 repo_name = repo_name_slug(value.get('repo_name', ''))
495 500 repo_group = value.get('repo_group')
496 501 if repo_group:
497 502 gr = RepoGroup.get(repo_group)
498 503 group_path = gr.full_path
499 504 group_name = gr.group_name
500 505 # value needs to be aware of group name in order to check
501 506 # db key This is an actual just the name to store in the
502 507 # database
503 508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
504 509 else:
505 510 group_name = group_path = ''
506 511 repo_name_full = repo_name
507 512
508 513 value['repo_name'] = repo_name
509 514 value['repo_name_full'] = repo_name_full
510 515 value['group_path'] = group_path
511 516 value['group_name'] = group_name
512 517 return value
513 518
514 519 def validate_python(self, value, state):
515 520
516 521 repo_name = value.get('repo_name')
517 522 repo_name_full = value.get('repo_name_full')
518 523 group_path = value.get('group_path')
519 524 group_name = value.get('group_name')
520 525
521 526 if repo_name in [ADMIN_PREFIX, '']:
522 527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
523 528 raise formencode.Invalid(
524 529 msg, value, state, error_dict={'repo_name': msg})
525 530
526 531 rename = old_data.get('repo_name') != repo_name_full
527 532 create = not edit
528 533 if rename or create:
529 534
530 535 if group_path:
531 536 if Repository.get_by_repo_name(repo_name_full):
532 537 msg = M(self, 'repository_in_group_exists', state,
533 538 repo=repo_name, group=group_name)
534 539 raise formencode.Invalid(
535 540 msg, value, state, error_dict={'repo_name': msg})
536 541 if RepoGroup.get_by_group_name(repo_name_full):
537 542 msg = M(self, 'group_in_group_exists', state,
538 543 repo=repo_name, group=group_name)
539 544 raise formencode.Invalid(
540 545 msg, value, state, error_dict={'repo_name': msg})
541 546 else:
542 547 if RepoGroup.get_by_group_name(repo_name_full):
543 548 msg = M(self, 'group_exists', state, repo=repo_name)
544 549 raise formencode.Invalid(
545 550 msg, value, state, error_dict={'repo_name': msg})
546 551
547 552 if Repository.get_by_repo_name(repo_name_full):
548 553 msg = M(
549 554 self, 'repository_exists', state, repo=repo_name)
550 555 raise formencode.Invalid(
551 556 msg, value, state, error_dict={'repo_name': msg})
552 557 return value
553 558 return _validator
554 559
555 560
556 561 def ValidForkName(*args, **kwargs):
557 562 return ValidRepoName(*args, **kwargs)
558 563
559 564
560 565 def SlugifyName():
561 566 class _validator(formencode.validators.FancyValidator):
562 567
563 568 def _to_python(self, value, state):
564 569 return repo_name_slug(value)
565 570
566 571 def validate_python(self, value, state):
567 572 pass
568 573
569 574 return _validator
570 575
571 576
572 577 def ValidCloneUri():
573 578 class InvalidCloneUrl(Exception):
574 579 allowed_prefixes = ()
575 580
576 581 def url_handler(repo_type, url):
577 582 config = make_db_config(clear_session=False)
578 583 if repo_type == 'hg':
579 584 allowed_prefixes = ('http', 'svn+http', 'git+http')
580 585
581 586 if 'http' in url[:4]:
582 587 # initially check if it's at least the proper URL
583 588 # or does it pass basic auth
584 589 MercurialRepository.check_url(url, config)
585 590 elif 'svn+http' in url[:8]: # svn->hg import
586 591 SubversionRepository.check_url(url, config)
587 592 elif 'git+http' in url[:8]: # git->hg import
588 593 raise NotImplementedError()
589 594 else:
590 595 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
591 596 'Allowed url must start with one of %s'
592 597 % (url, ','.join(allowed_prefixes)))
593 598 exc.allowed_prefixes = allowed_prefixes
594 599 raise exc
595 600
596 601 elif repo_type == 'git':
597 602 allowed_prefixes = ('http', 'svn+http', 'hg+http')
598 603 if 'http' in url[:4]:
599 604 # initially check if it's at least the proper URL
600 605 # or does it pass basic auth
601 606 GitRepository.check_url(url, config)
602 607 elif 'svn+http' in url[:8]: # svn->git import
603 608 raise NotImplementedError()
604 609 elif 'hg+http' in url[:8]: # hg->git import
605 610 raise NotImplementedError()
606 611 else:
607 612 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
608 613 'Allowed url must start with one of %s'
609 614 % (url, ','.join(allowed_prefixes)))
610 615 exc.allowed_prefixes = allowed_prefixes
611 616 raise exc
612 617
613 618 class _validator(formencode.validators.FancyValidator):
614 619 messages = {
615 620 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
616 621 'invalid_clone_uri': _(
617 622 u'Invalid clone url, provide a valid clone '
618 623 u'url starting with one of %(allowed_prefixes)s')
619 624 }
620 625
621 626 def validate_python(self, value, state):
622 627 repo_type = value.get('repo_type')
623 628 url = value.get('clone_uri')
624 629
625 630 if url:
626 631 try:
627 632 url_handler(repo_type, url)
628 633 except InvalidCloneUrl as e:
629 634 log.warning(e)
630 635 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
631 636 allowed_prefixes=','.join(e.allowed_prefixes))
632 637 raise formencode.Invalid(msg, value, state,
633 638 error_dict={'clone_uri': msg})
634 639 except Exception:
635 640 log.exception('Url validation failed')
636 641 msg = M(self, 'clone_uri', rtype=repo_type)
637 642 raise formencode.Invalid(msg, value, state,
638 643 error_dict={'clone_uri': msg})
639 644 return _validator
640 645
641 646
642 647 def ValidForkType(old_data={}):
643 648 class _validator(formencode.validators.FancyValidator):
644 649 messages = {
645 650 'invalid_fork_type': _(u'Fork have to be the same type as parent')
646 651 }
647 652
648 653 def validate_python(self, value, state):
649 654 if old_data['repo_type'] != value:
650 655 msg = M(self, 'invalid_fork_type', state)
651 656 raise formencode.Invalid(
652 657 msg, value, state, error_dict={'repo_type': msg}
653 658 )
654 659 return _validator
655 660
656 661
657 662 def CanWriteGroup(old_data=None):
658 663 class _validator(formencode.validators.FancyValidator):
659 664 messages = {
660 665 'permission_denied': _(
661 666 u"You do not have the permission "
662 667 u"to create repositories in this group."),
663 668 'permission_denied_root': _(
664 669 u"You do not have the permission to store repositories in "
665 670 u"the root location.")
666 671 }
667 672
668 673 def _to_python(self, value, state):
669 674 # root location
670 675 if value in [-1, "-1"]:
671 676 return None
672 677 return value
673 678
674 679 def validate_python(self, value, state):
675 680 gr = RepoGroup.get(value)
676 681 gr_name = gr.group_name if gr else None # None means ROOT location
677 682 # create repositories with write permission on group is set to true
678 683 create_on_write = HasPermissionAny(
679 684 'hg.create.write_on_repogroup.true')()
680 685 group_admin = HasRepoGroupPermissionAny('group.admin')(
681 686 gr_name, 'can write into group validator')
682 687 group_write = HasRepoGroupPermissionAny('group.write')(
683 688 gr_name, 'can write into group validator')
684 689 forbidden = not (group_admin or (group_write and create_on_write))
685 690 can_create_repos = HasPermissionAny(
686 691 'hg.admin', 'hg.create.repository')
687 692 gid = (old_data['repo_group'].get('group_id')
688 693 if (old_data and 'repo_group' in old_data) else None)
689 694 value_changed = gid != safe_int(value)
690 695 new = not old_data
691 696 # do check if we changed the value, there's a case that someone got
692 697 # revoked write permissions to a repository, he still created, we
693 698 # don't need to check permission if he didn't change the value of
694 699 # groups in form box
695 700 if value_changed or new:
696 701 # parent group need to be existing
697 702 if gr and forbidden:
698 703 msg = M(self, 'permission_denied', state)
699 704 raise formencode.Invalid(
700 705 msg, value, state, error_dict={'repo_type': msg}
701 706 )
702 707 # check if we can write to root location !
703 708 elif gr is None and not can_create_repos():
704 709 msg = M(self, 'permission_denied_root', state)
705 710 raise formencode.Invalid(
706 711 msg, value, state, error_dict={'repo_type': msg}
707 712 )
708 713
709 714 return _validator
710 715
711 716
712 717 def ValidPerms(type_='repo'):
713 718 if type_ == 'repo_group':
714 719 EMPTY_PERM = 'group.none'
715 720 elif type_ == 'repo':
716 721 EMPTY_PERM = 'repository.none'
717 722 elif type_ == 'user_group':
718 723 EMPTY_PERM = 'usergroup.none'
719 724
720 725 class _validator(formencode.validators.FancyValidator):
721 726 messages = {
722 727 'perm_new_member_name':
723 728 _(u'This username or user group name is not valid')
724 729 }
725 730
726 731 def _to_python(self, value, state):
727 732 perm_updates = OrderedSet()
728 733 perm_additions = OrderedSet()
729 734 perm_deletions = OrderedSet()
730 735 # build a list of permission to update/delete and new permission
731 736
732 737 # Read the perm_new_member/perm_del_member attributes and group
733 738 # them by they IDs
734 739 new_perms_group = defaultdict(dict)
735 740 del_perms_group = defaultdict(dict)
736 741 for k, v in value.copy().iteritems():
737 742 if k.startswith('perm_del_member'):
738 743 # delete from org storage so we don't process that later
739 744 del value[k]
740 745 # part is `id`, `type`
741 746 _type, part = k.split('perm_del_member_')
742 747 args = part.split('_')
743 748 if len(args) == 2:
744 749 _key, pos = args
745 750 del_perms_group[pos][_key] = v
746 751 if k.startswith('perm_new_member'):
747 752 # delete from org storage so we don't process that later
748 753 del value[k]
749 754 # part is `id`, `type`, `perm`
750 755 _type, part = k.split('perm_new_member_')
751 756 args = part.split('_')
752 757 if len(args) == 2:
753 758 _key, pos = args
754 759 new_perms_group[pos][_key] = v
755 760
756 761 # store the deletes
757 762 for k in sorted(del_perms_group.keys()):
758 763 perm_dict = del_perms_group[k]
759 764 del_member = perm_dict.get('id')
760 765 del_type = perm_dict.get('type')
761 766 if del_member and del_type:
762 767 perm_deletions.add((del_member, None, del_type))
763 768
764 769 # store additions in order of how they were added in web form
765 770 for k in sorted(new_perms_group.keys()):
766 771 perm_dict = new_perms_group[k]
767 772 new_member = perm_dict.get('id')
768 773 new_type = perm_dict.get('type')
769 774 new_perm = perm_dict.get('perm')
770 775 if new_member and new_perm and new_type:
771 776 perm_additions.add((new_member, new_perm, new_type))
772 777
773 778 # get updates of permissions
774 779 # (read the existing radio button states)
775 780 for k, update_value in value.iteritems():
776 781 if k.startswith('u_perm_') or k.startswith('g_perm_'):
777 782 member = k[7:]
778 783 update_type = {'u': 'user',
779 784 'g': 'users_group'}[k[0]]
780 785 if member == User.DEFAULT_USER:
781 786 if str2bool(value.get('repo_private')):
782 787 # set none for default when updating to
783 788 # private repo protects agains form manipulation
784 789 update_value = EMPTY_PERM
785 790 perm_updates.add((member, update_value, update_type))
786 791 # check the deletes
787 792
788 793 value['perm_additions'] = list(perm_additions)
789 794 value['perm_updates'] = list(perm_updates)
790 795 value['perm_deletions'] = list(perm_deletions)
791 796
792 797 # validate users they exist and they are active !
793 798 for member_id, _perm, member_type in perm_additions:
794 799 try:
795 800 if member_type == 'user':
796 801 self.user_db = User.query()\
797 802 .filter(User.active == true())\
798 803 .filter(User.user_id == member_id).one()
799 804 if member_type == 'users_group':
800 805 self.user_db = UserGroup.query()\
801 806 .filter(UserGroup.users_group_active == true())\
802 807 .filter(UserGroup.users_group_id == member_id)\
803 808 .one()
804 809
805 810 except Exception:
806 811 log.exception('Updated permission failed: org_exc:')
807 812 msg = M(self, 'perm_new_member_type', state)
808 813 raise formencode.Invalid(
809 814 msg, value, state, error_dict={
810 815 'perm_new_member_name': msg}
811 816 )
812 817 return value
813 818 return _validator
814 819
815 820
816 821 def ValidSettings():
817 822 class _validator(formencode.validators.FancyValidator):
818 823 def _to_python(self, value, state):
819 824 # settings form for users that are not admin
820 825 # can't edit certain parameters, it's extra backup if they mangle
821 826 # with forms
822 827
823 828 forbidden_params = [
824 829 'user', 'repo_type', 'repo_enable_locking',
825 830 'repo_enable_downloads', 'repo_enable_statistics'
826 831 ]
827 832
828 833 for param in forbidden_params:
829 834 if param in value:
830 835 del value[param]
831 836 return value
832 837
833 838 def validate_python(self, value, state):
834 839 pass
835 840 return _validator
836 841
837 842
838 843 def ValidPath():
839 844 class _validator(formencode.validators.FancyValidator):
840 845 messages = {
841 846 'invalid_path': _(u'This is not a valid path')
842 847 }
843 848
844 849 def validate_python(self, value, state):
845 850 if not os.path.isdir(value):
846 851 msg = M(self, 'invalid_path', state)
847 852 raise formencode.Invalid(
848 853 msg, value, state, error_dict={'paths_root_path': msg}
849 854 )
850 855 return _validator
851 856
852 857
853 858 def UniqSystemEmail(old_data={}):
854 859 class _validator(formencode.validators.FancyValidator):
855 860 messages = {
856 861 'email_taken': _(u'This e-mail address is already taken')
857 862 }
858 863
859 864 def _to_python(self, value, state):
860 865 return value.lower()
861 866
862 867 def validate_python(self, value, state):
863 868 if (old_data.get('email') or '').lower() != value:
864 869 user = User.get_by_email(value, case_insensitive=True)
865 870 if user:
866 871 msg = M(self, 'email_taken', state)
867 872 raise formencode.Invalid(
868 873 msg, value, state, error_dict={'email': msg}
869 874 )
870 875 return _validator
871 876
872 877
873 878 def ValidSystemEmail():
874 879 class _validator(formencode.validators.FancyValidator):
875 880 messages = {
876 881 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
877 882 }
878 883
879 884 def _to_python(self, value, state):
880 885 return value.lower()
881 886
882 887 def validate_python(self, value, state):
883 888 user = User.get_by_email(value, case_insensitive=True)
884 889 if user is None:
885 890 msg = M(self, 'non_existing_email', state, email=value)
886 891 raise formencode.Invalid(
887 892 msg, value, state, error_dict={'email': msg}
888 893 )
889 894
890 895 return _validator
891 896
892 897
893 898 def NotReviewedRevisions(repo_id):
894 899 class _validator(formencode.validators.FancyValidator):
895 900 messages = {
896 901 'rev_already_reviewed':
897 902 _(u'Revisions %(revs)s are already part of pull request '
898 903 u'or have set status'),
899 904 }
900 905
901 906 def validate_python(self, value, state):
902 907 # check revisions if they are not reviewed, or a part of another
903 908 # pull request
904 909 statuses = ChangesetStatus.query()\
905 910 .filter(ChangesetStatus.revision.in_(value))\
906 911 .filter(ChangesetStatus.repo_id == repo_id)\
907 912 .all()
908 913
909 914 errors = []
910 915 for status in statuses:
911 916 if status.pull_request_id:
912 917 errors.append(['pull_req', status.revision[:12]])
913 918 elif status.status:
914 919 errors.append(['status', status.revision[:12]])
915 920
916 921 if errors:
917 922 revs = ','.join([x[1] for x in errors])
918 923 msg = M(self, 'rev_already_reviewed', state, revs=revs)
919 924 raise formencode.Invalid(
920 925 msg, value, state, error_dict={'revisions': revs})
921 926
922 927 return _validator
923 928
924 929
925 930 def ValidIp():
926 931 class _validator(CIDR):
927 932 messages = {
928 933 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
929 934 'illegalBits': _(
930 935 u'The network size (bits) must be within the range '
931 936 u'of 0-32 (not %(bits)r)'),
932 937 }
933 938
934 939 # we ovveride the default to_python() call
935 940 def to_python(self, value, state):
936 941 v = super(_validator, self).to_python(value, state)
937 942 v = v.strip()
938 943 net = ipaddress.ip_network(address=v, strict=False)
939 944 return str(net)
940 945
941 946 def validate_python(self, value, state):
942 947 try:
943 948 addr = value.strip()
944 949 # this raises an ValueError if address is not IpV4 or IpV6
945 950 ipaddress.ip_network(addr, strict=False)
946 951 except ValueError:
947 952 raise formencode.Invalid(self.message('badFormat', state),
948 953 value, state)
949 954
950 955 return _validator
951 956
952 957
953 958 def FieldKey():
954 959 class _validator(formencode.validators.FancyValidator):
955 960 messages = {
956 961 'badFormat': _(
957 962 u'Key name can only consist of letters, '
958 963 u'underscore, dash or numbers'),
959 964 }
960 965
961 966 def validate_python(self, value, state):
962 967 if not re.match('[a-zA-Z0-9_-]+$', value):
963 968 raise formencode.Invalid(self.message('badFormat', state),
964 969 value, state)
965 970 return _validator
966 971
967 972
968 973 def BasePath():
969 974 class _validator(formencode.validators.FancyValidator):
970 975 messages = {
971 976 'badPath': _(u'Filename cannot be inside a directory'),
972 977 }
973 978
974 979 def _to_python(self, value, state):
975 980 return value
976 981
977 982 def validate_python(self, value, state):
978 983 if value != os.path.basename(value):
979 984 raise formencode.Invalid(self.message('badPath', state),
980 985 value, state)
981 986 return _validator
982 987
983 988
984 989 def ValidAuthPlugins():
985 990 class _validator(formencode.validators.FancyValidator):
986 991 messages = {
987 992 'import_duplicate': _(
988 993 u'Plugins %(loaded)s and %(next_to_load)s '
989 994 u'both export the same name'),
990 995 'missing_includeme': _(
991 996 u'The plugin "%(plugin_id)s" is missing an includeme '
992 997 u'function.'),
993 998 'import_error': _(
994 999 u'Can not load plugin "%(plugin_id)s"'),
995 1000 'no_plugin': _(
996 1001 u'No plugin available with ID "%(plugin_id)s"'),
997 1002 }
998 1003
999 1004 def _to_python(self, value, state):
1000 1005 # filter empty values
1001 1006 return filter(lambda s: s not in [None, ''], value)
1002 1007
1003 1008 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1004 1009 """
1005 1010 Validates that the plugin import works. It also checks that the
1006 1011 plugin has an includeme attribute.
1007 1012 """
1008 1013 try:
1009 1014 plugin = _import_legacy_plugin(plugin_id)
1010 1015 except Exception as e:
1011 1016 log.exception(
1012 1017 'Exception during import of auth legacy plugin "{}"'
1013 1018 .format(plugin_id))
1014 1019 msg = M(self, 'import_error', plugin_id=plugin_id)
1015 1020 raise formencode.Invalid(msg, value, state)
1016 1021
1017 1022 if not hasattr(plugin, 'includeme'):
1018 1023 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1019 1024 raise formencode.Invalid(msg, value, state)
1020 1025
1021 1026 return plugin
1022 1027
1023 1028 def _validate_plugin_id(self, plugin_id, value, state):
1024 1029 """
1025 1030 Plugins are already imported during app start up. Therefore this
1026 1031 validation only retrieves the plugin from the plugin registry and
1027 1032 if it returns something not None everything is OK.
1028 1033 """
1029 1034 plugin = loadplugin(plugin_id)
1030 1035
1031 1036 if plugin is None:
1032 1037 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1033 1038 raise formencode.Invalid(msg, value, state)
1034 1039
1035 1040 return plugin
1036 1041
1037 1042 def validate_python(self, value, state):
1038 1043 unique_names = {}
1039 1044 for plugin_id in value:
1040 1045
1041 1046 # Validate legacy or normal plugin.
1042 1047 if plugin_id.startswith(legacy_plugin_prefix):
1043 1048 plugin = self._validate_legacy_plugin_id(
1044 1049 plugin_id, value, state)
1045 1050 else:
1046 1051 plugin = self._validate_plugin_id(plugin_id, value, state)
1047 1052
1048 1053 # Only allow unique plugin names.
1049 1054 if plugin.name in unique_names:
1050 1055 msg = M(self, 'import_duplicate', state,
1051 1056 loaded=unique_names[plugin.name],
1052 1057 next_to_load=plugin)
1053 1058 raise formencode.Invalid(msg, value, state)
1054 1059 unique_names[plugin.name] = plugin
1055 1060
1056 1061 return _validator
1057 1062
1058 1063
1059 1064 def UniqGistId():
1060 1065 class _validator(formencode.validators.FancyValidator):
1061 1066 messages = {
1062 1067 'gistid_taken': _(u'This gistid is already in use')
1063 1068 }
1064 1069
1065 1070 def _to_python(self, value, state):
1066 1071 return repo_name_slug(value.lower())
1067 1072
1068 1073 def validate_python(self, value, state):
1069 1074 existing = Gist.get_by_access_id(value)
1070 1075 if existing:
1071 1076 msg = M(self, 'gistid_taken', state)
1072 1077 raise formencode.Invalid(
1073 1078 msg, value, state, error_dict={'gistid': msg}
1074 1079 )
1075 1080
1076 1081 return _validator
1077 1082
1078 1083
1079 1084 def ValidPattern():
1080 1085
1081 1086 class _Validator(formencode.validators.FancyValidator):
1082 1087
1083 1088 def _to_python(self, value, state):
1084 1089 patterns = []
1085 1090
1086 1091 prefix = 'new_pattern'
1087 1092 for name, v in value.iteritems():
1088 1093 pattern_name = '_'.join((prefix, 'pattern'))
1089 1094 if name.startswith(pattern_name):
1090 1095 new_item_id = name[len(pattern_name)+1:]
1091 1096
1092 1097 def _field(name):
1093 1098 return '%s_%s_%s' % (prefix, name, new_item_id)
1094 1099
1095 1100 values = {
1096 1101 'issuetracker_pat': value.get(_field('pattern')),
1097 1102 'issuetracker_pat': value.get(_field('pattern')),
1098 1103 'issuetracker_url': value.get(_field('url')),
1099 1104 'issuetracker_pref': value.get(_field('prefix')),
1100 1105 'issuetracker_desc': value.get(_field('description'))
1101 1106 }
1102 1107 new_uid = md5(values['issuetracker_pat'])
1103 1108
1104 1109 has_required_fields = (
1105 1110 values['issuetracker_pat']
1106 1111 and values['issuetracker_url'])
1107 1112
1108 1113 if has_required_fields:
1109 1114 settings = [
1110 1115 ('_'.join((key, new_uid)), values[key], 'unicode')
1111 1116 for key in values]
1112 1117 patterns.append(settings)
1113 1118
1114 1119 value['patterns'] = patterns
1115 1120 delete_patterns = value.get('uid') or []
1116 1121 if not isinstance(delete_patterns, (list, tuple)):
1117 1122 delete_patterns = [delete_patterns]
1118 1123 value['delete_patterns'] = delete_patterns
1119 1124 return value
1120 1125 return _Validator
@@ -1,293 +1,320 b''
1 1 // forms.less
2 2 // For use in RhodeCode applications;
3 3 // see style guide documentation for guidelines.
4 4
5 5 form.rcform {
6 6
7 7 // reset for ie
8 8 // using :not(#ie) prevents older browsers from applying these rules
9 9 input[type="radio"],
10 10 input[type="checkbox"] {
11 11 padding: 0;
12 12 border: none;
13 13 }
14 14 label { display: inline; border:none; padding:none; }
15 15 .label { display: none; }
16 16
17 17 max-width: 940px;
18 18 line-height: normal;
19 19 white-space: normal;
20 20 font-size: @basefontsize;
21 21 font-family: @text-light;
22 22 color: @form-textcolor;
23 23
24 24 fieldset,
25 25 .buttons {
26 26 clear: both;
27 27 position: relative;
28 28 display:block;
29 29 width: 100%;
30 30 min-height: 3em;
31 31 margin-bottom: @form-vertical-margin;
32 32 line-height: 1.2em;
33 33
34 34 &:after { //clearfix
35 35 content: "";
36 36 clear: both;
37 37 width: 100%;
38 38 height: 1em;
39 39 }
40 40
41 41 .label:not(#ie) {
42 42 display: inline;
43 43 margin: 0 1em 0 .5em;
44 44 line-height: 1em;
45 45 }
46 46 }
47 47
48 48 legend {
49 49 float: left;
50 50 display: block;
51 51 width: @legend-width;
52 52 margin: 0;
53 53 padding: 0 @padding 0 0;
54 54 }
55 55
56 56 .fields {
57 57 float: left;
58 58 display: block;
59 59 width: 100%;
60 60 max-width: 500px;
61 61 margin: 0 0 @padding -@legend-width;
62 62 padding: 0 0 0 @legend-width;
63 63
64 64 .btn {
65 65 display: inline-block;
66 66 margin: 0 1em @padding 0;
67 67 }
68 68 }
69 69
70 70 input,
71 71 textarea {
72 72 float: left;
73 73 .box-sizing(content-box);
74 74 padding: @input-padding;
75 75 border: @border-thickness-inputs solid @grey4;
76 76 }
77 77
78 78 input {
79 79 float: left;
80 80 margin: 0 @input-padding 0 0;
81 81 line-height: 1em;
82 82 }
83 83
84 84 input[type="text"],
85 85 input[type="password"],
86 86 textarea {
87 87 float: left;
88 88 min-width: 200px;
89 89 margin: 0 1em @padding 0;
90 90 color: @form-textcolor;
91 91 }
92 92
93 93 input[type="text"],
94 94 input[type="password"] {
95 95 height: 1em;
96 96 }
97 97
98 98 textarea {
99 99 width: 100%;
100 100 margin-top: -1em; //so it lines up with legend
101 101 overflow: auto;
102 102 }
103 103
104 104 label:not(#ie) {
105 105 cursor: pointer;
106 106 display: inline-block;
107 107 position: relative;
108 108 background: white;
109 109 border-radius: 4px;
110 110 box-shadow: none;
111 111
112 112 &:hover::after {
113 113 opacity: 0.5;
114 114 }
115 115 }
116 116
117 117 input[type="radio"]:not(#ie),
118 118 input[type="checkbox"]:not(#ie) {
119 119 // Hide the input, but have it still be clickable
120 120 opacity: 0;
121 121 float: left;
122 122 height: 0;
123 123 width: 0;
124 124 margin: 0;
125 125 padding: 0;
126 126 }
127 127 input[type='radio'] + label:not(#ie),
128 128 input[type='checkbox'] + label:not(#ie) {
129 129 margin: 0;
130 130 clear: none;
131 131 }
132 132
133 133 input[type='radio'] + label:not(#ie) {
134 134 .circle (@form-radio-width,white);
135 135 float: left;
136 136 display: inline-block;
137 137 height: @form-radio-width;
138 138 width: @form-radio-width;
139 139 margin: 2px 6px 2px 0;
140 140 border: 1px solid @grey4;
141 141 background-color: white;
142 142 box-shadow: none;
143 143 text-indent: -9999px;
144 144 transition: none;
145 145
146 146 & + .label {
147 147 float: left;
148 148 margin-top: 7px
149 149 }
150 150 }
151 151
152 152 input[type='radio']:checked + label:not(#ie) {
153 153 margin: 0 4px 0 -2px;
154 154 padding: 3px;
155 155 border-style: double;
156 156 border-color: white;
157 157 border-width: thick;
158 158 background-color: @rcblue;
159 159 box-shadow: none;
160 160 }
161 161
162 162 input[type='checkbox'] + label:not(#ie) {
163 163 float: left;
164 164 width: @form-check-width;
165 165 height: @form-check-width;
166 166 margin: 0 5px 1em 0;
167 167 border: 1px solid @grey3;
168 168 .border-radius(@border-radius);
169 169 background-color: white;
170 170 box-shadow: none;
171 171 text-indent: -9999px;
172 172 transition: none;
173 173
174 174 &:after {
175 175 content: '';
176 176 width: 9px;
177 177 height: 5px;
178 178 position: absolute;
179 179 top: 4px;
180 180 left: 4px;
181 181 border: 3px solid @grey3;
182 182 border-top: none;
183 183 border-right: none;
184 184 background: transparent;
185 185 opacity: 0;
186 186 transform: rotate(-45deg);
187 187 filter: progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476); /* IE6,IE7 */
188 188
189 189 -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(SizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)"; /* IE8 */ }
190 190
191 191 & + .label {
192 192 float: left;
193 193 margin-top: 5px
194 194 }
195 195 }
196 196
197 197 input[type=checkbox]:not(#ie) {
198 198 visibility: hidden;
199 199 &:checked + label:after {
200 200 opacity: 1;
201 201 }
202 202 }
203 203
204 204 // center checkbox and label on a drop-down
205 205 .drop-menu + select + input[type='checkbox'] + label:not(#ie) {
206 206 margin-top:10px;
207 207
208 208 & + .label {
209 209 margin-top: 15px;
210 210 }
211 211 }
212 212
213 213 .formlist {
214 214 position: relative;
215 215 float: left;
216 216 margin: 0;
217 217 padding: 0;
218 218
219 219 li {
220 220 list-style-type: none;
221 221
222 222 &:before { content:none; }
223 223 &:after {
224 224 content: "";
225 225 float: left;
226 226 display: block;
227 227 height: @padding;
228 228 width: 100%;
229 229 }
230 230 }
231 231 }
232 232
233 233 .drop-menu {
234 234 float: left;
235 235 margin: 0 @input-padding 0 0;
236 236 }
237 237
238 238 .help-block,
239 239 .error-message {
240 240 display: block;
241 241 clear: both;
242 242 margin: @textmargin 0;
243 243 }
244 244
245 245 .error-message {
246 246 margin-top: 5px;
247 247 }
248 248
249 249 input[type=submit] {
250 250 &:extend(.btn-primary);
251 251
252 252 &:hover {
253 253 &:extend(.btn-primary:hover);
254 254 }
255 255 }
256 256
257 257 input[type=reset] {
258 258 &:extend(.btn-default);
259 259
260 260 &:hover {
261 261 &:extend(.btn-default:hover);
262 262 }
263 263 }
264 264
265 265 select,
266 266 option:checked {
267 267 background-color: @rclightblue;
268 268 }
269 269
270 270 }
271 271
272 .badged-field {
273 .user-badge {
274 line-height: 25px;
275 padding: 10px 5px;
276 border-radius: @border-radius;
277 border-top: 1px solid @rclightblue;
278 border-left: 1px solid @rclightblue;
279 border-bottom: 1px solid @rclightblue;
280 font-size: 14px;
281 font-style: normal;
282 color: @text-light;
283 display: inline-block;
284 vertical-align: top;
285 cursor: default;
286 margin-right: -2px;
287 }
288 .badge-input-container {
289 display: flex;
290 position: relative;
291 }
292 .user-disabled {
293 text-decoration: line-through;
294 }
295 .badge-input-wrap {
296 display: inline-block;
297 }
298 }
272 299
273 300 // for situations where we wish to display the form value but not the form input
274 301 input.input-valuedisplay {
275 302 border: none;
276 303 }
277 304
278 305 // for forms which only display information
279 306 .infoform {
280 307 .fields {
281 308 .field {
282 309 label,
283 310 .label,
284 311 input,
285 312 .input {
286 313 margin-top: 0;
287 314 margin-bottom: 0;
288 315 padding-top: 0;
289 316 padding-bottom: 0;
290 317 }
291 318 }
292 319 }
293 320 }
@@ -1,2094 +1,2095 b''
1 1 //Primary CSS
2 2
3 3 //--- IMPORTS ------------------//
4 4
5 5 @import 'helpers';
6 6 @import 'mixins';
7 7 @import 'rcicons';
8 8 @import 'fonts';
9 9 @import 'variables';
10 10 @import 'bootstrap-variables';
11 11 @import 'form-bootstrap';
12 12 @import 'codemirror';
13 13 @import 'legacy_code_styles';
14 14 @import 'progress-bar';
15 15
16 16 @import 'type';
17 17 @import 'alerts';
18 18 @import 'buttons';
19 19 @import 'tags';
20 20 @import 'code-block';
21 21 @import 'examples';
22 22 @import 'login';
23 23 @import 'main-content';
24 24 @import 'select2';
25 25 @import 'comments';
26 26 @import 'panels-bootstrap';
27 27 @import 'panels';
28 28
29 29
30 30 //--- BASE ------------------//
31 31 .noscript-error {
32 32 top: 0;
33 33 left: 0;
34 34 width: 100%;
35 35 z-index: 101;
36 36 text-align: center;
37 37 font-family: @text-semibold;
38 38 font-size: 120%;
39 39 color: white;
40 40 background-color: @alert2;
41 41 padding: 5px 0 5px 0;
42 42 }
43 43
44 44 html {
45 45 display: table;
46 46 height: 100%;
47 47 width: 100%;
48 48 }
49 49
50 50 body {
51 51 display: table-cell;
52 52 width: 100%;
53 53 }
54 54
55 55 //--- LAYOUT ------------------//
56 56
57 57 .hidden{
58 58 display: none !important;
59 59 }
60 60
61 61 .box{
62 62 float: left;
63 63 width: 100%;
64 64 }
65 65
66 66 .browser-header {
67 67 clear: both;
68 68 }
69 69 .main {
70 70 clear: both;
71 71 padding:0 0 @pagepadding;
72 72 height: auto;
73 73
74 74 &:after { //clearfix
75 75 content:"";
76 76 clear:both;
77 77 width:100%;
78 78 display:block;
79 79 }
80 80 }
81 81
82 82 .action-link{
83 83 margin-left: @padding;
84 84 padding-left: @padding;
85 85 border-left: @border-thickness solid @border-default-color;
86 86 }
87 87
88 88 input + .action-link, .action-link.first{
89 89 border-left: none;
90 90 }
91 91
92 92 .action-link.last{
93 93 margin-right: @padding;
94 94 padding-right: @padding;
95 95 }
96 96
97 97 .action-link.active,
98 98 .action-link.active a{
99 99 color: @grey4;
100 100 }
101 101
102 102 ul.simple-list{
103 103 list-style: none;
104 104 margin: 0;
105 105 padding: 0;
106 106 }
107 107
108 108 .main-content {
109 109 padding-bottom: @pagepadding;
110 110 }
111 111
112 112 .wrapper {
113 113 position: relative;
114 114 max-width: @wrapper-maxwidth;
115 115 margin: 0 auto;
116 116 }
117 117
118 118 #content {
119 119 clear: both;
120 120 padding: 0 @contentpadding;
121 121 }
122 122
123 123 .advanced-settings-fields{
124 124 input{
125 125 margin-left: @textmargin;
126 126 margin-right: @padding/2;
127 127 }
128 128 }
129 129
130 130 .cs_files_title {
131 131 margin: @pagepadding 0 0;
132 132 }
133 133
134 134 input.inline[type="file"] {
135 135 display: inline;
136 136 }
137 137
138 138 .error_page {
139 139 margin: 10% auto;
140 140
141 141 h1 {
142 142 color: @grey2;
143 143 }
144 144
145 145 .error-branding {
146 146 font-family: @text-semibold;
147 147 color: @grey4;
148 148 }
149 149
150 150 .error_message {
151 151 font-family: @text-regular;
152 152 }
153 153
154 154 .sidebar {
155 155 min-height: 275px;
156 156 margin: 0;
157 157 padding: 0 0 @sidebarpadding @sidebarpadding;
158 158 border: none;
159 159 }
160 160
161 161 .main-content {
162 162 position: relative;
163 163 margin: 0 @sidebarpadding @sidebarpadding;
164 164 padding: 0 0 0 @sidebarpadding;
165 165 border-left: @border-thickness solid @grey5;
166 166
167 167 @media (max-width:767px) {
168 168 clear: both;
169 169 width: 100%;
170 170 margin: 0;
171 171 border: none;
172 172 }
173 173 }
174 174
175 175 .inner-column {
176 176 float: left;
177 177 width: 29.75%;
178 178 min-height: 150px;
179 179 margin: @sidebarpadding 2% 0 0;
180 180 padding: 0 2% 0 0;
181 181 border-right: @border-thickness solid @grey5;
182 182
183 183 @media (max-width:767px) {
184 184 clear: both;
185 185 width: 100%;
186 186 border: none;
187 187 }
188 188
189 189 ul {
190 190 padding-left: 1.25em;
191 191 }
192 192
193 193 &:last-child {
194 194 margin: @sidebarpadding 0 0;
195 195 border: none;
196 196 }
197 197
198 198 h4 {
199 199 margin: 0 0 @padding;
200 200 font-family: @text-semibold;
201 201 }
202 202 }
203 203 }
204 204 .error-page-logo {
205 205 width: 130px;
206 206 height: 160px;
207 207 }
208 208
209 209 // HEADER
210 210 .header {
211 211
212 212 // TODO: johbo: Fix login pages, so that they work without a min-height
213 213 // for the header and then remove the min-height. I chose a smaller value
214 214 // intentionally here to avoid rendering issues in the main navigation.
215 215 min-height: 49px;
216 216
217 217 position: relative;
218 218 vertical-align: bottom;
219 219 padding: 0 @header-padding;
220 220 background-color: @grey2;
221 221 color: @grey5;
222 222
223 223 .title {
224 224 overflow: visible;
225 225 }
226 226
227 227 &:before,
228 228 &:after {
229 229 content: "";
230 230 clear: both;
231 231 width: 100%;
232 232 }
233 233
234 234 // TODO: johbo: Avoids breaking "Repositories" chooser
235 235 .select2-container .select2-choice .select2-arrow {
236 236 display: none;
237 237 }
238 238 }
239 239
240 240 #header-inner {
241 241 &.title {
242 242 margin: 0;
243 243 }
244 244 &:before,
245 245 &:after {
246 246 content: "";
247 247 clear: both;
248 248 }
249 249 }
250 250
251 251 // Gists
252 252 #files_data {
253 253 clear: both; //for firefox
254 254 }
255 255 #gistid {
256 256 margin-right: @padding;
257 257 }
258 258
259 259 // Global Settings Editor
260 260 .textarea.editor {
261 261 float: left;
262 262 position: relative;
263 263 max-width: @texteditor-width;
264 264
265 265 select {
266 266 position: absolute;
267 267 top:10px;
268 268 right:0;
269 269 }
270 270
271 271 .CodeMirror {
272 272 margin: 0;
273 273 }
274 274
275 275 .help-block {
276 276 margin: 0 0 @padding;
277 277 padding:.5em;
278 278 background-color: @grey6;
279 279 }
280 280 }
281 281
282 282 ul.auth_plugins {
283 283 margin: @padding 0 @padding @legend-width;
284 284 padding: 0;
285 285
286 286 li {
287 287 margin-bottom: @padding;
288 288 line-height: 1em;
289 289 list-style-type: none;
290 290
291 291 .auth_buttons .btn {
292 292 margin-right: @padding;
293 293 }
294 294
295 295 &:before { content: none; }
296 296 }
297 297 }
298 298
299 // Pull Requests
299
300 // My Account PR list
301
302 #show_closed {
303 margin: 0 1em 0 0;
304 }
300 305
301 306 .pullrequestlist {
302 max-width: @pullrequest-width;
303 margin-bottom: @space;
304
305 // Tweaks for "My Account" / "Pull requests"
306 .prwrapper {
307 clear: left;
307 .closed {
308 background-color: @grey6;
309 }
310 .td-status {
311 padding-left: .5em;
312 }
313 .truncate {
314 height: 2.75em;
315 white-space: pre-line;
316 }
317 table.rctable .user {
318 padding-left: 0;
319 }
320 }
308 321
309 .pr {
310 margin: 0;
311 padding: 0;
312 border-bottom: none;
313 }
314
315 // TODO: johbo: Replace with something that makes up an inline form or
316 // similar.
317 .repolist_actions {
318 display: inline-block;
319 }
320 }
321
322 }
322 // Pull Requests
323 323
324 324 .pullrequests_section_head {
325 325 display: block;
326 326 clear: both;
327 327 margin: @padding 0;
328 328 font-family: @text-bold;
329 329 }
330 330
331 331 .pr-origininfo, .pr-targetinfo {
332 332 position: relative;
333 333
334 334 .tag {
335 335 display: inline-block;
336 336 margin: 0 1em .5em 0;
337 337 }
338 338
339 339 .clone-url {
340 340 display: inline-block;
341 341 margin: 0 0 .5em 0;
342 342 padding: 0;
343 343 line-height: 1.2em;
344 344 }
345 345 }
346 346
347 347 .pr-pullinfo {
348 348 clear: both;
349 349 margin: .5em 0;
350 350 }
351 351
352 352 #pr-title-input {
353 353 width: 72%;
354 354 font-size: 1em;
355 355 font-family: @text-bold;
356 356 margin: 0;
357 357 padding: 0 0 0 @padding/4;
358 358 line-height: 1.7em;
359 359 color: @text-color;
360 360 letter-spacing: .02em;
361 361 }
362 362
363 363 #pullrequest_title {
364 364 width: 100%;
365 365 box-sizing: border-box;
366 366 }
367 367
368 368 #pr_open_message {
369 369 border: @border-thickness solid #fff;
370 370 border-radius: @border-radius;
371 371 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
372 372 text-align: right;
373 373 overflow: hidden;
374 374 }
375 375
376 376 .pr-submit-button {
377 377 float: right;
378 378 margin: 0 0 0 5px;
379 379 }
380 380
381 381 .pr-spacing-container {
382 382 padding: 20px;
383 383 clear: both
384 384 }
385 385
386 386 #pr-description-input {
387 387 margin-bottom: 0;
388 388 }
389 389
390 390 .pr-description-label {
391 391 vertical-align: top;
392 392 }
393 393
394 394 .perms_section_head {
395 395 min-width: 625px;
396 396
397 397 h2 {
398 398 margin-bottom: 0;
399 399 }
400 400
401 401 .label-checkbox {
402 402 float: left;
403 403 }
404 404
405 405 &.field {
406 406 margin: @space 0 @padding;
407 407 }
408 408
409 409 &:first-child.field {
410 410 margin-top: 0;
411 411
412 412 .label {
413 413 margin-top: 0;
414 414 padding-top: 0;
415 415 }
416 416
417 417 .radios {
418 418 padding-top: 0;
419 419 }
420 420 }
421 421
422 422 .radios {
423 423 float: right;
424 424 position: relative;
425 425 width: 405px;
426 426 }
427 427 }
428 428
429 429 //--- MODULES ------------------//
430 430
431 431
432 432 // Fixed Sidebar Column
433 433 .sidebar-col-wrapper {
434 434 padding-left: @sidebar-all-width;
435 435
436 436 .sidebar {
437 437 width: @sidebar-width;
438 438 margin-left: -@sidebar-all-width;
439 439 }
440 440 }
441 441
442 442 .sidebar-col-wrapper.scw-small {
443 443 padding-left: @sidebar-small-all-width;
444 444
445 445 .sidebar {
446 446 width: @sidebar-small-width;
447 447 margin-left: -@sidebar-small-all-width;
448 448 }
449 449 }
450 450
451 451
452 452 // FOOTER
453 453 #footer {
454 454 padding: 0;
455 455 text-align: center;
456 456 vertical-align: middle;
457 457 color: @grey2;
458 458 background-color: @grey6;
459 459
460 460 p {
461 461 margin: 0;
462 462 padding: 1em;
463 463 line-height: 1em;
464 464 }
465 465
466 466 .server-instance { //server instance
467 467 display: none;
468 468 }
469 469
470 470 .title {
471 471 float: none;
472 472 margin: 0 auto;
473 473 }
474 474 }
475 475
476 476 button.close {
477 477 padding: 0;
478 478 cursor: pointer;
479 479 background: transparent;
480 480 border: 0;
481 481 .box-shadow(none);
482 482 -webkit-appearance: none;
483 483 }
484 484
485 485 .close {
486 486 float: right;
487 487 font-size: 21px;
488 488 font-family: @text-bootstrap;
489 489 line-height: 1em;
490 490 font-weight: bold;
491 491 color: @grey2;
492 492
493 493 &:hover,
494 494 &:focus {
495 495 color: @grey1;
496 496 text-decoration: none;
497 497 cursor: pointer;
498 498 }
499 499 }
500 500
501 501 // GRID
502 502 .sorting,
503 503 .sorting_desc,
504 504 .sorting_asc {
505 505 cursor: pointer;
506 506 }
507 507 .sorting_desc:after {
508 508 content: "\00A0\25B2";
509 509 font-size: .75em;
510 510 }
511 511 .sorting_asc:after {
512 512 content: "\00A0\25BC";
513 513 font-size: .68em;
514 514 }
515 515
516 516
517 517 .user_auth_tokens {
518 518
519 519 &.truncate {
520 520 white-space: nowrap;
521 521 overflow: hidden;
522 522 text-overflow: ellipsis;
523 523 }
524 524
525 525 .fields .field .input {
526 526 margin: 0;
527 527 }
528 528
529 529 input#description {
530 530 width: 100px;
531 531 margin: 0;
532 532 }
533 533
534 534 .drop-menu {
535 535 // TODO: johbo: Remove this, should work out of the box when
536 536 // having multiple inputs inline
537 537 margin: 0 0 0 5px;
538 538 }
539 539 }
540 540 #user_list_table {
541 541 .closed {
542 542 background-color: @grey6;
543 543 }
544 544 }
545 545
546 546
547 547 input {
548 548 &.disabled {
549 549 opacity: .5;
550 550 }
551 551 }
552 552
553 553 // remove extra padding in firefox
554 554 input::-moz-focus-inner { border:0; padding:0 }
555 555
556 556 .adjacent input {
557 557 margin-bottom: @padding;
558 558 }
559 559
560 560 .permissions_boxes {
561 561 display: block;
562 562 }
563 563
564 564 //TODO: lisa: this should be in tables
565 565 .show_more_col {
566 566 width: 20px;
567 567 }
568 568
569 569 //FORMS
570 570
571 571 .medium-inline,
572 572 input#description.medium-inline {
573 573 display: inline;
574 574 width: @medium-inline-input-width;
575 575 min-width: 100px;
576 576 }
577 577
578 578 select {
579 579 //reset
580 580 -webkit-appearance: none;
581 581 -moz-appearance: none;
582 582
583 583 display: inline-block;
584 584 height: 28px;
585 585 width: auto;
586 586 margin: 0 @padding @padding 0;
587 587 padding: 0 18px 0 8px;
588 588 line-height:1em;
589 589 font-size: @basefontsize;
590 590 border: @border-thickness solid @rcblue;
591 591 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
592 592 color: @rcblue;
593 593
594 594 &:after {
595 595 content: "\00A0\25BE";
596 596 }
597 597
598 598 &:focus {
599 599 outline: none;
600 600 }
601 601 }
602 602
603 603 option {
604 604 &:focus {
605 605 outline: none;
606 606 }
607 607 }
608 608
609 609 input,
610 610 textarea {
611 611 padding: @input-padding;
612 612 border: @input-border-thickness solid @border-highlight-color;
613 613 .border-radius (@border-radius);
614 614 font-family: @text-light;
615 615 font-size: @basefontsize;
616 616
617 617 &.input-sm {
618 618 padding: 5px;
619 619 }
620 620
621 621 &#description {
622 622 min-width: @input-description-minwidth;
623 623 min-height: 1em;
624 624 padding: 10px;
625 625 }
626 626 }
627 627
628 628 .field-sm {
629 629 input,
630 630 textarea {
631 631 padding: 5px;
632 632 }
633 633 }
634 634
635 635 textarea {
636 636 display: block;
637 637 clear: both;
638 638 width: 100%;
639 639 min-height: 100px;
640 640 margin-bottom: @padding;
641 641 .box-sizing(border-box);
642 642 overflow: auto;
643 643 }
644 644
645 645 label {
646 646 font-family: @text-light;
647 647 }
648 648
649 649 // GRAVATARS
650 650 // centers gravatar on username to the right
651 651
652 652 .gravatar {
653 653 display: inline;
654 654 min-width: 16px;
655 655 min-height: 16px;
656 656 margin: -5px 0;
657 657 padding: 0;
658 658 line-height: 1em;
659 659 border: 1px solid @grey4;
660 660
661 661 &.gravatar-large {
662 662 margin: -0.5em .25em -0.5em 0;
663 663 }
664 664
665 665 & + .user {
666 666 display: inline;
667 667 margin: 0;
668 668 padding: 0 0 0 .17em;
669 669 line-height: 1em;
670 670 }
671 671 }
672 672
673 673 .rc-user { // gravatar + user wrapper
674 674 float: left;
675 675 position: relative;
676 676 min-width: 100px;
677 677 max-width: 200px;
678 678 min-height: (@gravatar-size + @border-thickness * 2); // account for border
679 679 display: block;
680 680 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
681 681
682 682
683 683 .gravatar {
684 684 display: block;
685 685 position: absolute;
686 686 top: 0;
687 687 left: 0;
688 688 min-width: @gravatar-size;
689 689 min-height: @gravatar-size;
690 690 margin: 0;
691 691 }
692 692
693 693 .user {
694 694 display: block;
695 695 max-width: 175px;
696 696 padding-top: 2px;
697 697 overflow: hidden;
698 698 text-overflow: ellipsis;
699 699 }
700 700 }
701 701
702 702 .gist-gravatar,
703 703 .journal_container {
704 704 .gravatar-large {
705 705 margin: 0 .5em -10px 0;
706 706 }
707 707 }
708 708
709 709
710 710 // ADMIN SETTINGS
711 711
712 712 // Tag Patterns
713 713 .tag_patterns {
714 714 .tag_input {
715 715 margin-bottom: @padding;
716 716 }
717 717 }
718 718
719 719 .locked_input {
720 720 position: relative;
721 721
722 722 input {
723 723 display: inline;
724 724 margin-top: 3px;
725 725 }
726 726
727 727 br {
728 728 display: none;
729 729 }
730 730
731 731 .error-message {
732 732 float: left;
733 733 width: 100%;
734 734 }
735 735
736 736 .lock_input_button {
737 737 display: inline;
738 738 }
739 739
740 740 .help-block {
741 741 clear: both;
742 742 }
743 743 }
744 744
745 745 // Notifications
746 746
747 747 .notifications_buttons {
748 748 margin: 0 0 @space 0;
749 749 padding: 0;
750 750
751 751 .btn {
752 752 display: inline-block;
753 753 }
754 754 }
755 755
756 756 .notification-list {
757 757
758 758 div {
759 759 display: inline-block;
760 760 vertical-align: middle;
761 761 }
762 762
763 763 .container {
764 764 display: block;
765 765 margin: 0 0 @padding 0;
766 766 }
767 767
768 768 .delete-notifications {
769 769 margin-left: @padding;
770 770 text-align: right;
771 771 cursor: pointer;
772 772 }
773 773
774 774 .read-notifications {
775 775 margin-left: @padding/2;
776 776 text-align: right;
777 777 width: 35px;
778 778 cursor: pointer;
779 779 }
780 780
781 781 .icon-minus-sign {
782 782 color: @alert2;
783 783 }
784 784
785 785 .icon-ok-sign {
786 786 color: @alert1;
787 787 }
788 788 }
789 789
790 790 .user_settings {
791 791 float: left;
792 792 clear: both;
793 793 display: block;
794 794 width: 100%;
795 795
796 796 .gravatar_box {
797 797 margin-bottom: @padding;
798 798
799 799 &:after {
800 800 content: " ";
801 801 clear: both;
802 802 width: 100%;
803 803 }
804 804 }
805 805
806 806 .fields .field {
807 807 clear: both;
808 808 }
809 809 }
810 810
811 811 .advanced_settings {
812 812 margin-bottom: @space;
813 813
814 814 .help-block {
815 815 margin-left: 0;
816 816 }
817 817
818 818 button + .help-block {
819 819 margin-top: @padding;
820 820 }
821 821 }
822 822
823 823 // admin settings radio buttons and labels
824 824 .label-2 {
825 825 float: left;
826 826 width: @label2-width;
827 827
828 828 label {
829 829 color: @grey1;
830 830 }
831 831 }
832 832 .checkboxes {
833 833 float: left;
834 834 width: @checkboxes-width;
835 835 margin-bottom: @padding;
836 836
837 837 .checkbox {
838 838 width: 100%;
839 839
840 840 label {
841 841 margin: 0;
842 842 padding: 0;
843 843 }
844 844 }
845 845
846 846 .checkbox + .checkbox {
847 847 display: inline-block;
848 848 }
849 849
850 850 label {
851 851 margin-right: 1em;
852 852 }
853 853 }
854 854
855 855 // CHANGELOG
856 856 .container_header {
857 857 float: left;
858 858 display: block;
859 859 width: 100%;
860 860 margin: @padding 0 @padding;
861 861
862 862 #filter_changelog {
863 863 float: left;
864 864 margin-right: @padding;
865 865 }
866 866
867 867 .breadcrumbs_light {
868 868 display: inline-block;
869 869 }
870 870 }
871 871
872 872 .info_box {
873 873 float: right;
874 874 }
875 875
876 876
877 877 #graph_nodes {
878 878 padding-top: 43px;
879 879 }
880 880
881 881 #graph_content{
882 882
883 883 // adjust for table headers so that graph renders properly
884 884 // #graph_nodes padding - table cell padding
885 885 padding-top: (@space - (@basefontsize * 2.4));
886 886
887 887 &.graph_full_width {
888 888 width: 100%;
889 889 max-width: 100%;
890 890 }
891 891 }
892 892
893 893 #graph {
894 894 .flag_status {
895 895 margin: 0;
896 896 }
897 897
898 898 .pagination-left {
899 899 float: left;
900 900 clear: both;
901 901 }
902 902
903 903 .log-container {
904 904 max-width: 345px;
905 905
906 906 .message{
907 907 max-width: 340px;
908 908 }
909 909 }
910 910
911 911 .graph-col-wrapper {
912 912 padding-left: 110px;
913 913
914 914 #graph_nodes {
915 915 width: 100px;
916 916 margin-left: -110px;
917 917 float: left;
918 918 clear: left;
919 919 }
920 920 }
921 921 }
922 922
923 923 #filter_changelog {
924 924 float: left;
925 925 }
926 926
927 927
928 928 //--- THEME ------------------//
929 929
930 930 #logo {
931 931 float: left;
932 932 margin: 9px 0 0 0;
933 933
934 934 .header {
935 935 background-color: transparent;
936 936 }
937 937
938 938 a {
939 939 display: inline-block;
940 940 }
941 941
942 942 img {
943 943 height:30px;
944 944 }
945 945 }
946 946
947 947 .logo-wrapper {
948 948 float:left;
949 949 }
950 950
951 951 .branding{
952 952 float: left;
953 953 padding: 9px 2px;
954 954 line-height: 1em;
955 955 font-size: @navigation-fontsize;
956 956 }
957 957
958 958 img {
959 959 border: none;
960 960 outline: none;
961 961 }
962 962 user-profile-header
963 963 label {
964 964
965 965 input[type="checkbox"] {
966 966 margin-right: 1em;
967 967 }
968 968 input[type="radio"] {
969 969 margin-right: 1em;
970 970 }
971 971 }
972 972
973 973 .flag_status {
974 974 margin: 2px 8px 6px 2px;
975 975 &.under_review {
976 976 .circle(5px, @alert3);
977 977 }
978 978 &.approved {
979 979 .circle(5px, @alert1);
980 980 }
981 981 &.rejected,
982 982 &.forced_closed{
983 983 .circle(5px, @alert2);
984 984 }
985 985 &.not_reviewed {
986 986 .circle(5px, @grey5);
987 987 }
988 988 }
989 989
990 990 .flag_status_comment_box {
991 991 margin: 5px 6px 0px 2px;
992 992 }
993 993 .test_pattern_preview {
994 994 margin: @space 0;
995 995
996 996 p {
997 997 margin-bottom: 0;
998 998 border-bottom: @border-thickness solid @border-default-color;
999 999 color: @grey3;
1000 1000 }
1001 1001
1002 1002 .btn {
1003 1003 margin-bottom: @padding;
1004 1004 }
1005 1005 }
1006 1006 #test_pattern_result {
1007 1007 display: none;
1008 1008 &:extend(pre);
1009 1009 padding: .9em;
1010 1010 color: @grey3;
1011 1011 background-color: @grey7;
1012 1012 border-right: @border-thickness solid @border-default-color;
1013 1013 border-bottom: @border-thickness solid @border-default-color;
1014 1014 border-left: @border-thickness solid @border-default-color;
1015 1015 }
1016 1016
1017 1017 #repo_vcs_settings {
1018 1018 #inherit_overlay_vcs_default {
1019 1019 display: none;
1020 1020 }
1021 1021 #inherit_overlay_vcs_custom {
1022 1022 display: custom;
1023 1023 }
1024 1024 &.inherited {
1025 1025 #inherit_overlay_vcs_default {
1026 1026 display: block;
1027 1027 }
1028 1028 #inherit_overlay_vcs_custom {
1029 1029 display: none;
1030 1030 }
1031 1031 }
1032 1032 }
1033 1033
1034 1034 .issue-tracker-link {
1035 1035 color: @rcblue;
1036 1036 }
1037 1037
1038 1038 // Issue Tracker Table Show/Hide
1039 1039 #repo_issue_tracker {
1040 1040 #inherit_overlay {
1041 1041 display: none;
1042 1042 }
1043 1043 #custom_overlay {
1044 1044 display: custom;
1045 1045 }
1046 1046 &.inherited {
1047 1047 #inherit_overlay {
1048 1048 display: block;
1049 1049 }
1050 1050 #custom_overlay {
1051 1051 display: none;
1052 1052 }
1053 1053 }
1054 1054 }
1055 1055 table.issuetracker {
1056 1056 &.readonly {
1057 1057 tr, td {
1058 1058 color: @grey3;
1059 1059 }
1060 1060 }
1061 1061 .edit {
1062 1062 display: none;
1063 1063 }
1064 1064 .editopen {
1065 1065 .edit {
1066 1066 display: inline;
1067 1067 }
1068 1068 .entry {
1069 1069 display: none;
1070 1070 }
1071 1071 }
1072 1072 tr td.td-action {
1073 1073 min-width: 117px;
1074 1074 }
1075 1075 td input {
1076 1076 max-width: none;
1077 1077 min-width: 30px;
1078 1078 width: 80%;
1079 1079 }
1080 1080 .issuetracker_pref input {
1081 1081 width: 40%;
1082 1082 }
1083 1083 input.edit_issuetracker_update {
1084 1084 margin-right: 0;
1085 1085 width: auto;
1086 1086 }
1087 1087 }
1088 1088
1089
1089 1090 //Permissions Settings
1090 1091 #add_perm {
1091 1092 margin: 0 0 @padding;
1092 1093 cursor: pointer;
1093 1094 }
1094 1095
1095 1096 .perm_ac {
1096 1097 input {
1097 1098 width: 95%;
1098 1099 }
1099 1100 }
1100 1101
1101 1102 .autocomplete-suggestions {
1102 1103 width: auto !important; // overrides autocomplete.js
1103 1104 margin: 0;
1104 1105 border: @border-thickness solid @rcblue;
1105 1106 border-radius: @border-radius;
1106 1107 color: @rcblue;
1107 1108 background-color: white;
1108 1109 }
1109 1110 .autocomplete-selected {
1110 1111 background: #F0F0F0;
1111 1112 }
1112 1113 .ac-container-wrap {
1113 1114 margin: 0;
1114 1115 padding: 8px;
1115 1116 border-bottom: @border-thickness solid @rclightblue;
1116 1117 list-style-type: none;
1117 1118 cursor: pointer;
1118 1119
1119 1120 &:hover {
1120 1121 background-color: @rclightblue;
1121 1122 }
1122 1123
1123 1124 img {
1124 1125 margin-right: 1em;
1125 1126 }
1126 1127
1127 1128 strong {
1128 1129 font-weight: normal;
1129 1130 }
1130 1131 }
1131 1132
1132 1133 // Settings Dropdown
1133 1134 .user-menu .container {
1134 1135 padding: 0 4px;
1135 1136 margin: 0;
1136 1137 }
1137 1138
1138 1139 .user-menu .gravatar {
1139 1140 cursor: pointer;
1140 1141 }
1141 1142
1142 1143 .codeblock {
1143 1144 margin-bottom: @padding;
1144 1145 clear: both;
1145 1146
1146 1147 .stats{
1147 1148 overflow: hidden;
1148 1149 }
1149 1150
1150 1151 .message{
1151 1152 textarea{
1152 1153 margin: 0;
1153 1154 }
1154 1155 }
1155 1156
1156 1157 .code-header {
1157 1158 .stats {
1158 1159 line-height: 2em;
1159 1160
1160 1161 .revision_id {
1161 1162 margin-left: 0;
1162 1163 }
1163 1164 .buttons {
1164 1165 padding-right: 0;
1165 1166 }
1166 1167 }
1167 1168
1168 1169 .item{
1169 1170 margin-right: 0.5em;
1170 1171 }
1171 1172 }
1172 1173
1173 1174 #editor_container{
1174 1175 position: relative;
1175 1176 margin: @padding;
1176 1177 }
1177 1178 }
1178 1179
1179 1180 #file_history_container {
1180 1181 display: none;
1181 1182 }
1182 1183
1183 1184 .file-history-inner {
1184 1185 margin-bottom: 10px;
1185 1186 }
1186 1187
1187 1188 // Pull Requests
1188 1189 .summary-details {
1189 1190 width: 72%;
1190 1191 }
1191 1192 .pr-summary {
1192 1193 border-bottom: @border-thickness solid @grey5;
1193 1194 margin-bottom: @space;
1194 1195 }
1195 1196 .reviewers-title {
1196 1197 width: 25%;
1197 1198 min-width: 200px;
1198 1199 }
1199 1200 .reviewers {
1200 1201 width: 25%;
1201 1202 min-width: 200px;
1202 1203 }
1203 1204 .reviewers ul li {
1204 1205 position: relative;
1205 1206 width: 100%;
1206 1207 margin-bottom: 8px;
1207 1208 }
1208 1209 .reviewers_member {
1209 1210 width: 100%;
1210 1211 overflow: auto;
1211 1212 }
1212 1213 .reviewer_status {
1213 1214 display: inline-block;
1214 1215 vertical-align: top;
1215 1216 width: 7%;
1216 1217 min-width: 20px;
1217 1218 height: 1.2em;
1218 1219 margin-top: 3px;
1219 1220 line-height: 1em;
1220 1221 }
1221 1222
1222 1223 .reviewer_name {
1223 1224 display: inline-block;
1224 1225 max-width: 83%;
1225 1226 padding-right: 20px;
1226 1227 vertical-align: middle;
1227 1228 line-height: 1;
1228 1229
1229 1230 .rc-user {
1230 1231 min-width: 0;
1231 1232 margin: -2px 1em 0 0;
1232 1233 }
1233 1234
1234 1235 .reviewer {
1235 1236 float: left;
1236 1237 }
1237 1238
1238 1239 &.to-delete {
1239 1240 .user,
1240 1241 .reviewer {
1241 1242 text-decoration: line-through;
1242 1243 }
1243 1244 }
1244 1245 }
1245 1246
1246 1247 .reviewer_member_remove {
1247 1248 position: absolute;
1248 1249 right: 0;
1249 1250 top: 0;
1250 1251 width: 16px;
1251 1252 margin-bottom: 10px;
1252 1253 padding: 0;
1253 1254 color: black;
1254 1255 }
1255 1256 .reviewer_member_status {
1256 1257 margin-top: 5px;
1257 1258 }
1258 1259 .pr-summary #summary{
1259 1260 width: 100%;
1260 1261 }
1261 1262 .pr-summary .action_button:hover {
1262 1263 border: 0;
1263 1264 cursor: pointer;
1264 1265 }
1265 1266 .pr-details-title {
1266 1267 padding-bottom: 8px;
1267 1268 border-bottom: @border-thickness solid @grey5;
1268 1269 .action_button {
1269 1270 color: @rcblue;
1270 1271 }
1271 1272 }
1272 1273 .pr-details-content {
1273 1274 margin-top: @textmargin;
1274 1275 margin-bottom: @textmargin;
1275 1276 }
1276 1277 .pr-description {
1277 1278 white-space:pre-wrap;
1278 1279 }
1279 1280 .group_members {
1280 1281 margin-top: 0;
1281 1282 padding: 0;
1282 1283 list-style: outside none none;
1283 1284 }
1284 1285 .reviewer_ac .ac-input {
1285 1286 width: 92%;
1286 1287 margin-bottom: 1em;
1287 1288 }
1288 1289 #update_commits {
1289 1290 float: right;
1290 1291 }
1291 1292 .compare_view_commits tr{
1292 1293 height: 20px;
1293 1294 }
1294 1295 .compare_view_commits td {
1295 1296 vertical-align: top;
1296 1297 padding-top: 10px;
1297 1298 }
1298 1299 .compare_view_commits .author {
1299 1300 margin-left: 5px;
1300 1301 }
1301 1302
1302 1303 .compare_view_files {
1303 1304 width: 100%;
1304 1305
1305 1306 td {
1306 1307 vertical-align: middle;
1307 1308 }
1308 1309 }
1309 1310
1310 1311 .compare_view_filepath {
1311 1312 color: @grey1;
1312 1313 }
1313 1314
1314 1315 .show_more {
1315 1316 display: inline-block;
1316 1317 position: relative;
1317 1318 vertical-align: middle;
1318 1319 width: 4px;
1319 1320 height: @basefontsize;
1320 1321
1321 1322 &:after {
1322 1323 content: "\00A0\25BE";
1323 1324 display: inline-block;
1324 1325 width:10px;
1325 1326 line-height: 5px;
1326 1327 font-size: 12px;
1327 1328 cursor: pointer;
1328 1329 }
1329 1330 }
1330 1331
1331 1332 .journal_more .show_more {
1332 1333 display: inline;
1333 1334
1334 1335 &:after {
1335 1336 content: none;
1336 1337 }
1337 1338 }
1338 1339
1339 1340 .open .show_more:after,
1340 1341 .select2-dropdown-open .show_more:after {
1341 1342 .rotate(180deg);
1342 1343 margin-left: 4px;
1343 1344 }
1344 1345
1345 1346
1346 1347 .compare_view_commits .collapse_commit:after {
1347 1348 cursor: pointer;
1348 1349 content: "\00A0\25B4";
1349 1350 margin-left: -3px;
1350 1351 font-size: 17px;
1351 1352 color: @grey4;
1352 1353 }
1353 1354
1354 1355 .diff_links {
1355 1356 margin-left: 8px;
1356 1357 }
1357 1358
1358 1359 p.ancestor {
1359 1360 margin: @padding 0;
1360 1361 }
1361 1362
1362 1363 .cs_icon_td input[type="checkbox"] {
1363 1364 display: none;
1364 1365 }
1365 1366
1366 1367 .cs_icon_td .expand_file_icon:after {
1367 1368 cursor: pointer;
1368 1369 content: "\00A0\25B6";
1369 1370 font-size: 12px;
1370 1371 color: @grey4;
1371 1372 }
1372 1373
1373 1374 .cs_icon_td .collapse_file_icon:after {
1374 1375 cursor: pointer;
1375 1376 content: "\00A0\25BC";
1376 1377 font-size: 12px;
1377 1378 color: @grey4;
1378 1379 }
1379 1380
1380 1381 /*new binary
1381 1382 NEW_FILENODE = 1
1382 1383 DEL_FILENODE = 2
1383 1384 MOD_FILENODE = 3
1384 1385 RENAMED_FILENODE = 4
1385 1386 COPIED_FILENODE = 5
1386 1387 CHMOD_FILENODE = 6
1387 1388 BIN_FILENODE = 7
1388 1389 */
1389 1390 .cs_files_expand {
1390 1391 font-size: @basefontsize + 5px;
1391 1392 line-height: 1.8em;
1392 1393 float: right;
1393 1394 }
1394 1395
1395 1396 .cs_files_expand span{
1396 1397 color: @rcblue;
1397 1398 cursor: pointer;
1398 1399 }
1399 1400 .cs_files {
1400 1401 clear: both;
1401 1402 padding-bottom: @padding;
1402 1403
1403 1404 .cur_cs {
1404 1405 margin: 10px 2px;
1405 1406 font-weight: bold;
1406 1407 }
1407 1408
1408 1409 .node {
1409 1410 float: left;
1410 1411 }
1411 1412
1412 1413 .changes {
1413 1414 float: right;
1414 1415 color: white;
1415 1416 font-size: @basefontsize - 4px;
1416 1417 margin-top: 4px;
1417 1418 opacity: 0.6;
1418 1419 filter: Alpha(opacity=60); /* IE8 and earlier */
1419 1420
1420 1421 .added {
1421 1422 background-color: @alert1;
1422 1423 float: left;
1423 1424 text-align: center;
1424 1425 }
1425 1426
1426 1427 .deleted {
1427 1428 background-color: @alert2;
1428 1429 float: left;
1429 1430 text-align: center;
1430 1431 }
1431 1432
1432 1433 .bin {
1433 1434 background-color: @alert1;
1434 1435 text-align: center;
1435 1436 }
1436 1437
1437 1438 /*new binary*/
1438 1439 .bin.bin1 {
1439 1440 background-color: @alert1;
1440 1441 text-align: center;
1441 1442 }
1442 1443
1443 1444 /*deleted binary*/
1444 1445 .bin.bin2 {
1445 1446 background-color: @alert2;
1446 1447 text-align: center;
1447 1448 }
1448 1449
1449 1450 /*mod binary*/
1450 1451 .bin.bin3 {
1451 1452 background-color: @grey2;
1452 1453 text-align: center;
1453 1454 }
1454 1455
1455 1456 /*rename file*/
1456 1457 .bin.bin4 {
1457 1458 background-color: @alert4;
1458 1459 text-align: center;
1459 1460 }
1460 1461
1461 1462 /*copied file*/
1462 1463 .bin.bin5 {
1463 1464 background-color: @alert4;
1464 1465 text-align: center;
1465 1466 }
1466 1467
1467 1468 /*chmod file*/
1468 1469 .bin.bin6 {
1469 1470 background-color: @grey2;
1470 1471 text-align: center;
1471 1472 }
1472 1473 }
1473 1474 }
1474 1475
1475 1476 .cs_files .cs_added, .cs_files .cs_A,
1476 1477 .cs_files .cs_added, .cs_files .cs_M,
1477 1478 .cs_files .cs_added, .cs_files .cs_D {
1478 1479 height: 16px;
1479 1480 padding-right: 10px;
1480 1481 margin-top: 7px;
1481 1482 text-align: left;
1482 1483 }
1483 1484
1484 1485 .cs_icon_td {
1485 1486 min-width: 16px;
1486 1487 width: 16px;
1487 1488 }
1488 1489
1489 1490 .pull-request-merge {
1490 1491 padding: 10px 0;
1491 1492 margin-top: 10px;
1492 1493 margin-bottom: 20px;
1493 1494 }
1494 1495
1495 1496 .pull-request-merge .pull-request-wrap {
1496 1497 height: 25px;
1497 1498 padding: 5px 0;
1498 1499 }
1499 1500
1500 1501 .pull-request-merge span {
1501 1502 margin-right: 10px;
1502 1503 }
1503 1504 #close_pull_request {
1504 1505 margin-right: 0px;
1505 1506 }
1506 1507
1507 1508 .empty_data {
1508 1509 color: @grey4;
1509 1510 }
1510 1511
1511 1512 #changeset_compare_view_content {
1512 1513 margin-bottom: @space;
1513 1514 clear: both;
1514 1515 width: 100%;
1515 1516 box-sizing: border-box;
1516 1517 .border-radius(@border-radius);
1517 1518
1518 1519 .help-block {
1519 1520 margin: @padding 0;
1520 1521 color: @text-color;
1521 1522 }
1522 1523
1523 1524 .empty_data {
1524 1525 margin: @padding 0;
1525 1526 }
1526 1527
1527 1528 .alert {
1528 1529 margin-bottom: @space;
1529 1530 }
1530 1531 }
1531 1532
1532 1533 .table_disp {
1533 1534 .status {
1534 1535 width: auto;
1535 1536
1536 1537 .flag_status {
1537 1538 float: left;
1538 1539 }
1539 1540 }
1540 1541 }
1541 1542
1542 1543 .status_box_menu {
1543 1544 margin: 0;
1544 1545 }
1545 1546
1546 1547 .notification-table{
1547 1548 margin-bottom: @space;
1548 1549 display: table;
1549 1550 width: 100%;
1550 1551
1551 1552 .container{
1552 1553 display: table-row;
1553 1554
1554 1555 .notification-header{
1555 1556 border-bottom: @border-thickness solid @border-default-color;
1556 1557 }
1557 1558
1558 1559 .notification-subject{
1559 1560 display: table-cell;
1560 1561 }
1561 1562 }
1562 1563 }
1563 1564
1564 1565 // Notifications
1565 1566 .notification-header{
1566 1567 display: table;
1567 1568 width: 100%;
1568 1569 padding: floor(@basefontsize/2) 0;
1569 1570 line-height: 1em;
1570 1571
1571 1572 .desc, .delete-notifications, .read-notifications{
1572 1573 display: table-cell;
1573 1574 text-align: left;
1574 1575 }
1575 1576
1576 1577 .desc{
1577 1578 width: 1163px;
1578 1579 }
1579 1580
1580 1581 .delete-notifications, .read-notifications{
1581 1582 width: 35px;
1582 1583 min-width: 35px; //fixes when only one button is displayed
1583 1584 }
1584 1585 }
1585 1586
1586 1587 .notification-body {
1587 1588 .markdown-block,
1588 1589 .rst-block {
1589 1590 padding: @padding 0;
1590 1591 }
1591 1592
1592 1593 .notification-subject {
1593 1594 padding: @textmargin 0;
1594 1595 border-bottom: @border-thickness solid @border-default-color;
1595 1596 }
1596 1597 }
1597 1598
1598 1599
1599 1600 .notifications_buttons{
1600 1601 float: right;
1601 1602 }
1602 1603
1603 1604 // Repositories
1604 1605
1605 1606 #summary.fields{
1606 1607 display: table;
1607 1608
1608 1609 .field{
1609 1610 display: table-row;
1610 1611
1611 1612 .label-summary{
1612 1613 display: table-cell;
1613 1614 min-width: @label-summary-minwidth;
1614 1615 padding-top: @padding/2;
1615 1616 padding-bottom: @padding/2;
1616 1617 padding-right: @padding/2;
1617 1618 }
1618 1619
1619 1620 .input{
1620 1621 display: table-cell;
1621 1622 padding: @padding/2;
1622 1623
1623 1624 input{
1624 1625 min-width: 29em;
1625 1626 padding: @padding/4;
1626 1627 }
1627 1628 }
1628 1629 .statistics, .downloads{
1629 1630 .disabled{
1630 1631 color: @grey4;
1631 1632 }
1632 1633 }
1633 1634 }
1634 1635 }
1635 1636
1636 1637 #summary{
1637 1638 width: 70%;
1638 1639 }
1639 1640
1640 1641
1641 1642 // Journal
1642 1643 .journal.title {
1643 1644 h5 {
1644 1645 float: left;
1645 1646 margin: 0;
1646 1647 width: 70%;
1647 1648 }
1648 1649
1649 1650 ul {
1650 1651 float: right;
1651 1652 display: inline-block;
1652 1653 margin: 0;
1653 1654 width: 30%;
1654 1655 text-align: right;
1655 1656
1656 1657 li {
1657 1658 display: inline;
1658 1659 font-size: @journal-fontsize;
1659 1660 line-height: 1em;
1660 1661
1661 1662 &:before { content: none; }
1662 1663 }
1663 1664 }
1664 1665 }
1665 1666
1666 1667 .filterexample {
1667 1668 position: absolute;
1668 1669 top: 95px;
1669 1670 left: @contentpadding;
1670 1671 color: @rcblue;
1671 1672 font-size: 11px;
1672 1673 font-family: @text-regular;
1673 1674 cursor: help;
1674 1675
1675 1676 &:hover {
1676 1677 color: @rcdarkblue;
1677 1678 }
1678 1679
1679 1680 @media (max-width:768px) {
1680 1681 position: relative;
1681 1682 top: auto;
1682 1683 left: auto;
1683 1684 display: block;
1684 1685 }
1685 1686 }
1686 1687
1687 1688
1688 1689 #journal{
1689 1690 margin-bottom: @space;
1690 1691
1691 1692 .journal_day{
1692 1693 margin-bottom: @textmargin/2;
1693 1694 padding-bottom: @textmargin/2;
1694 1695 font-size: @journal-fontsize;
1695 1696 border-bottom: @border-thickness solid @border-default-color;
1696 1697 }
1697 1698
1698 1699 .journal_container{
1699 1700 margin-bottom: @space;
1700 1701
1701 1702 .journal_user{
1702 1703 display: inline-block;
1703 1704 }
1704 1705 .journal_action_container{
1705 1706 display: block;
1706 1707 margin-top: @textmargin;
1707 1708
1708 1709 div{
1709 1710 display: inline;
1710 1711 }
1711 1712
1712 1713 div.journal_action_params{
1713 1714 display: block;
1714 1715 }
1715 1716
1716 1717 div.journal_repo:after{
1717 1718 content: "\A";
1718 1719 white-space: pre;
1719 1720 }
1720 1721
1721 1722 div.date{
1722 1723 display: block;
1723 1724 margin-bottom: @textmargin;
1724 1725 }
1725 1726 }
1726 1727 }
1727 1728 }
1728 1729
1729 1730 // Files
1730 1731 .edit-file-title {
1731 1732 border-bottom: @border-thickness solid @border-default-color;
1732 1733
1733 1734 .breadcrumbs {
1734 1735 margin-bottom: 0;
1735 1736 }
1736 1737 }
1737 1738
1738 1739 .edit-file-fieldset {
1739 1740 margin-top: @sidebarpadding;
1740 1741
1741 1742 .fieldset {
1742 1743 .left-label {
1743 1744 width: 13%;
1744 1745 }
1745 1746 .right-content {
1746 1747 width: 87%;
1747 1748 max-width: 100%;
1748 1749 }
1749 1750 .filename-label {
1750 1751 margin-top: 13px;
1751 1752 }
1752 1753 .commit-message-label {
1753 1754 margin-top: 4px;
1754 1755 }
1755 1756 .file-upload-input {
1756 1757 input {
1757 1758 display: none;
1758 1759 }
1759 1760 }
1760 1761 p {
1761 1762 margin-top: 5px;
1762 1763 }
1763 1764
1764 1765 }
1765 1766 .custom-path-link {
1766 1767 margin-left: 5px;
1767 1768 }
1768 1769 #commit {
1769 1770 resize: vertical;
1770 1771 }
1771 1772 }
1772 1773
1773 1774 .delete-file-preview {
1774 1775 max-height: 250px;
1775 1776 }
1776 1777
1777 1778 .new-file,
1778 1779 #filter_activate,
1779 1780 #filter_deactivate {
1780 1781 float: left;
1781 1782 margin: 0 0 0 15px;
1782 1783 }
1783 1784
1784 1785 h3.files_location{
1785 1786 line-height: 2.4em;
1786 1787 }
1787 1788
1788 1789 .browser-nav {
1789 1790 display: table;
1790 1791 margin-bottom: @space;
1791 1792
1792 1793
1793 1794 .info_box {
1794 1795 display: inline-table;
1795 1796 height: 2.5em;
1796 1797
1797 1798 .browser-cur-rev, .info_box_elem {
1798 1799 display: table-cell;
1799 1800 vertical-align: middle;
1800 1801 }
1801 1802
1802 1803 .info_box_elem {
1803 1804 border-top: @border-thickness solid @rcblue;
1804 1805 border-bottom: @border-thickness solid @rcblue;
1805 1806
1806 1807 #at_rev, a {
1807 1808 padding: 0.6em 0.9em;
1808 1809 margin: 0;
1809 1810 .box-shadow(none);
1810 1811 border: 0;
1811 1812 height: 12px;
1812 1813 }
1813 1814
1814 1815 input#at_rev {
1815 1816 max-width: 50px;
1816 1817 text-align: right;
1817 1818 }
1818 1819
1819 1820 &.previous {
1820 1821 border: @border-thickness solid @rcblue;
1821 1822 .disabled {
1822 1823 color: @grey4;
1823 1824 cursor: not-allowed;
1824 1825 }
1825 1826 }
1826 1827
1827 1828 &.next {
1828 1829 border: @border-thickness solid @rcblue;
1829 1830 .disabled {
1830 1831 color: @grey4;
1831 1832 cursor: not-allowed;
1832 1833 }
1833 1834 }
1834 1835 }
1835 1836
1836 1837 .browser-cur-rev {
1837 1838
1838 1839 span{
1839 1840 margin: 0;
1840 1841 color: @rcblue;
1841 1842 height: 12px;
1842 1843 display: inline-block;
1843 1844 padding: 0.7em 1em ;
1844 1845 border: @border-thickness solid @rcblue;
1845 1846 margin-right: @padding;
1846 1847 }
1847 1848 }
1848 1849 }
1849 1850
1850 1851 .search_activate {
1851 1852 display: table-cell;
1852 1853 vertical-align: middle;
1853 1854
1854 1855 input, label{
1855 1856 margin: 0;
1856 1857 padding: 0;
1857 1858 }
1858 1859
1859 1860 input{
1860 1861 margin-left: @textmargin;
1861 1862 }
1862 1863
1863 1864 }
1864 1865 }
1865 1866
1866 1867 .file_author{
1867 1868 margin-bottom: @padding;
1868 1869
1869 1870 div{
1870 1871 display: inline-block;
1871 1872 margin-right: 0.5em;
1872 1873 }
1873 1874 }
1874 1875
1875 1876 .browser-cur-rev{
1876 1877 margin-bottom: @textmargin;
1877 1878 }
1878 1879
1879 1880 #node_filter_box_loading{
1880 1881 .info_text;
1881 1882 }
1882 1883
1883 1884 .browser-search {
1884 1885 margin: -25px 0px 5px 0px;
1885 1886 }
1886 1887
1887 1888 .node-filter {
1888 1889 font-size: @repo-title-fontsize;
1889 1890 padding: 4px 0px 0px 0px;
1890 1891
1891 1892 .node-filter-path {
1892 1893 float: left;
1893 1894 color: @grey4;
1894 1895 }
1895 1896 .node-filter-input {
1896 1897 float: left;
1897 1898 margin: -2px 0px 0px 2px;
1898 1899 input {
1899 1900 padding: 2px;
1900 1901 border: none;
1901 1902 font-size: @repo-title-fontsize;
1902 1903 }
1903 1904 }
1904 1905 }
1905 1906
1906 1907
1907 1908 .browser-result{
1908 1909 td a{
1909 1910 margin-left: 0.5em;
1910 1911 display: inline-block;
1911 1912
1912 1913 em{
1913 1914 font-family: @text-bold;
1914 1915 }
1915 1916 }
1916 1917 }
1917 1918
1918 1919 .browser-highlight{
1919 1920 background-color: @grey5-alpha;
1920 1921 }
1921 1922
1922 1923
1923 1924 // Search
1924 1925
1925 1926 .search-form{
1926 1927 #q {
1927 1928 width: @search-form-width;
1928 1929 }
1929 1930 .fields{
1930 1931 margin: 0 0 @space;
1931 1932 }
1932 1933
1933 1934 label{
1934 1935 display: inline-block;
1935 1936 margin-right: @textmargin;
1936 1937 padding-top: 0.25em;
1937 1938 }
1938 1939
1939 1940
1940 1941 .results{
1941 1942 clear: both;
1942 1943 margin: 0 0 @padding;
1943 1944 }
1944 1945 }
1945 1946
1946 1947 div.search-feedback-items {
1947 1948 display: inline-block;
1948 1949 padding:0px 0px 0px 96px;
1949 1950 }
1950 1951
1951 1952 div.search-code-body {
1952 1953 background-color: #ffffff; padding: 5px 0 5px 10px;
1953 1954 pre {
1954 1955 .match { background-color: #faffa6;}
1955 1956 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
1956 1957 }
1957 1958 }
1958 1959
1959 1960 .expand_commit.search {
1960 1961 .show_more.open {
1961 1962 height: auto;
1962 1963 max-height: none;
1963 1964 }
1964 1965 }
1965 1966
1966 1967 .search-results {
1967 1968
1968 1969 h2 {
1969 1970 margin-bottom: 0;
1970 1971 }
1971 1972 .codeblock {
1972 1973 border: none;
1973 1974 background: transparent;
1974 1975 }
1975 1976
1976 1977 .codeblock-header {
1977 1978 border: none;
1978 1979 background: transparent;
1979 1980 }
1980 1981
1981 1982 .code-body {
1982 1983 border: @border-thickness solid @border-default-color;
1983 1984 .border-radius(@border-radius);
1984 1985 }
1985 1986
1986 1987 .td-commit {
1987 1988 &:extend(pre);
1988 1989 border-bottom: @border-thickness solid @border-default-color;
1989 1990 }
1990 1991
1991 1992 .message {
1992 1993 height: auto;
1993 1994 max-width: 350px;
1994 1995 white-space: normal;
1995 1996 text-overflow: initial;
1996 1997 overflow: visible;
1997 1998
1998 1999 .match { background-color: #faffa6;}
1999 2000 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2000 2001 }
2001 2002
2002 2003 }
2003 2004
2004 2005 table.rctable td.td-search-results div {
2005 2006 max-width: 100%;
2006 2007 }
2007 2008
2008 2009 #tip-box, .tip-box{
2009 2010 padding: @menupadding/2;
2010 2011 display: block;
2011 2012 border: @border-thickness solid @border-highlight-color;
2012 2013 .border-radius(@border-radius);
2013 2014 background-color: white;
2014 2015 z-index: 99;
2015 2016 white-space: pre-wrap;
2016 2017 }
2017 2018
2018 2019 #linktt {
2019 2020 width: 79px;
2020 2021 }
2021 2022
2022 2023 #help_kb .modal-content{
2023 2024 max-width: 750px;
2024 2025 margin: 10% auto;
2025 2026
2026 2027 table{
2027 2028 td,th{
2028 2029 border-bottom: none;
2029 2030 line-height: 2.5em;
2030 2031 }
2031 2032 th{
2032 2033 padding-bottom: @textmargin/2;
2033 2034 }
2034 2035 td.keys{
2035 2036 text-align: center;
2036 2037 }
2037 2038 }
2038 2039
2039 2040 .block-left{
2040 2041 width: 45%;
2041 2042 margin-right: 5%;
2042 2043 }
2043 2044 .modal-footer{
2044 2045 clear: both;
2045 2046 }
2046 2047 .key.tag{
2047 2048 padding: 0.5em;
2048 2049 background-color: @rcblue;
2049 2050 color: white;
2050 2051 border-color: @rcblue;
2051 2052 .box-shadow(none);
2052 2053 }
2053 2054 }
2054 2055
2055 2056
2056 2057
2057 2058 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2058 2059
2059 2060 @import 'statistics-graph';
2060 2061 @import 'tables';
2061 2062 @import 'forms';
2062 2063 @import 'diff';
2063 2064 @import 'summary';
2064 2065 @import 'navigation';
2065 2066
2066 2067 //--- SHOW/HIDE SECTIONS --//
2067 2068
2068 2069 .btn-collapse {
2069 2070 float: right;
2070 2071 text-align: right;
2071 2072 font-family: @text-light;
2072 2073 font-size: @basefontsize;
2073 2074 cursor: pointer;
2074 2075 border: none;
2075 2076 color: @rcblue;
2076 2077 }
2077 2078
2078 2079 table.rctable,
2079 2080 table.dataTable {
2080 2081 .btn-collapse {
2081 2082 float: right;
2082 2083 text-align: right;
2083 2084 }
2084 2085 }
2085 2086
2086 2087
2087 2088 // TODO: johbo: Fix for IE10, this avoids that we see a border
2088 2089 // and padding around checkboxes and radio boxes. Move to the right place,
2089 2090 // or better: Remove this once we did the form refactoring.
2090 2091 input[type=checkbox],
2091 2092 input[type=radio] {
2092 2093 padding: 0;
2093 2094 border: none;
2094 2095 }
@@ -1,623 +1,628 b''
1 1 // navigation.less
2 2 // For use in RhodeCode applications;
3 3 // see style guide documentation for guidelines.
4 4
5 5 // HEADER NAVIGATION
6 6
7 7 .horizontal-list {
8 8 float: right;
9 9 display: block;
10 10 margin: 0;
11 11 padding: 0;
12 12 -webkit-padding-start: 0;
13 13 text-align: left;
14 14 font-size: @navigation-fontsize;
15 15 color: @grey6;
16 16 z-index:10;
17 17
18 18 li {
19 19 line-height: 1em;
20 20 list-style-type: none;
21 21
22 22 a {
23 23 padding: 0 .5em;
24 24
25 25 &.menu_link_notifications {
26 26 .pill(7px,@rcblue);
27 27 display: inline;
28 28 margin: 0 7px 0 .7em;
29 29 font-size: @basefontsize;
30 30 color: white;
31
31
32 32 &.empty {
33 33 background-color: @grey4;
34 34 }
35 35
36 36 &:hover {
37 37 background-color: @rcdarkblue;
38 38 }
39 39 }
40 40 }
41 41 .pill_container {
42 42 margin: 1.25em 0px 0px 0px;
43 43 float: right;
44 44 }
45 45
46 46 &#quick_login_li {
47 47 &:hover {
48 48 color: @grey5;
49 49 }
50 50
51 51 a.menu_link_notifications {
52 52 color: white;
53 53 }
54 54
55 55 .user {
56 56 padding-bottom: 10px;
57 57 }
58 58
59 59 &.open {
60 60 .user {
61 61 border-bottom: 5px solid @rcblue;
62 62 }
63 63 }
64 64 }
65 65
66 66 &:before { content: none; }
67 67
68 68 &:last-child {
69 69 .menulabel {
70 70 padding-right: 0;
71 71 border-right: none;
72 72
73 73 .show_more {
74 74 padding-right: 0;
75 75 }
76 76 }
77 77
78 78 &> a {
79 79 border-bottom: none;
80 80 }
81 81 }
82 82
83 83 &.active {
84 84 border-bottom: 5px solid @rcblue;
85 85 }
86 86
87 87 &.open {
88 88
89 89 a {
90 90 color: white;
91 91 }
92 92 }
93 93
94 94 &:focus {
95 95 outline: none;
96 96 }
97
97
98 98 ul li {
99 99 display: block;
100 100
101 101 &:last-child> a {
102 102 border-bottom: none;
103 103 }
104 104
105 105 ul li:last-child a {
106 106 /* we don't expect more then 3 levels of submenu and the third
107 107 level can have different html structure */
108 108 border-bottom: none;
109 109 }
110 110 }
111 111 }
112 112
113 113 > li {
114 114 float: left;
115 115 display: block;
116 116 padding: 0;
117 117
118 118 > a,
119 119 &.has_select2 a {
120 120 display: block;
121 121 padding: 10px 0 2px;
122 122
123 123 .show_more {
124 124 margin-top: -4px;
125 125 padding-right: .5em;
126 126 }
127 127 }
128 128
129 129 .menulabel {
130 130 padding: 0 .5em;
131 131 line-height: 1em;
132 132 // for this specifically we do not use a variable
133 133 border-right: 1px solid @grey4;
134 134 }
135 135
136 136 .pr_notifications {
137 137 padding-left: .5em;
138 138 }
139 139
140 140 .pr_notifications + .menulabel {
141 141 display:inline;
142 142 padding-left: 0;
143 143 }
144 144
145 145 &:hover,
146 146 &.open,
147 147 &.active {
148 a {
148 a {
149 149 color: @grey1;
150 150 }
151 151 }
152 152 }
153 153
154 154 pre {
155 155 margin: 0;
156 156 padding: 0;
157 157 }
158 158
159 159 .select2-container,
160 160 .menulink.childs {
161 161 position: relative;
162 162 }
163 163
164 164 #quick_login {
165 165
166 166 li a {
167 167 padding: .5em 0;
168 168 border-bottom: none;
169 169 color: @grey2;
170 170
171 171 &:hover { color: @grey1; }
172 172 }
173 173
174 174 .show_more {
175 175 padding-left: .5em;
176 176 }
177 177 }
178 178
179 179 #quick_login_link {
180 180 display: inline-block;
181 181
182 182 .gravatar {
183 183 border: 1px solid @grey2;
184 184 }
185 185
186 186 .gravatar-login {
187 187 height: 20px;
188 188 width: 20px;
189 189 margin: -8px 0;
190 190 padding: 0;
191 191 }
192 192
193 193 &:hover .user {
194 194 color: @grey6;
195 195 }
196 196 }
197 197 }
198 198 .header .horizontal-list {
199 199
200 200 li {
201 201
202 202 &#quick_login_li {
203 203 padding-left: .5em;
204 204
205 205 &:hover #quick_login_link {
206 206 color: inherit;
207 207 }
208 208 }
209 209
210 210 &:before { content: none; }
211 211 }
212 212
213 213 > li {
214 214
215 215 a {
216 216 padding: 18px 0 12px 0;
217 217 color: @nav-grey;
218 218
219 219 &.menu_link_notifications {
220 220 padding: 1px 8px;
221 221 }
222 222 }
223 223
224 224 &:hover,
225 225 &.open,
226 226 &.active {
227 227 .pill_container a {
228 228 // don't select text for the pill container, it has it' own
229 229 // hover behaviour
230 230 color: @nav-grey;
231 231 }
232 232 }
233 233
234 234 &:hover,
235 235 &.open,
236 236 &.active {
237 237 a {
238 238 color: @grey6;
239 239 }
240 240 }
241 241
242 242 .select2-dropdown-open a {
243 243 color: @grey6;
244 244 }
245 245
246 246 .repo-switcher {
247 247 padding-left: 0;
248 248
249 249 .menulabel {
250 250 padding-left: 0;
251 251 }
252 252 }
253 253 }
254 254
255 255 li ul li {
256 256 background-color:@grey2;
257 257
258 258 a {
259 259 padding: .5em 0;
260 260 border-bottom: @border-thickness solid @border-default-color;
261 261 color: @grey6;
262 262 }
263 263
264 264 &:last-child a, &.last a{
265 265 border-bottom: none;
266 266 }
267 267
268 268 &:hover {
269 269 background-color: @grey3;
270 270 }
271 271 }
272 272
273 273 .submenu {
274 274 margin-top: 5px;
275 275 }
276 276 }
277 277
278 278 // SUBMENUS
279 279 .navigation .submenu {
280 280 display: none;
281 281 }
282 282
283 283 .navigation li.open {
284 284
285 285 .submenu,
286 286 .repo_switcher {
287 287 display: block;
288 288 }
289 289 }
290 290
291 .navigation li:last-child .submenu {
292 right: -20px;
293 left: auto;
294 }
295
291 296 .submenu {
292 297 position: absolute;
293 298 top: 100%;
294 299 left: 0;
295 300 min-width: 150px;
296 301 margin: 6px 0 0;
297 302 padding: 0;
298 303 text-align: left;
299 304 font-family: @text-light;
300 305 border-radius: @border-radius;
301 306 z-index: 20;
302 307
303 308 li {
304 309 display: block;
305 310 margin: 0;
306 311 padding: 0 .5em;
307 312 line-height: 1em;
308 313 color: @grey3;
309 314 background-color: @grey6;
310 315
311 316 &:before { content: none; }
312 317
313 318 a {
314 319 display: block;
315 320 width: 100%;
316 321 padding: .5em 0;
317 322 border-right: none;
318 323 border-bottom: @border-thickness solid white;
319 324 color: @grey3;
320 325 }
321 326
322 327 ul {
323 328 display: none;
324 329 position: absolute;
325 330 top: 0;
326 331 right: 100%;
327 332 padding: 0;
328 333 z-index: 30;
329 334 }
330 335 &:hover {
331 336 background-color: @grey5;
332 337 -webkit-transition: background .3s;
333 338 -moz-transition: background .3s;
334 339 -o-transition: background .3s;
335 340 transition: background .3s;
336
341
337 342 ul {
338 343 display: block;
339 344 }
340 345 }
341 346 }
342 347 }
343 348
344 349
345 350
346 351
347 352 // repo dropdown
348 353 .quick_repo_menu {
349 354 width: 15px;
350 355 text-align: center;
351 356 position: relative;
352 357 cursor: pointer;
353 358
354 359 div {
355 360 overflow: visible !important;
356 361 }
357 362
358 363 &.sorting {
359 364 cursor: auto;
360 365 }
361 366
362 367 &:hover {
363 368 .menu_items_container {
364 369 position: absolute;
365 370 display: block;
366 371 }
367 372 .menu_items {
368 373 display: block;
369 374 }
370 375 }
371 376
372 377 i {
373 378 margin: 0;
374 379 color: @grey4;
375 380 }
376 381
377 382 .menu_items_container {
378 383 position: absolute;
379 384 top: 0;
380 385 left: 100%;
381 386 margin: 0;
382 387 padding: 0;
383 388 list-style: none;
384 389 background-color: @grey6;
385 390 z-index: 999;
386 391 text-align: left;
387 392
388 393 a {
389 394 color: @grey2;
390 395 }
391 396
392 397 ul.menu_items {
393 398 margin: 0;
394 399 padding: 0;
395 400 }
396 401
397 402 li {
398 403 margin: 0;
399 404 padding: 0;
400 405 line-height: 1em;
401 406 list-style-type: none;
402 407
403 408 &:before { content: none; }
404 409
405 410 a {
406 411 display: block;
407 412 height: 16px;
408 413 padding: 8px; //must add up to td height (28px)
409 414
410 415 &:hover {
411 416 background-color: @grey5;
412 417 -webkit-transition: background .3s;
413 418 -moz-transition: background .3s;
414 419 -o-transition: background .3s;
415 420 transition: background .3s;
416 421 }
417 422 }
418 423 }
419 424 }
420 425 }
421 426
422 427 // Header Repository Switcher
423 428 // Select2 Dropdown
424 429 #select2-drop.select2-drop.repo-switcher-dropdown {
425 430 width: auto !important;
426 431 margin-top: 5px;
427 432 padding: 1em 0;
428 433 text-align: left;
429 434 .border-radius-bottom(@border-radius);
430 435 border-color: transparent;
431 436 color: @grey6;
432 437 background-color: @grey2;
433 438
434 439 input {
435 440 min-width: 90%;
436 441 }
437 442
438 443 ul.select2-result-sub {
439 444
440 445 li {
441 446 line-height: 1em;
442 447
443 448 &:hover,
444 449 &.select2-highlighted {
445 450 background-color: @grey3;
446 451 }
447 452 }
448 453
449 454 &:before { content: none; }
450 455 }
451 456
452 457 ul.select2-results {
453 458 min-width: 200px;
454 459 margin: 0;
455 460 padding: 0;
456 461 list-style-type: none;
457 462 overflow-x: visible;
458 463 overflow-y: scroll;
459 464
460 465 li {
461 466 padding: 0 8px;
462 467 line-height: 1em;
463 468 color: @grey6;
464 469
465 470 &:before { content: none; }
466 471
467 472 &>.select2-result-label {
468 473 padding: 8px 0;
469 474 border-bottom: @border-thickness solid @grey3;
470 475 white-space: nowrap;
471 476 color: @grey5;
472 477 cursor: pointer;
473 478 }
474 479
475 480 &.select2-result-with-children {
476 481 margin: 0;
477 482 padding: 0;
478 483 }
479 484
480 485 &.select2-result-unselectable > .select2-result-label {
481 486 margin: 0 8px;
482 487 }
483
488
484 489 }
485 490 }
486 491
487 492 ul.select2-result-sub {
488 493 margin: 0;
489 494 padding: 0;
490 495
491 496 li {
492 497 display: block;
493 498 margin: 0;
494 499 border-right: none;
495 500 line-height: 1em;
496 501 font-family: @text-light;
497 502 color: @grey2;
498 503
499 504 &:before { content: none; }
500 505
501 506 &:hover {
502 507 background-color: @grey3;
503 508 }
504 509 }
505 510 }
506 511 }
507 512
508 513
509 514 #context-bar {
510 515 display: block;
511 516 margin: 0 auto;
512 517 padding: 0 @header-padding;
513 518 background-color: @grey6;
514 519 border-bottom: @border-thickness solid @grey5;
515 520
516 521 .clear {
517 522 clear: both;
518 523 }
519 524 }
520 525
521 526 ul#context-pages {
522 527 li {
523 528 line-height: 1em;
524 529
525 530 &:before { content: none; }
526 531
527 532 a {
528 533 color: @grey3;
529 534 }
530 535
531 536 &.active {
532 537 // special case, non-variable color
533 538 border-bottom: 4px solid @nav-grey;
534 539
535 540 a {
536 541 color: @grey1;
537 542 }
538 543 }
539 544 }
540 545 }
541 546
542 547 // PAGINATION
543 548
544 549 .pagination {
545 550 border: @border-thickness solid @rcblue;
546 551 color: @rcblue;
547 552
548 553 .current {
549 554 color: @grey4;
550 555 }
551 556 }
552 557
553 558 .dataTables_paginate, .pagination-wh {
554 559 text-align: left;
555 560 display: inline-block;
556 561 border-left: 1px solid @rcblue;
557 562 float: none;
558 563 overflow: hidden;
559 564
560 565 .paginate_button, .pager_curpage,
561 566 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
562 567 display: inline-block;
563 568 padding: @menupadding/4 @menupadding;
564 569 border: 1px solid @rcblue;
565 570 border-left: 0;
566 571 color: @rcblue;
567 572 cursor: pointer;
568 573 float: left;
569 574 }
570 575
571 576 .pager_curpage, .pager_dotdot,
572 577 .paginate_button.current, .paginate_button.disabled,
573 578 .disabled {
574 579 color: @grey3;
575 580 cursor: default;
576 581 }
577 582 }
578 583
579 584 // SIDEBAR
580 585
581 586 .sidebar {
582 587 .block-left;
583 588 clear: left;
584 589 max-width: @sidebar-width;
585 590 margin-right: @sidebarpadding;
586 591 padding-right: @sidebarpadding;
587 592 font-family: @text-regular;
588 593 color: @grey1;
589 594
590 595 &#graph_nodes {
591 596 clear:both;
592 597 width: auto;
593 598 margin-left: -100px;
594 599 padding: 0;
595 600 border: none;
596 601 }
597
602
598 603 .nav-pills {
599 604 margin: 0;
600 605 }
601 606
602 607 .nav {
603 608 list-style: none;
604 609 padding: 0;
605 610
606 611 li {
607 612 padding-bottom: @menupadding;
608 613 line-height: 1em;
609 614 color: @grey4;
610 615
611 616 &.active a {
612 617 color: @grey2;
613 618 }
614 619
615 620 a {
616 621 color: @grey4;
617 622 }
618 623
619 624 &:before { content: none; }
620 625 }
621 626
622 627 }
623 628 }
@@ -1,87 +1,85 b''
1 1 // See panels-bootstrap.less
2 2 // These provide overrides for custom styling of Bootstrap panels
3 3
4 4 .panel {
5 5 &:extend(.clearfix);
6 6
7 7 width: 100%;
8 8 margin: 0 0 25px 0;
9 border-color: @grey5;
10 9 .border-radius(@border-radius);
11 10 .box-shadow(none);
12 11
13 12 .permalink {
14 13 visibility: hidden;
15 14 }
16 15
17 16 &:hover .permalink {
18 17 visibility: visible;
19 18 color: @rcblue;
20 19 }
21 20
22 21 .panel-heading {
23 22 position: relative;
24 23 min-height: 1em;
25 24 padding: @padding @panel-padding;
26 background-color: @grey6;
27 25 border-bottom: none;
28 26
29 27 .panel-title,
30 28 h3.panel-title {
31 29 float: left;
32 30 padding: 0 @padding 0 0;
33 31 line-height: 1;
34 32 font-size: @panel-title;
35 33 color: @grey1;
36 34 }
37 35
38 36 .panel-edit {
39 37 float: right;
40 38 line-height: 1;
41 39 font-size: @panel-title;
42 40 }
43 41 }
44 42
45 43 .panel-body {
46 44 padding: @panel-padding;
47 45 }
48 46
49 47 .panel-footer {
50 48 background-color: white;
51 49 padding: .65em @panel-padding .5em;
52 50 font-size: @panel-footer;
53 51 color: @text-muted;
54 52 }
55 53
56 54 .q_filter_box {
57 55 min-width: 40%;
58 56 }
59 57
60 58 // special cases
61 59 &.user-profile {
62 60 float: left;
63 61
64 62 .panel-heading {
65 63 margin-bottom: @padding;
66 64 }
67 65
68 66 .panel-body {
69 67 &:extend(.clearfix);
70 68 }
71 69 }
72 70 }
73 71
74 72 .main-content h3.panel-title {
75 73 font-size: @panel-title;
76 74 color: @grey1;
77 75 }
78 76
79 77 .panel-body-title-text {
80 78 margin: 0 0 20px 0;
81 79 }
82 80
83 81 // play nice with the current form and field css
84 82 .field.panel-default,
85 83 .form.panel-default {
86 84 width: auto;
87 85 } No newline at end of file
@@ -1,42 +1,48 b''
1 1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
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 Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 * autocomplete formatter that uses gravatar
21 21 * */
22 var autocompleteFormatResult = function(data, value, org_formatter) {
23 var value_display = data.value_display;
22 var autocompleteFormatResult = function (data, value, org_formatter) {
23 var activeUser = data.active || true;
24 var valueDisplay = data.value_display;
25
26 if (!activeUser) {
27 valueDisplay = '<strong>(disabled)</strong> ' + valueDisplay;
28 }
29
24 30 var escapeRegExChars = function (value) {
25 31 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
26 32 };
27 33 var pattern = '(' + escapeRegExChars(value) + ')';
28 value_display = value_display.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
34 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
29 35 var tmpl = '<div class="ac-container-wrap"><img class="gravatar" src="{0}"/>{1}</div>';
30 36 if (data.icon_link === "") {
31 37 tmpl = '<div class="ac-container-wrap">{0}</div>';
32 return tmpl.format(value_display);
38 return tmpl.format(valueDisplay);
33 39 }
34 return tmpl.format(data.icon_link, value_display);
40 return tmpl.format(data.icon_link, valueDisplay);
35 41 };
36 42
37 43 /**
38 44 * autocomplete filter that uses display value to filter
39 45 */
40 46 var autocompleteFilterResult = function (suggestion, originalQuery, queryLowerCase) {
41 47 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
42 48 };
@@ -1,85 +1,153 b''
1 <%namespace name="base" file="/base/base.html"/>
1 2
2 3 <div class="panel panel-default">
3 4 <div class="panel-body">
4 <div class="field">
5 5 %if c.show_closed:
6 6 ${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))}
7 7 %else:
8 8 ${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))}
9 9 %endif
10 </div>
11 10 </div>
12 11 </div>
13 12
14 13 <div class="panel panel-default">
15 14 <div class="panel-heading">
16 15 <h3 class="panel-title">${_('Pull Requests You Opened')}</h3>
17 16 </div>
18
19 17 <div class="panel-body">
20 18 <div class="pullrequestlist">
21 19 %if c.my_pull_requests:
20 <table class="rctable">
21 <thead>
22 <th class="td-status"></th>
23 <th>${_('Target Repo')}</th>
24 <th>${_('Author')}</th>
25 <th></th>
26 <th>${_('Title')}</th>
27 <th class="td-time">${_('Opened On')}</th>
28 <th></th>
29 </thead>
22 30 %for pull_request in c.my_pull_requests:
23 <div class="${'closed' if pull_request.is_closed() else ''} prwrapper">
24 <div class="pr">
31 <tr class="${'closed' if pull_request.is_closed() else ''} prwrapper">
32 <td class="td-status">
25 33 <div class="${'flag_status %s' % pull_request.calculated_review_status()} pull-left"></div>
26 <a href="${h.url('pullrequest_show',repo_name=pull_request.target_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">
27 ${_('Pull request #%s opened on %s') % (pull_request.pull_request_id, h.format_date(pull_request.created_on))}
28 %if pull_request.is_closed():
29 (${_('Closed')})
30 %endif
31 </a>
32 <div class="repolist_actions">
33 ${h.secure_form(url('pullrequest_delete', repo_name=pull_request.target_repo.repo_name, pull_request_id=pull_request.pull_request_id),method='delete')}
34 ${h.submit('remove_%s' % pull_request.pull_request_id, _('Delete'),
35 class_="btn btn-link btn-danger",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
36 ${h.end_form()}
34 </td>
35 <td class="td-componentname">
36 ${h.link_to(pull_request.target_repo.repo_name,h.url('summary_home',repo_name=pull_request.target_repo.repo_name))}
37 </td>
38 <td class="user">
39 ${base.gravatar_with_user(pull_request.author.email, 16)}
40 </td>
41 <td class="td-message expand_commit" data-pr-id="m${pull_request.pull_request_id}" title="${_('Expand commit message')}">
42 <div class="show_more_col">
43 <i class="show_more"></i>&nbsp;
37 44 </div>
38 </div>
39 </div>
45 </td>
46 <td class="mid td-description">
47 <div class="log-container truncate-wrap">
48 <div class="message truncate" id="c-m${pull_request.pull_request_id}"><a href="${h.url('pullrequest_show',repo_name=pull_request.target_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">#${pull_request.pull_request_id}: ${pull_request.title}</a>\
49 %if pull_request.is_closed():
50 &nbsp;(${_('Closed')})\
51 %endif
52 <br/>${pull_request.description}</div>
53 </div>
54 </td>
55
56 <td class="td-time">
57 ${h.age_component(pull_request.created_on)}
58 </td>
59 <td class="td-action repolist_actions">
60 ${h.secure_form(url('pullrequest_delete', repo_name=pull_request.target_repo.repo_name, pull_request_id=pull_request.pull_request_id),method='delete')}
61 ${h.submit('remove_%s' % pull_request.pull_request_id, _('Delete'),
62 class_="btn btn-link btn-danger",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
63 ${h.end_form()}
64 </td>
65 </tr>
40 66 %endfor
67 </table>
41 68 %else:
42 69 <h2><span class="empty_data">${_('You currently have no open pull requests.')}</span></h2>
43 70 %endif
44 71 </div>
45 72 </div>
46 73 </div>
47 74
48 75 <div class="panel panel-default">
49 76 <div class="panel-heading">
50 77 <h3 class="panel-title">${_('Pull Requests You Participate In')}</h3>
51 78 </div>
52 79
53 80 <div class="panel-body">
54 81 <div class="pullrequestlist">
55 82 %if c.participate_in_pull_requests:
83 <table class="rctable">
84 <thead>
85 <th class="td-status"></th>
86 <th>${_('Target Repo')}</th>
87 <th>${_('Author')}</th>
88 <th></th>
89 <th>${_('Title')}</th>
90 <th class="td-time">${_('Opened On')}</th>
91 </thead>
56 92 %for pull_request in c.participate_in_pull_requests:
57 <div class="${'closed' if pull_request.is_closed() else ''} prwrapper">
58 <div class="pr">
59 <div class="${'flag_status %s' % pull_request.calculated_review_status()} pull-left"></div>
60 <a href="${h.url('pullrequest_show',repo_name=pull_request.target_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">
61 ${_('Pull request #%s opened by %s on %s') % (pull_request.pull_request_id, pull_request.author.full_name, h.format_date(pull_request.created_on))}
62 </a>
63 %if pull_request.is_closed():
64 (${_('Closed')})
65 %endif
66 </div>
67 </div>
93 <tr class="${'closed' if pull_request.is_closed() else ''} prwrapper">
94 <td class="td-status">
95 <div class="${'flag_status %s' % pull_request.calculated_review_status()} pull-left"></div>
96 </td>
97 <td class="td-componentname">
98 ${h.link_to(pull_request.target_repo.repo_name,h.url('summary_home',repo_name=pull_request.target_repo.repo_name))}
99 </td>
100 <td class="user">
101 ${base.gravatar_with_user(pull_request.author.email, 16)}
102 </td>
103 <td class="td-message expand_commit" data-pr-id="p${pull_request.pull_request_id}" title="${_('Expand commit message')}">
104 <div class="show_more_col">
105 <i class="show_more"></i>&nbsp;
106 </div>
107 </td>
108 <td class="mid td-description">
109 <div class="log-container truncate-wrap">
110 <div class="message truncate" id="c-p${pull_request.pull_request_id}"><a href="${h.url('pullrequest_show',repo_name=pull_request.target_repo.repo_name,pull_request_id=pull_request.pull_request_id)}">#${pull_request.pull_request_id}: ${pull_request.title}</a>\
111 %if pull_request.is_closed():
112 &nbsp;(${_('Closed')})\
113 %endif
114 <br/>${pull_request.description}</div>
115 </div>
116 </td>
117
118 <td class="td-time">
119 ${h.age_component(pull_request.created_on)}
120 </td>
121 </tr>
68 122 %endfor
123 </table>
69 124 %else:
70 <li><span class="empty_data">${_('There are currently no open pull requests requiring your participation.')}</span></li>
125 <h2 class="empty_data">${_('There are currently no open pull requests requiring your participation.')}</h2>
71 126 %endif
72 127 </div>
73 128 </div>
74 129 </div>
75 130
76 131 <script>
77 132 $('#show_closed').on('click', function(e){
78 133 if($(this).is(":checked")){
79 134 window.location = "${h.url('my_account_pullrequests', pr_show_closed=1)}";
80 135 }
81 136 else{
82 137 window.location = "${h.url('my_account_pullrequests')}";
83 138 }
84 })
139 });
140 $('.expand_commit').on('click',function(e){
141 var target_expand = $(this);
142 var cid = target_expand.data('prId');
143
144 if (target_expand.hasClass('open')){
145 $('#c-'+cid).css({'height': '2.75em', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
146 target_expand.removeClass('open');
147 }
148 else {
149 $('#c-'+cid).css({'height': 'auto', 'text-overflow': 'initial', 'overflow':'visible'});
150 target_expand.addClass('open');
151 }
152 });
85 153 </script>
@@ -1,72 +1,84 b''
1 1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.html"/>
3
2 4 <div class="panel panel-default">
3 5 <div class="panel-heading">
4 6 <h3 class="panel-title">${_('Settings for Repository Group: %s') % c.repo_group.name}</h3>
5 7 </div>
6 8 <div class="panel-body">
7 9 ${h.secure_form(url('update_repo_group',group_name=c.repo_group.group_name),method='put')}
8 10 <div class="form">
9 11 <!-- fields -->
10 12 <div class="fields">
11 13 <div class="field">
12 14 <div class="label">
13 15 <label for="group_name">${_('Group Name')}:</label>
14 16 </div>
15 17 <div class="input">
16 18 ${h.text('group_name',class_='medium')}
17 19 </div>
18 20 </div>
19 <div class="field">
21
22 <div class="field badged-field">
20 23 <div class="label">
21 24 <label for="user">${_('Owner')}:</label>
22 25 </div>
23 26 <div class="input">
24 ${h.text('user', class_="medium", autocomplete="off")}
25 <span class="help-block">${_('Change Repository Group Owner.')}</span>
27 <div class="badge-input-container">
28 <div class="user-badge">
29 ${base.gravatar_with_user(c.repo_group.user.email, show_disabled=not c.repo_group.user.active)}
30 </div>
31 <div class="badge-input-wrap">
32 ${h.text('user', class_="medium", autocomplete="off")}
33 </div>
34 </div>
35 <form:error name="user"/>
36 <p class="help-block">${_('Change owner of this repository group.')}</p>
26 37 </div>
27 38 </div>
39
28 40 <div class="field">
29 41 <div class="label label-textarea">
30 42 <label for="group_description">${_('Description')}:</label>
31 43 </div>
32 44 <div class="textarea text-area editor">
33 45 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
34 46 </div>
35 47 </div>
36 48
37 49 <div class="field">
38 50 <div class="label">
39 51 <label for="group_parent_id">${_('Group parent')}:</label>
40 52 </div>
41 53 <div class="select">
42 54 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
43 55 </div>
44 56 </div>
45 57 <div class="field">
46 58 <div class="label label-checkbox">
47 59 <label for="enable_locking">${_('Enable Repository Locking')}:</label>
48 60 </div>
49 61 <div class="checkboxes">
50 62 ${h.checkbox('enable_locking',value="True")}
51 63 <span class="help-block">${_('Repository locking will be enabled on all subgroups and repositories inside this repository group. Pulling from a repository locks it, and it is unlocked by pushing back by the same user.')}</span>
52 64 </div>
53 65 </div>
54 66 <div class="buttons">
55 67 ${h.submit('save',_('Save'),class_="btn")}
56 68 ${h.reset('reset',_('Reset'),class_="btn")}
57 69 </div>
58 70 </div>
59 71 </div>
60 72 ${h.end_form()}
61 73 </div>
62 74 </div>
63 75 <script>
64 76 $(document).ready(function(){
65 77 $("#group_parent_id").select2({
66 78 'containerCssClass': "drop-menu",
67 79 'dropdownCssClass': "drop-menu-dropdown",
68 80 'dropdownAutoWidth': true
69 81 });
70 82 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
71 83 })
72 84 </script>
@@ -1,225 +1,238 b''
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.html"/>
3
1 4 <div class="panel panel-default">
2 5 <div class="panel-heading">
3 6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.repo_info.repo_name}</h3>
4 7 </div>
5 8 <div class="panel-body">
6 9 ${h.secure_form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
7 10 <div class="form">
8 11 <!-- fields -->
9 12 <div class="fields">
10 13 <div class="field">
11 14 <div class="label">
12 15 <label for="repo_name">${_('Name')}:</label>
13 16 </div>
14 17 <div class="input">
15 18 ${h.text('repo_name',class_="medium")}
16 19 <p class="help-block">${_('Non-changeable id')}: `_${c.repo_info.repo_id}` <span><a id="show_more_clone_id" href="#">${_('what is that ?')}</a></span></p>
17 20 <p id="clone_id" style="display:none;">
18 21 ${_('URL by id')}: `${c.repo_info.clone_url(with_id=True)}` </br>
19 22 ${_('''In case this repository is renamed or moved into another group the repository url changes.
20 23 Using above url guarantees that this repository will always be accessible under such url.
21 24 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
22 25 </div>
23 26 </div>
24 27 % if c.repo_info.repo_type != 'svn':
25 28 <div class="field">
26 29 <div class="label">
27 30 <label for="clone_uri">${_('Remote uri')}:</label>
28 31 </div>
29 32 <div class="input">
30 33 %if c.repo_info.clone_uri:
31 34 <div id="clone_uri_hidden" class='text-as-placeholder'>
32 35 <span id="clone_uri_hidden_value">${c.repo_info.clone_uri_hidden}</span>
33 36 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
34 37 </div>
35 38 <div id="alter_clone_uri" style="display: none">
36 39 ${h.text('clone_uri',class_="medium", placeholder=_('new value, leave empty to remove'))}
37 40 ${h.hidden('clone_uri_change', 'OLD')}
38 41 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
39 42 </div>
40 43 %else:
41 44 ## not set yet, display form to set it
42 45 ${h.text('clone_uri',class_="medium")}
43 46 ${h.hidden('clone_uri_change', 'NEW')}
44 47 %endif
45 48 <p id="alter_clone_uri_help_block" class="help-block">${_('http[s] url where from repository was imported, also used for doing remote pulls.')}</p>
46 49 </div>
47 50 </div>
48 51 % else:
49 52 ${h.hidden('clone_uri', '')}
50 53 % endif
51 54 <div class="field">
52 55 <div class="label">
53 56 <label for="repo_group">${_('Repository group')}:</label>
54 57 </div>
55 58 <div class="select">
56 59 ${h.select('repo_group','',c.repo_groups,class_="medium")}
57 60 %if c.personal_repo_group:
58 61 <a style="padding: 4px" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}</a>
59 62 %endif
60 63 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
61 64 </div>
62 65 </div>
63 66 <div class="field">
64 67 <div class="label">
65 68 <label for="repo_landing_rev">${_('Landing commit')}:</label>
66 69 </div>
67 70 <div class="select">
68 71 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
69 72 <p class="help-block">${_('Default commit for files page, downloads, whoosh and readme')}</p>
70 73 </div>
71 74 </div>
72 <div class="field">
75
76 <div class="field badged-field">
73 77 <div class="label">
74 78 <label for="user">${_('Owner')}:</label>
75 79 </div>
76 80 <div class="input">
77 ${h.text('user', class_="medium", autocomplete="off")}
78 <p class="help-block">${_('Change owner of this repository.')}</p>
81 <div class="badge-input-container">
82 <div class="user-badge">
83 ${base.gravatar_with_user(c.repo_info.user.email, show_disabled=not c.repo_info.user.active)}
84 </div>
85 <div class="badge-input-wrap">
86 ${h.text('user', class_="medium", autocomplete="off")}
87 </div>
88 </div>
89 <form:error name="user"/>
90 <p class="help-block">${_('Change owner of this repository.')}</p>
79 91 </div>
80 </div>
92 </div>
93
81 94 <div class="field">
82 95 <div class="label label-textarea">
83 96 <label for="repo_description">${_('Description')}:</label>
84 97 </div>
85 98 <div class="textarea text-area editor">
86 99 ${h.textarea('repo_description', )}
87 100 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
88 101 </div>
89 102 </div>
90 103
91 104 <div class="field">
92 105 <div class="label label-checkbox">
93 106 <label for="repo_private">${_('Private repository')}:</label>
94 107 </div>
95 108 <div class="checkboxes">
96 109 ${h.checkbox('repo_private',value="True")}
97 110 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
98 111 </div>
99 112 </div>
100 113 <div class="field">
101 114 <div class="label label-checkbox">
102 115 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
103 116 </div>
104 117 <div class="checkboxes">
105 118 ${h.checkbox('repo_enable_statistics',value="True")}
106 119 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
107 120 </div>
108 121 </div>
109 122 <div class="field">
110 123 <div class="label label-checkbox">
111 124 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
112 125 </div>
113 126 <div class="checkboxes">
114 127 ${h.checkbox('repo_enable_downloads',value="True")}
115 128 <span class="help-block">${_('Enable download menu on summary page.')}</span>
116 129 </div>
117 130 </div>
118 131 <div class="field">
119 132 <div class="label label-checkbox">
120 133 <label for="repo_enable_locking">${_('Enable automatic locking')}:</label>
121 134 </div>
122 135 <div class="checkboxes">
123 136 ${h.checkbox('repo_enable_locking',value="True")}
124 137 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
125 138 </div>
126 139 </div>
127 140
128 141 %if c.visual.repository_fields:
129 142 ## EXTRA FIELDS
130 143 %for field in c.repo_fields:
131 144 <div class="field">
132 145 <div class="label">
133 146 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
134 147 </div>
135 148 <div class="input input-medium">
136 149 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
137 150 %if field.field_desc:
138 151 <span class="help-block">${field.field_desc}</span>
139 152 %endif
140 153 </div>
141 154 </div>
142 155 %endfor
143 156 %endif
144 157 <div class="buttons">
145 158 ${h.submit('save',_('Save'),class_="btn")}
146 159 ${h.reset('reset',_('Reset'),class_="btn")}
147 160 </div>
148 161 </div>
149 162 </div>
150 163 ${h.end_form()}
151 164 </div>
152 165 </div>
153 166
154 167 <script>
155 168 $(document).ready(function(){
156 169 var select2Options = {
157 170 'containerCssClass': "drop-menu",
158 171 'dropdownCssClass': "drop-menu-dropdown",
159 172 'dropdownAutoWidth': true
160 173 };
161 174
162 175 var cloneUrl = function() {
163 176 var alterButton = $('#alter_clone_uri');
164 177 var editButton = $('#edit_clone_uri');
165 178 var cancelEditButton = $('#cancel_edit_clone_uri');
166 179 var hiddenUrl = $('#clone_uri_hidden');
167 180 var hiddenUrlValue = $('#clone_uri_hidden_value');
168 181 var input = $('#clone_uri');
169 182 var helpBlock = $('#alter_clone_uri_help_block');
170 183 var changedFlag = $('#clone_uri_change');
171 184 var originalText = helpBlock.html();
172 185 var obfuscatedUrl = hiddenUrlValue.html();
173 186
174 187 var edit = function(e) {
175 188 alterButton.show();
176 189 editButton.hide();
177 190 hiddenUrl.hide();
178 191
179 192 //add the old value next to input for verification
180 193 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
181 194 changedFlag.val('MOD');
182 195 };
183 196
184 197 var cancelEdit = function(e) {
185 198 alterButton.hide();
186 199 editButton.show();
187 200 hiddenUrl.show();
188 201
189 202 helpBlock.html(originalText);
190 203 changedFlag.val('OLD');
191 204 input.val('');
192 205 };
193 206
194 207 var initEvents = function() {
195 208 editButton.on('click', edit);
196 209 cancelEditButton.on('click', cancelEdit);
197 210 };
198 211
199 212 var setInitialState = function() {
200 213 if (input.hasClass('error')) {
201 214 alterButton.show();
202 215 editButton.hide();
203 216 hiddenUrl.hide();
204 217 }
205 218 };
206 219
207 220 setInitialState();
208 221 initEvents();
209 222 }();
210 223
211 224 $('#show_more_clone_id').on('click', function(e){
212 225 $('#clone_id').show();
213 226 e.preventDefault();
214 227 });
215 228
216 229 $('#repo_landing_rev').select2(select2Options);
217 230 $('#repo_group').select2(select2Options);
218 231
219 232 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
220 233 $('#select_my_group').on('click', function(e){
221 234 e.preventDefault();
222 235 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
223 236 });
224 237 });
225 238 </script>
@@ -1,32 +1,33 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Licenses of Third Party Packages')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <p>
7 7 RhodeCode Enterprise uses various third party packages, many of them
8 8 provided by the open source community.
9 9 </p>
10 10
11 11 % if c.opensource_licenses:
12 12 <table class="rctable dl-settings">
13 13 <thead>
14 14 <th>Product</th>
15 15 <th>License</th>
16 16 </thead>
17 17 %for product, licenses in c.opensource_licenses.items():
18 18 <tr>
19 19 <td>${product}</td>
20 20 <td>
21 21 ${h.literal(', '.join([
22 22 '<a href="%(link)s" title="%(name)s">%(name)s</a>' % {'link':link, 'name':name}
23 if link else name
23 24 for name,link in licenses.items()]))}
24 25 </td>
25 26 </tr>
26 27 %endfor
27 28 </table>
28 29 % endif
29 30 </div>
30 31 </div>
31 32
32 33
@@ -1,86 +1,89 b''
1 1 <%
2 2 elems = [
3 3 ## general
4 4 (_('RhodeCode Enterprise version'), h.literal('%s <div class="link" id="check_for_update" >%s</div>' % (c.rhodecode_version, _('check for updates'))), ''),
5 5 (_('Upgrade info endpoint'), h.literal('%s <br/><span >%s.</span>' % (c.rhodecode_update_url, _('Note: please make sure this server can access this url'))), ''),
6 6 (_('Configuration INI file'), c.rhodecode_config_ini, ''),
7 7 ## systems stats
8 8 (_('RhodeCode Enterprise Server IP'), c.server_ip, ''),
9 9 (_('RhodeCode Enterprise Server ID'), c.server_id, ''),
10 10 (_('Platform'), c.platform, ''),
11 11 (_('Uptime'), c.uptime_age, ''),
12 12 (_('Storage location'), c.storage, ''),
13 13 (_('Storage disk space'), "%s/%s, %s%% used%s" % (h.format_byte_size_binary(c.disk['used']), h.format_byte_size_binary(c.disk['total']),(c.disk['percent']), ' %s' % c.disk['error'] if 'error' in c.disk else ''), ''),
14 14
15 15 (_('Search index storage'), c.index_storage, ''),
16 16 (_('Search index size'), "%s %s" % (h.format_byte_size_binary(c.disk_index['used']), ' %s' % c.disk_index['error'] if 'error' in c.disk_index else ''), ''),
17 17
18 18 (_('Gist storage'), c.gist_storage, ''),
19 19 (_('Gist storage size'), "%s (%s items)%s" % (h.format_byte_size_binary(c.disk_gist['used']),c.disk_gist['items'], ' %s' % c.disk_gist['error'] if 'error' in c.disk_gist else ''), ''),
20 20
21 21 (_('Archive cache'), h.literal('%s <br/><span >%s.</span>' % (c.archive_storage, _('Enable this by setting archive_cache_dir=/path/to/cache option in the .ini file'))), ''),
22 22 (_('Archive cache size'), "%s%s" % (h.format_byte_size_binary(c.disk_archive['used']), ' %s' % c.disk_archive['error'] if 'error' in c.disk_archive else ''), ''),
23 23
24 24 (_('System memory'), c.system_memory, ''),
25 25 (_('CPU'), '%s %%' %(c.cpu), ''),
26 26 (_('Load'), '1min: %s, 5min: %s, 15min: %s' %(c.load['1_min'],c.load['5_min'],c.load['15_min']), ''),
27 27
28 28 ## rhodecode stuff
29 29 (_('Python version'), c.py_version, ''),
30 30 (_('Python path'), c.py_path, ''),
31 31 (_('GIT version'), c.git_version, ''),
32 32 (_('HG version'), c.hg_version, ''),
33 33 (_('SVN version'), c.svn_version, ''),
34 34 (_('Database'), "%s @ version: %s" % (c.db_type, c.db_migrate_version), ''),
35 35 (_('Database version'), c.db_version, ''),
36 36
37 37 ]
38 38 %>
39 39
40 40 <div id="update_notice" style="display: none; margin: -40px 0px 20px 0px">
41 41 <div>${_('Checking for updates...')}</div>
42 42 </div>
43 43
44 44
45 45 <div class="panel panel-default">
46 46 <div class="panel-heading">
47 47 <h3 class="panel-title">${_('System Info')}</h3>
48 % if c.allowed_to_snapshot:
49 <a href="${url('admin_settings_system', snapshot=1)}" class="panel-edit">${_('create snapshot')}</a>
50 % endif
48 51 </div>
49 52 <div class="panel-body">
50 53 <dl class="dl-horizontal settings">
51 54 %for dt, dd, tt in elems:
52 <dt >${dt}:</dt>
53 <dd title="${tt}">${dd}</dd>
55 <dt>${dt}:</dt>
56 <dd title="${tt}">${dd}</dd>
54 57 %endfor
55 58 </dl>
56 59 </div>
57 60 </div>
58 61
59 62 <div class="panel panel-default">
60 63 <div class="panel-heading">
61 64 <h3 class="panel-title">${_('Python Packages')}</h3>
62 65 </div>
63 66 <div class="panel-body">
64 67 <table class="table">
65 68 <colgroup>
66 69 <col class='label'>
67 70 <col class='content'>
68 71 </colgroup>
69 72 <tbody>
70 73 %for key, value in c.py_modules:
71 74 <tr>
72 <td >${key}</td>
75 <td>${key}</td>
73 76 <td>${value}</td>
74 77 </tr>
75 78 %endfor
76 79 </tbody>
77 80 </table>
78 81 </div>
79 82 </div>
80 83
81 84 <script>
82 85 $('#check_for_update').click(function(e){
83 86 $('#update_notice').show();
84 87 $('#update_notice').load("${h.url('admin_settings_system_update')}");
85 88 })
86 89 </script>
@@ -1,131 +1,144 b''
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.html"/>
3
1 4 <div class="panel panel-default">
2 5 <div class="panel-heading">
3 6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
4 7 </div>
5 8 <div class="panel-body">
6 9 ${h.secure_form(url('update_users_group', user_group_id=c.user_group.users_group_id),method='put', id='edit_users_group')}
7 10 <div class="form">
8 11 <!-- fields -->
9 12 <div class="fields">
10 13 <div class="field">
11 14 <div class="label">
12 15 <label for="users_group_name">${_('Group name')}:</label>
13 16 </div>
14 17 <div class="input">
15 18 ${h.text('users_group_name',class_='medium')}
16 19 </div>
17 20 </div>
18 <div class="field">
21
22 <div class="field badged-field">
19 23 <div class="label">
20 24 <label for="user">${_('Owner')}:</label>
21 25 </div>
22 26 <div class="input">
23 ${h.text('user', class_="medium", autocomplete="off")}
24 <span class="help-block">${_('Change owner of this user group.')}</span>
27 <div class="badge-input-container">
28 <div class="user-badge">
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
30 </div>
31 <div class="badge-input-wrap">
32 ${h.text('user', class_="medium", autocomplete="off")}
33 </div>
34 </div>
35 <form:error name="user"/>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
25 37 </div>
26 </div>
38 </div>
39
27 40 <div class="field">
28 41 <div class="label label-textarea">
29 42 <label for="user_group_description">${_('Description')}:</label>
30 43 </div>
31 44 <div class="textarea textarea-small editor">
32 45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
33 46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
34 47 </div>
35 48 </div>
36 49 <div class="field">
37 50 <div class="label label-checkbox">
38 51 <label for="users_group_active">${_('Active')}:</label>
39 52 </div>
40 53 <div class="checkboxes">
41 54 ${h.checkbox('users_group_active',value=True)}
42 55 </div>
43 56 </div>
44 57 <div class="field">
45 58 <div class="label">
46 59 <label for="users_group_active">${_('Search')}:</label>
47 60 ${h.text('from_user_group',
48 61 placeholder="user/usergroup",
49 62 class_="medium")}
50 63 </div>
51 64 <div class="select side-by-side-selector">
52 65 <div class="left-group">
53 66 <label class="text"><strong>${_('Chosen group members')}</strong></label>
54 67 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,)}
55 68 <div class="btn" id="remove_all_elements" >
56 69 ${_('Remove all elements')}
57 70 <i class="icon-chevron-right"></i>
58 71 </div>
59 72 </div>
60 73 <div class="middle-group">
61 74 <i id="add_element" class="icon-chevron-left"></i>
62 75 <br />
63 76 <i id="remove_element" class="icon-chevron-right"></i>
64 77 </div>
65 78 <div class="right-group">
66 79 <label class="text" >${_('Available users')}
67 80 </label>
68 81 ${h.select('available_members',[],c.available_members,multiple=True,size=8,)}
69 82 <div class="btn" id="add_all_elements" >
70 83 <i class="icon-chevron-left"></i>${_('Add all elements')}
71 84 </div>
72 85 </div>
73 86 </div>
74 87 </div>
75 88 <div class="buttons">
76 89 ${h.submit('Save',_('Save'),class_="btn")}
77 90 </div>
78 91 </div>
79 92 </div>
80 93 ${h.end_form()}
81 94 </div>
82 95 </div>
83 96 <script>
84 97 $(document).ready(function(){
85 98 MultiSelectWidget('users_group_members','available_members','edit_users_group');
86 99
87 100 $("#group_parent_id").select2({
88 101 'containerCssClass': "drop-menu",
89 102 'dropdownCssClass': "drop-menu-dropdown",
90 103 'dropdownAutoWidth': true
91 104 });
92 105
93 106 $('#from_user_group').autocomplete({
94 107 serviceUrl: pyroutes.url('user_autocomplete_data'),
95 108 minChars:2,
96 109 maxHeight:400,
97 110 width:300,
98 111 deferRequestBy: 300, //miliseconds
99 112 showNoSuggestionNotice: true,
100 113 params: { user_groups:true },
101 114 formatResult: autocompleteFormatResult,
102 115 lookupFilter: autocompleteFilterResult,
103 116 onSelect: function(element, suggestion){
104 117
105 118 function preSelectUserIds(uids) {
106 119 $('#available_members').val(uids);
107 120 $('#users_group_members').val(uids);
108 121 }
109 122
110 123 if (suggestion.value_type == 'user_group') {
111 124 $.getJSON(
112 125 pyroutes.url('edit_user_group_members',
113 126 {'user_group_id': suggestion.id}),
114 127 function(data) {
115 128 var uids = [];
116 129 $.each(data.members, function(idx, user) {
117 130 var userid = user[0],
118 131 username = user[1];
119 132 uids.push(userid.toString());
120 133 });
121 134 preSelectUserIds(uids)
122 135 }
123 136 );
124 137 } else if (suggestion.value_type == 'user') {
125 138 preSelectUserIds([suggestion.id.toString()]);
126 139 }
127 140 }
128 141 });
129 142 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
130 143 })
131 144 </script>
@@ -1,144 +1,144 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add user')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10 <%def name="breadcrumbs_links()">
11 11 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 12 &raquo;
13 13 ${h.link_to(_('Users'),h.url('users'))}
14 14 &raquo;
15 15 ${_('Add User')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='admin')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <!-- box / title -->
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28 <!-- end box / title -->
29 29 ${h.secure_form(url('users'))}
30 30 <div class="form">
31 31 <!-- fields -->
32 32 <div class="fields">
33 33 <div class="field">
34 34 <div class="label">
35 35 <label for="username">${_('Username')}:</label>
36 36 </div>
37 37 <div class="input">
38 38 ${h.text('username', class_='medium')}
39 39 </div>
40 40 </div>
41 41
42 42 <div class="field">
43 43 <div class="label">
44 44 <label for="password">${_('Password')}:</label>
45 45 </div>
46 46 <div class="input">
47 47 ${h.password('password', class_='medium')}
48 48 </div>
49 49 </div>
50 50
51 51 <div class="field">
52 52 <div class="label">
53 53 <label for="password_confirmation">${_('Password confirmation')}:</label>
54 54 </div>
55 55 <div class="input">
56 56 ${h.password('password_confirmation',autocomplete="off", class_='medium')}
57 57 <div class="info-block">
58 58 <a id="generate_password" href="#">
59 59 <i class="icon-lock"></i> ${_('Generate password')}
60 60 </a>
61 61 <span id="generate_password_preview"></span>
62 62 </div>
63 63 </div>
64 64 </div>
65 65
66 66 <div class="field">
67 67 <div class="label">
68 68 <label for="firstname">${_('First Name')}:</label>
69 69 </div>
70 70 <div class="input">
71 71 ${h.text('firstname', class_='medium')}
72 72 </div>
73 73 </div>
74 74
75 75 <div class="field">
76 76 <div class="label">
77 77 <label for="lastname">${_('Last Name')}:</label>
78 78 </div>
79 79 <div class="input">
80 80 ${h.text('lastname', class_='medium')}
81 81 </div>
82 82 </div>
83 83
84 84 <div class="field">
85 85 <div class="label">
86 86 <label for="email">${_('Email')}:</label>
87 87 </div>
88 88 <div class="input">
89 89 ${h.text('email', class_='medium')}
90 90 ${h.hidden('extern_name', c.default_extern_type)}
91 91 ${h.hidden('extern_type', c.default_extern_type)}
92 92 </div>
93 93 </div>
94 94
95 95 <div class="field">
96 96 <div class="label label-checkbox">
97 97 <label for="active">${_('Active')}:</label>
98 98 </div>
99 99 <div class="checkboxes">
100 100 ${h.checkbox('active',value=True,checked='checked')}
101 101 </div>
102 102 </div>
103 103
104 104 <div class="field">
105 105 <div class="label label-checkbox">
106 106 <label for="password_change">${_('Password change')}:</label>
107 107 </div>
108 108 <div class="checkboxes">
109 109 ${h.checkbox('password_change',value=True)}
110 110 <span class="help-block">${_('Force user to change his password on the next login')}</span>
111 111 </div>
112 112 </div>
113 113
114 114 <div class="field">
115 115 <div class="label label-checkbox">
116 116 <label for="create_repo_group">${_('Add repository group')}:</label>
117 117 </div>
118 118 <div class="checkboxes">
119 119 ${h.checkbox('create_repo_group',value=True)}
120 120 <span class="help-block">${_('Add repository group with the same name as username. \nUser will be automatically set as this group owner.')}</span>
121 121 </div>
122 122 </div>
123 123
124 124 <div class="buttons">
125 125 ${h.submit('save',_('Save'),class_="btn")}
126 126 </div>
127 127 </div>
128 128 </div>
129 129 ${h.end_form()}
130 130 </div>
131 </%def>
132 131 <script>
133 132 $(document).ready(function(){
134 133 $('#username').focus();
135 134
136 135 $('#generate_password').on('click', function(e){
137 136 var tmpl = "(${_('generated password:')} {0})"
138 137 var new_passwd = generatePassword(12)
139 138 $('#generate_password_preview').html(tmpl.format(new_passwd))
140 139 $('#password').val(new_passwd);
141 140 $('#password_confirmation').val(new_passwd);
142 141 })
143 142 })
144 143 </script>
144 </%def>
@@ -1,655 +1,655 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 ${self.body()}
20 20 </div>
21 21 </div>
22 22 ${self.menu_bar_subnav()}
23 23 <!-- END HEADER -->
24 24
25 25 <!-- CONTENT -->
26 26 <div id="content" class="wrapper">
27 27 ${self.flash_msg()}
28 28 <div class="main">
29 29 ${next.main()}
30 30 </div>
31 31 </div>
32 32 <!-- END CONTENT -->
33 33
34 34 </div>
35 35 <!-- FOOTER -->
36 36 <div id="footer">
37 37 <div id="footer-inner" class="title wrapper">
38 38 <div>
39 39 <p class="footer-link-right">
40 40 % if c.visual.show_version:
41 41 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 42 % endif
43 43 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 44 % if c.visual.rhodecode_support_url:
45 45 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 46 % endif
47 47 </p>
48 48 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 49 <p class="server-instance" style="display:${sid}">
50 50 ## display hidden instance ID if specially defined
51 51 % if c.rhodecode_instanceid:
52 52 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 53 % endif
54 54 </p>
55 55 </div>
56 56 </div>
57 57 </div>
58 58
59 59 <!-- END FOOTER -->
60 60
61 61 ### MAKO DEFS ###
62 62
63 63 <%def name="menu_bar_subnav()">
64 64 </%def>
65 65
66 66 <%def name="flash_msg()">
67 67 <%include file="/base/flash_msg.html"/>
68 68 </%def>
69 69
70 70 <%def name="breadcrumbs(class_='breadcrumbs')">
71 71 <div class="${class_}">
72 72 ${self.breadcrumbs_links()}
73 73 </div>
74 74 </%def>
75 75
76 76 <%def name="admin_menu()">
77 77 <ul class="admin_menu submenu">
78 78 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
79 79 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
80 80 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
81 81 <li><a href="${h.url('users')}">${_('Users')}</a></li>
82 82 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
83 83 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
84 84 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
85 85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
87 87 </ul>
88 88 </%def>
89 89
90 90
91 91 <%def name="dt_info_panel(elements)">
92 92 <dl class="dl-horizontal">
93 93 %for dt, dd, title, show_items in elements:
94 94 <dt>${dt}:</dt>
95 95 <dd title="${title}">
96 96 %if callable(dd):
97 97 ## allow lazy evaluation of elements
98 98 ${dd()}
99 99 %else:
100 100 ${dd}
101 101 %endif
102 102 %if show_items:
103 103 <span class="btn-collapse" data-toggle="item-${h.md5(dt)[:6]}-details">${_('Show More')} </span>
104 104 %endif
105 105 </dd>
106 106
107 107 %if show_items:
108 108 <div class="collapsable-content" data-toggle="item-${h.md5(dt)[:6]}-details" style="display: none">
109 109 %for item in show_items:
110 110 <dt></dt>
111 111 <dd>${item}</dd>
112 112 %endfor
113 113 </div>
114 114 %endif
115 115
116 116 %endfor
117 117 </dl>
118 118 </%def>
119 119
120 120
121 121 <%def name="gravatar(email, size=16)">
122 122 <%
123 123 if (size > 16):
124 124 gravatar_class = 'gravatar gravatar-large'
125 125 else:
126 126 gravatar_class = 'gravatar'
127 127 %>
128 128 <%doc>
129 129 TODO: johbo: For now we serve double size images to make it smooth
130 130 for retina. This is how it worked until now. Should be replaced
131 131 with a better solution at some point.
132 132 </%doc>
133 133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 134 </%def>
135 135
136 136
137 <%def name="gravatar_with_user(contact, size=16)">
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 138 <div class="rc-user tooltip" title="${contact}">
139 139 ${self.gravatar(h.email_or_none(contact), size)}
140 <span class="user"> ${h.link_to_user(contact)}</span>
140 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 141 </div>
142 142 </%def>
143 143
144 144
145 145 ## admin menu used for people that have some admin resources
146 146 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 147 <ul class="submenu">
148 148 %if repositories:
149 149 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
150 150 %endif
151 151 %if repository_groups:
152 152 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
153 153 %endif
154 154 %if user_groups:
155 155 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
156 156 %endif
157 157 </ul>
158 158 </%def>
159 159
160 160 <%def name="repo_page_title(repo_instance)">
161 161 <div class="title-content">
162 162 <div class="title-main">
163 163 ## SVN/HG/GIT icons
164 164 %if h.is_hg(repo_instance):
165 165 <i class="icon-hg"></i>
166 166 %endif
167 167 %if h.is_git(repo_instance):
168 168 <i class="icon-git"></i>
169 169 %endif
170 170 %if h.is_svn(repo_instance):
171 171 <i class="icon-svn"></i>
172 172 %endif
173 173
174 174 ## public/private
175 175 %if repo_instance.private:
176 176 <i class="icon-repo-private"></i>
177 177 %else:
178 178 <i class="icon-repo-public"></i>
179 179 %endif
180 180
181 181 ## repo name with group name
182 182 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183 183
184 184 </div>
185 185
186 186 ## FORKED
187 187 %if repo_instance.fork:
188 188 <p>
189 189 <i class="icon-code-fork"></i> ${_('Fork of')}
190 190 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
191 191 </p>
192 192 %endif
193 193
194 194 ## IMPORTED FROM REMOTE
195 195 %if repo_instance.clone_uri:
196 196 <p>
197 197 <i class="icon-code-fork"></i> ${_('Clone from')}
198 <a href="${h.url(str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 199 </p>
200 200 %endif
201 201
202 202 ## LOCKING STATUS
203 203 %if repo_instance.locked[0]:
204 204 <p class="locking_locked">
205 205 <i class="icon-repo-lock"></i>
206 206 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 207 </p>
208 208 %elif repo_instance.enable_locking:
209 209 <p class="locking_unlocked">
210 210 <i class="icon-repo-unlock"></i>
211 211 ${_('Repository not locked. Pull repository to lock it.')}
212 212 </p>
213 213 %endif
214 214
215 215 </div>
216 216 </%def>
217 217
218 218 <%def name="repo_menu(active=None)">
219 219 <%
220 220 def is_active(selected):
221 221 if selected == active:
222 222 return "active"
223 223 %>
224 224
225 225 <!--- CONTEXT BAR -->
226 226 <div id="context-bar">
227 227 <div class="wrapper">
228 228 <ul id="context-pages" class="horizontal-list navigation">
229 229 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 230 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 231 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
232 232 <li class="${is_active('compare')}">
233 233 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
234 234 </li>
235 235 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
236 236 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
237 237 <li class="${is_active('showpullrequest')}">
238 238 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
239 239 %if c.repository_pull_requests:
240 240 <span class="pr_notifications">${c.repository_pull_requests}</span>
241 241 %endif
242 242 <div class="menulabel">${_('Pull Requests')}</div>
243 243 </a>
244 244 </li>
245 245 %endif
246 246 <li class="${is_active('options')}">
247 247 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
248 248 <ul class="submenu">
249 249 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 250 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 251 %endif
252 252 %if c.rhodecode_db_repo.fork:
253 253 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
254 254 ${_('Compare fork')}</a></li>
255 255 %endif
256 256
257 257 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
258 258
259 259 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 260 %if c.rhodecode_db_repo.locked[0]:
261 261 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
262 262 %else:
263 263 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
264 264 %endif
265 265 %endif
266 266 %if c.rhodecode_user.username != h.DEFAULT_USER:
267 267 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
268 268 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
269 269 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
270 270 %endif
271 271 %endif
272 272 </ul>
273 273 </li>
274 274 </ul>
275 275 </div>
276 276 <div class="clear"></div>
277 277 </div>
278 278 <!--- END CONTEXT BAR -->
279 279
280 280 </%def>
281 281
282 282 <%def name="usermenu()">
283 283 ## USER MENU
284 284 <li id="quick_login_li">
285 285 <a id="quick_login_link" class="menulink childs">
286 286 ${gravatar(c.rhodecode_user.email, 20)}
287 287 <span class="user">
288 288 %if c.rhodecode_user.username != h.DEFAULT_USER:
289 289 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
290 290 %else:
291 291 <span>${_('Sign in')}</span>
292 292 %endif
293 293 </span>
294 294 </a>
295 295
296 296 <div class="user-menu submenu">
297 297 <div id="quick_login">
298 298 %if c.rhodecode_user.username == h.DEFAULT_USER:
299 299 <h4>${_('Sign in to your account')}</h4>
300 300 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
301 301 <div class="form form-vertical">
302 302 <div class="fields">
303 303 <div class="field">
304 304 <div class="label">
305 305 <label for="username">${_('Username')}:</label>
306 306 </div>
307 307 <div class="input">
308 308 ${h.text('username',class_='focus',tabindex=1)}
309 309 </div>
310 310
311 311 </div>
312 312 <div class="field">
313 313 <div class="label">
314 314 <label for="password">${_('Password')}:</label>
315 315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
316 316 </div>
317 317 <div class="input">
318 318 ${h.password('password',class_='focus',tabindex=2)}
319 319 </div>
320 320 </div>
321 321 <div class="buttons">
322 322 <div class="register">
323 323 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
324 324 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
325 325 %endif
326 326 </div>
327 327 <div class="submit">
328 328 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
329 329 </div>
330 330 </div>
331 331 </div>
332 332 </div>
333 333 ${h.end_form()}
334 334 %else:
335 335 <div class="">
336 336 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
337 337 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
338 338 <div class="email">${c.rhodecode_user.email}</div>
339 339 </div>
340 340 <div class="">
341 341 <ol class="links">
342 342 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
343 343 <li class="logout">
344 344 ${h.secure_form(h.route_path('logout'))}
345 345 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
346 346 ${h.end_form()}
347 347 </li>
348 348 </ol>
349 349 </div>
350 350 %endif
351 351 </div>
352 352 </div>
353 353 %if c.rhodecode_user.username != h.DEFAULT_USER:
354 354 <div class="pill_container">
355 355 % if c.unread_notifications == 0:
356 356 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
357 357 % else:
358 358 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
359 359 % endif
360 360 </div>
361 361 % endif
362 362 </li>
363 363 </%def>
364 364
365 365 <%def name="menu_items(active=None)">
366 366 <%
367 367 def is_active(selected):
368 368 if selected == active:
369 369 return "active"
370 370 return ""
371 371 %>
372 372 <ul id="quick" class="main_nav navigation horizontal-list">
373 373 <!-- repo switcher -->
374 374 <li class="${is_active('repositories')} repo_switcher_li has_select2">
375 375 <input id="repo_switcher" name="repo_switcher" type="hidden">
376 376 </li>
377 377
378 378 ## ROOT MENU
379 379 %if c.rhodecode_user.username != h.DEFAULT_USER:
380 380 <li class="${is_active('journal')}">
381 381 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
382 382 <div class="menulabel">${_('Journal')}</div>
383 383 </a>
384 384 </li>
385 385 %else:
386 386 <li class="${is_active('journal')}">
387 387 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
388 388 <div class="menulabel">${_('Public journal')}</div>
389 389 </a>
390 390 </li>
391 391 %endif
392 392 <li class="${is_active('gists')}">
393 393 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
394 394 <div class="menulabel">${_('Gists')}</div>
395 395 </a>
396 396 </li>
397 397 <li class="${is_active('search')}">
398 398 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
399 399 <div class="menulabel">${_('Search')}</div>
400 400 </a>
401 401 </li>
402 402 % if h.HasPermissionAll('hg.admin')('access admin main page'):
403 403 <li class="${is_active('admin')}">
404 404 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
405 405 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
406 406 </a>
407 407 ${admin_menu()}
408 408 </li>
409 409 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
410 410 <li class="${is_active('admin')}">
411 411 <a class="menulink childs" title="${_('Delegated Admin settings')}">
412 412 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
413 413 </a>
414 414 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
415 415 c.rhodecode_user.repository_groups_admin,
416 416 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
417 417 </li>
418 418 % endif
419 419 % if c.debug_style:
420 420 <li class="${is_active('debug_style')}">
421 421 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
422 422 <div class="menulabel">${_('Style')}</div>
423 423 </a>
424 424 </li>
425 425 % endif
426 426 ## render extra user menu
427 427 ${usermenu()}
428 428 </ul>
429 429
430 430 <script type="text/javascript">
431 431 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
432 432
433 433 /*format the look of items in the list*/
434 434 var format = function(state, escapeMarkup){
435 435 if (!state.id){
436 436 return state.text; // optgroup
437 437 }
438 438 var obj_dict = state.obj;
439 439 var tmpl = '';
440 440
441 441 if(obj_dict && state.type == 'repo'){
442 442 if(obj_dict['repo_type'] === 'hg'){
443 443 tmpl += '<i class="icon-hg"></i> ';
444 444 }
445 445 else if(obj_dict['repo_type'] === 'git'){
446 446 tmpl += '<i class="icon-git"></i> ';
447 447 }
448 448 else if(obj_dict['repo_type'] === 'svn'){
449 449 tmpl += '<i class="icon-svn"></i> ';
450 450 }
451 451 if(obj_dict['private']){
452 452 tmpl += '<i class="icon-lock" ></i> ';
453 453 }
454 454 else if(visual_show_public_icon){
455 455 tmpl += '<i class="icon-unlock-alt"></i> ';
456 456 }
457 457 }
458 458 if(obj_dict && state.type == 'commit') {
459 459 tmpl += '<i class="icon-tag"></i>';
460 460 }
461 461 if(obj_dict && state.type == 'group'){
462 462 tmpl += '<i class="icon-folder-close"></i> ';
463 463 }
464 464 tmpl += escapeMarkup(state.text);
465 465 return tmpl;
466 466 };
467 467
468 468 var formatResult = function(result, container, query, escapeMarkup) {
469 469 return format(result, escapeMarkup);
470 470 };
471 471
472 472 var formatSelection = function(data, container, escapeMarkup) {
473 473 return format(data, escapeMarkup);
474 474 };
475 475
476 476 $("#repo_switcher").select2({
477 477 cachedDataSource: {},
478 478 minimumInputLength: 2,
479 479 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
480 480 dropdownAutoWidth: true,
481 481 formatResult: formatResult,
482 482 formatSelection: formatSelection,
483 483 containerCssClass: "repo-switcher",
484 484 dropdownCssClass: "repo-switcher-dropdown",
485 485 escapeMarkup: function(m){
486 486 // don't escape our custom placeholder
487 487 if(m.substr(0,23) == '<div class="menulabel">'){
488 488 return m;
489 489 }
490 490
491 491 return Select2.util.escapeMarkup(m);
492 492 },
493 493 query: $.debounce(250, function(query){
494 494 self = this;
495 495 var cacheKey = query.term;
496 496 var cachedData = self.cachedDataSource[cacheKey];
497 497
498 498 if (cachedData) {
499 499 query.callback({results: cachedData.results});
500 500 } else {
501 501 $.ajax({
502 502 url: "${h.url('goto_switcher_data')}",
503 503 data: {'query': query.term},
504 504 dataType: 'json',
505 505 type: 'GET',
506 506 success: function(data) {
507 507 self.cachedDataSource[cacheKey] = data;
508 508 query.callback({results: data.results});
509 509 },
510 510 error: function(data, textStatus, errorThrown) {
511 511 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
512 512 }
513 513 })
514 514 }
515 515 })
516 516 });
517 517
518 518 $("#repo_switcher").on('select2-selecting', function(e){
519 519 e.preventDefault();
520 520 window.location = e.choice.url;
521 521 });
522 522
523 523 ## Global mouse bindings ##
524 524
525 525 // general help "?"
526 526 Mousetrap.bind(['?'], function(e) {
527 527 $('#help_kb').modal({})
528 528 });
529 529
530 530 // / open the quick filter
531 531 Mousetrap.bind(['/'], function(e) {
532 532 $("#repo_switcher").select2("open");
533 533
534 534 // return false to prevent default browser behavior
535 535 // and stop event from bubbling
536 536 return false;
537 537 });
538 538
539 539 // general nav g + action
540 540 Mousetrap.bind(['g h'], function(e) {
541 541 window.location = pyroutes.url('home');
542 542 });
543 543 Mousetrap.bind(['g g'], function(e) {
544 544 window.location = pyroutes.url('gists', {'private':1});
545 545 });
546 546 Mousetrap.bind(['g G'], function(e) {
547 547 window.location = pyroutes.url('gists', {'public':1});
548 548 });
549 549 Mousetrap.bind(['n g'], function(e) {
550 550 window.location = pyroutes.url('new_gist');
551 551 });
552 552 Mousetrap.bind(['n r'], function(e) {
553 553 window.location = pyroutes.url('new_repo');
554 554 });
555 555
556 556 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
557 557 // nav in repo context
558 558 Mousetrap.bind(['g s'], function(e) {
559 559 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
560 560 });
561 561 Mousetrap.bind(['g c'], function(e) {
562 562 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
563 563 });
564 564 Mousetrap.bind(['g F'], function(e) {
565 565 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
566 566 });
567 567 Mousetrap.bind(['g f'], function(e) {
568 568 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
569 569 });
570 570 Mousetrap.bind(['g p'], function(e) {
571 571 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
572 572 });
573 573 Mousetrap.bind(['g o'], function(e) {
574 574 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
575 575 });
576 576 Mousetrap.bind(['g O'], function(e) {
577 577 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
578 578 });
579 579 % endif
580 580
581 581 </script>
582 582 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
583 583 </%def>
584 584
585 585 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
586 586 <div class="modal-dialog">
587 587 <div class="modal-content">
588 588 <div class="modal-header">
589 589 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
590 590 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
591 591 </div>
592 592 <div class="modal-body">
593 593 <div class="block-left">
594 594 <table class="keyboard-mappings">
595 595 <tbody>
596 596 <tr>
597 597 <th></th>
598 598 <th>${_('Site-wide shortcuts')}</th>
599 599 </tr>
600 600 <%
601 601 elems = [
602 602 ('/', 'Open quick search box'),
603 603 ('g h', 'Goto home page'),
604 604 ('g g', 'Goto my private gists page'),
605 605 ('g G', 'Goto my public gists page'),
606 606 ('n r', 'New repository page'),
607 607 ('n g', 'New gist page'),
608 608 ]
609 609 %>
610 610 %for key, desc in elems:
611 611 <tr>
612 612 <td class="keys">
613 613 <span class="key tag">${key}</span>
614 614 </td>
615 615 <td>${desc}</td>
616 616 </tr>
617 617 %endfor
618 618 </tbody>
619 619 </table>
620 620 </div>
621 621 <div class="block-left">
622 622 <table class="keyboard-mappings">
623 623 <tbody>
624 624 <tr>
625 625 <th></th>
626 626 <th>${_('Repositories')}</th>
627 627 </tr>
628 628 <%
629 629 elems = [
630 630 ('g s', 'Goto summary page'),
631 631 ('g c', 'Goto changelog page'),
632 632 ('g f', 'Goto files page'),
633 633 ('g F', 'Goto files page with file search activated'),
634 634 ('g p', 'Goto pull requests page'),
635 635 ('g o', 'Goto repository settings'),
636 636 ('g O', 'Goto repository permissions settings'),
637 637 ]
638 638 %>
639 639 %for key, desc in elems:
640 640 <tr>
641 641 <td class="keys">
642 642 <span class="key tag">${key}</span>
643 643 </td>
644 644 <td>${desc}</td>
645 645 </tr>
646 646 %endfor
647 647 </tbody>
648 648 </table>
649 649 </div>
650 650 </div>
651 651 <div class="modal-footer">
652 652 </div>
653 653 </div><!-- /.modal-content -->
654 654 </div><!-- /.modal-dialog -->
655 655 </div><!-- /.modal -->
@@ -1,21 +1,22 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="subject()" filter="n,trim">
5 5 RhodeCode new user registration
6 6 </%def>
7 7
8 8 ## plain text version of the email. Empty by default
9 9 <%def name="body_plaintext()" filter="n,trim">
10 10
11 11 A new user `${user.username}` has registered on ${h.format_date(date)}
12 12
13 13 - Username: ${user.username}
14 14 - Full Name: ${user.firstname} ${user.lastname}
15 15 - Email: ${user.email}
16 - Profile link: ${h.url('user_profile', username=user.username, qualified=True)}
16 17 </%def>
17 18
18 19 ## BODY GOES BELOW
19 20 <div style="white-space: pre-wrap">
20 21 ${body_plaintext()}
21 </div> No newline at end of file
22 </div>
@@ -1,77 +1,77 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/root.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Sign In')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <style>body{background-color:#eeeeee;}</style>
12 12 <div class="loginbox">
13 13 <div class="header">
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 17 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
21 21 %endif
22 22 </div>
23 23 </div>
24 24 </div>
25 25
26 26 <div class="loginwrapper">
27 27 <div class="left-column">
28 28 <img class="sign-in-image" src="${h.url('/images/sign-in.png')}" alt="RhodeCode"/>
29 29 </div>
30
30 <%block name="above_login_button" />
31 31 <div id="login" class="right-column">
32 32 <%include file="/base/flash_msg.html"/>
33 33 <!-- login -->
34 34 <div class="sign-in-title">
35 35 <h1>${_('Sign In')}</h1>
36 36 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
37 37 <h4>${h.link_to(_("Go to the registration page to create a new account."), request.route_path('register'))}</h4>
38 38 %endif
39 39 </div>
40 40 <div class="inner form">
41 41 ${h.form(request.route_path('login', _query={'came_from': came_from}), needs_csrf_token=False)}
42 42
43 43 <label for="username">${_('Username')}:</label>
44 44 ${h.text('username', class_='focus', value=defaults.get('username'))}
45 45 %if 'username' in errors:
46 46 <span class="error-message">${errors.get('username')}</span>
47 47 <br />
48 48 %endif
49 49
50 50 <label for="password">${_('Password')}:</label>
51 51 ${h.password('password', class_='focus')}
52 52 %if 'password' in errors:
53 53 <span class="error-message">${errors.get('password')}</span>
54 54 <br />
55 55 %endif
56 56
57 57 ${h.checkbox('remember', value=True, checked=defaults.get('remember'))}
58 58 <label class="checkbox" for="remember">${_('Remember me')}</label>
59 59
60 60 <p class="links">
61 61 ${h.link_to(_('Forgot your password?'), h.route_path('reset_password'))}
62 62 </p>
63 63
64 64 ${h.submit('sign_in', _('Sign In'), class_="btn sign-in")}
65 65
66 66 ${h.end_form()}
67 67 <script type="text/javascript">
68 68 $(document).ready(function(){
69 69 $('#username').focus();
70 70 })
71 71 </script>
72 72 </div>
73 73 <!-- end login -->
74 74 <%block name="below_login_button" />
75 75 </div>
76 76 </div>
77 77 </div>
@@ -1,132 +1,132 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Pull Requests') % c.repo_name}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()">
11 11
12 12 </%def>
13 13
14 14 <%def name="menu_bar_nav()">
15 15 ${self.menu_items(active='repositories')}
16 16 </%def>
17 17
18 18
19 19 <%def name="menu_bar_subnav()">
20 20 ${self.repo_menu(active='showpullrequest')}
21 21 </%def>
22 22
23 23
24 24 <%def name="main()">
25 25 <div class="box">
26 26 <div class="title">
27 27 ${self.repo_page_title(c.rhodecode_db_repo)}
28 28
29 29 <ul class="links">
30 30 <li>
31 31 %if c.rhodecode_user.username != h.DEFAULT_USER:
32 32 <span>
33 33 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
34 34 ${_('Open new Pull Request')}
35 35 </a>
36 36 </span>
37 37 %endif
38 38 </li>
39 39 </ul>
40 40
41 41 ${self.breadcrumbs()}
42 42 </div>
43 43
44 44 <div class="sidebar-col-wrapper">
45 45 ##main
46 46 <div class="sidebar">
47 47 <ul class="nav nav-pills nav-stacked">
48 48 <li class="${'active' if c.active=='open' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0)}">${_('Opened')}</a></li>
49 49 <li class="${'active' if c.active=='my' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,my=1)}">${_('Opened by me')}</a></li>
50 <li class="${'active' if c.active=='awaiting' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,awaiting_review=1)}">${_('Awaiting review')}</a></li>
50 51 <li class="${'active' if c.active=='awaiting_my' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,awaiting_my_review=1)}">${_('Awaiting my review')}</a></li>
51 52 <li class="${'active' if c.active=='closed' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,closed=1)}">${_('Closed')}</a></li>
52 <li class="${'active' if c.active=='awaiting' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,awaiting_review=1)}">${_('Awaiting review')}</a></li>
53 53 <li class="${'active' if c.active=='source' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=1)}">${_('From this repo')}</a></li>
54 54 </ul>
55 55 </div>
56 56
57 57 <div class="main-content-full-width">
58 58 <div class="panel panel-default">
59 59 <div class="panel-heading">
60 60 <h3 class="panel-title">
61 61 %if c.source:
62 62 ${_('Pull Requests from %(repo_name)s repository') % {'repo_name': c.repo_name}}
63 63 %elif c.closed:
64 64 ${_('Closed Pull Requests to repository %(repo_name)s') % {'repo_name': c.repo_name}}
65 65 %elif c.my:
66 66 ${_('Pull Requests to %(repo_name)s repository opened by me') % {'repo_name': c.repo_name}}
67 67 %elif c.awaiting_review:
68 68 ${_('Pull Requests to %(repo_name)s repository awaiting review') % {'repo_name': c.repo_name}}
69 69 %elif c.awaiting_my_review:
70 70 ${_('Pull Requests to %(repo_name)s repository awaiting my review') % {'repo_name': c.repo_name}}
71 71 %else:
72 72 ${_('Pull Requests to %(repo_name)s repository') % {'repo_name': c.repo_name}}
73 73 %endif
74 74 </h3>
75 75 </div>
76 76 <div class="panel-body">
77 77 <table id="pull_request_list_table" class="display"></table>
78 78 </div>
79 79 </div>
80 80 </div>
81 81 </div>
82 82 </div>
83 83
84 84 <script type="text/javascript">
85 85 $(document).ready(function() {
86 86 // object list
87 87 $('#pull_request_list_table').DataTable({
88 88 data: ${c.data|n},
89 89 processing: true,
90 90 serverSide: true,
91 91 deferLoading: ${c.records_total},
92 92 ajax: "",
93 93 dom: 'tp',
94 94 pageLength: ${c.visual.dashboard_items},
95 95 order: [[ 1, "desc" ]],
96 96 columns: [
97 97 { data: {"_": "status",
98 98 "sort": "status"}, title: "", className: "td-status", orderable: false},
99 99 { data: {"_": "name",
100 100 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname", "type": "num" },
101 101 { data: {"_": "author",
102 102 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
103 103 { data: {"_": "title",
104 104 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
105 105 { data: {"_": "comments",
106 106 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
107 107 { data: {"_": "updated_on",
108 108 "sort": "updated_on_raw"}, title: "${_('Updated on')}", className: "td-time" }
109 109 ],
110 110 language: {
111 111 paginate: DEFAULT_GRID_PAGINATION
112 112 },
113 113 "drawCallback": function( settings, json ) {
114 114 timeagoActivate();
115 115 tooltip_activate();
116 116 },
117 117 "createdRow": function ( row, data, index ) {
118 118 if (data['closed']) {
119 119 $(row).addClass('closed');
120 120 }
121 121 }
122 122 });
123 123 });
124 124 $('#pull_request_list_table').on('xhr.dt', function(e, settings, json, xhr){
125 125 $('#pull_request_list_table').css('opacity', 1);
126 126 });
127 127
128 128 $('#pull_request_list_table').on('preXhr.dt', function(e, settings, data){
129 129 $('#pull_request_list_table').css('opacity', 0.3);
130 130 });
131 131 </script>
132 132 </%def>
@@ -1,123 +1,123 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/root.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Create an Account')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10 <style>body{background-color:#eeeeee;}</style>
11 11
12 12 <div class="loginbox">
13 13 <div class="header">
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 17 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
21 21 %endif
22 22 </div>
23 23 </div>
24 24 </div>
25 25
26 26 <div class="loginwrapper">
27 27 <div class="left-column">
28 28 <img class="sign-in-image" src="${h.url('/images/sign-in.png')}" alt="RhodeCode"/>
29 29 </div>
30
30 <%block name="above_register_button" />
31 31 <div id="register" class="right-column">
32 32 <%include file="/base/flash_msg.html"/>
33 33 <!-- login -->
34 34 <div class="sign-in-title">
35 35 <h1>${_('Create an account')}</h1>
36 36 <h4>${h.link_to(_("Go to the login page to sign in with an existing account."), request.route_path('login'))}</h4>
37 37 </div>
38 38 <div class="inner form">
39 39 ${h.form(request.route_path('register'), needs_csrf_token=False)}
40 40
41 41 <label for="username">${_('Username')}:</label>
42 42 ${h.text('username', defaults.get('username'))}
43 43 %if 'username' in errors:
44 44 <span class="error-message">${errors.get('username')}</span>
45 45 <br />
46 46 %endif
47 47
48 48 <label for="password">${_('Password')}:</label>
49 49 ${h.password('password', defaults.get('password'))}
50 50 %if 'password' in errors:
51 51 <span class="error-message">${errors.get('password')}</span>
52 52 <br />
53 53 %endif
54 54
55 55 <label for="password_confirmation">${_('Re-enter password')}:</label>
56 56 ${h.password('password_confirmation', defaults.get('password_confirmation'))}
57 57 %if 'password_confirmation' in errors:
58 58 <span class="error-message">${errors.get('password_confirmation')}</span>
59 59 <br />
60 60 %endif
61 61
62 62 <label for="firstname">${_('First Name')}:</label>
63 63 ${h.text('firstname', defaults.get('firstname'))}
64 64 %if 'firstname' in errors:
65 65 <span class="error-message">${errors.get('firstname')}</span>
66 66 <br />
67 67 %endif
68 68
69 69 <label for="lastname">${_('Last Name')}:</label>
70 70 ${h.text('lastname', defaults.get('lastname'))}
71 71 %if 'lastname' in errors:
72 72 <span class="error-message">${errors.get('lastname')}</span>
73 73 <br />
74 74 %endif
75 75
76 76 <label for="email">${_('Email')}:</label>
77 77 ${h.text('email', defaults.get('email'))}
78 78 %if 'email' in errors:
79 79 <span class="error-message">${errors.get('email')}</span>
80 80 <br />
81 81 %endif
82 82
83 83 %if captcha_active:
84 84 <div>
85 85 <label for="recaptcha">${_('Captcha')}:</label>
86 86 ${h.hidden('recaptcha_field')}
87 87 <div id="recaptcha"></div>
88 88 %if 'recaptcha_field' in errors:
89 89 <span class="error-message">${errors.get('recaptcha_field')}</span>
90 90 <br />
91 91 %endif
92 92 </div>
93 93 %endif
94 94
95 95 %if not auto_active:
96 96 <p class="activation_msg">
97 97 ${_('Account activation requires admin approval.')}
98 98 </p>
99 99 %endif
100 100 <p class="register_message">
101 101 ${register_message|n}
102 102 </p>
103 103
104 104 ${h.submit('sign_up',_('Create Account'),class_="btn sign-in")}
105 105
106 106 ${h.end_form()}
107 107 </div>
108 108 <%block name="below_register_button" />
109 109 </div>
110 110 </div>
111 111 </div>
112 112
113 113 %if captcha_active:
114 114 <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
115 115 %endif
116 116 <script type="text/javascript">
117 117 $(document).ready(function(){
118 118 $('#username').focus();
119 119 %if captcha_active:
120 120 Recaptcha.create("${captcha_public_key}", "recaptcha", {theme: "white"});
121 121 %endif
122 122 });
123 123 </script>
@@ -1,341 +1,344 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.lib import helpers as h
24 24 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
25 25 from rhodecode.model.meta import Session
26 26 from rhodecode.tests import (
27 27 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
28 28 assert_session_flash)
29 29 from rhodecode.tests.fixture import Fixture
30 30 from rhodecode.tests.utils import AssertResponse
31 31
32 32 fixture = Fixture()
33 33
34 34
35 35 class TestMyAccountController(TestController):
36 36 test_user_1 = 'testme'
37 37 destroy_users = set()
38 38
39 39 @classmethod
40 40 def teardown_class(cls):
41 41 fixture.destroy_users(cls.destroy_users)
42 42
43 43 def test_my_account(self):
44 44 self.log_user()
45 45 response = self.app.get(url('my_account'))
46 46
47 47 response.mustcontain('test_admin')
48 48 response.mustcontain('href="/_admin/my_account/edit"')
49 49
50 50 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
51 51 response = self.app.get(url('my_account'))
52 52 assert_response = AssertResponse(response)
53 53 element = assert_response.get_element('.logout #csrf_token')
54 54 assert element.value == csrf_token
55 55
56 56 def test_my_account_edit(self):
57 57 self.log_user()
58 58 response = self.app.get(url('my_account_edit'))
59 59
60 60 response.mustcontain('value="test_admin')
61 61
62 62 def test_my_account_my_repos(self):
63 63 self.log_user()
64 64 response = self.app.get(url('my_account_repos'))
65 65 repos = Repository.query().filter(
66 66 Repository.user == User.get_by_username(
67 67 TEST_USER_ADMIN_LOGIN)).all()
68 68 for repo in repos:
69 69 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
70 70
71 71 def test_my_account_my_watched(self):
72 72 self.log_user()
73 73 response = self.app.get(url('my_account_watched'))
74 74
75 75 repos = UserFollowing.query().filter(
76 76 UserFollowing.user == User.get_by_username(
77 77 TEST_USER_ADMIN_LOGIN)).all()
78 78 for repo in repos:
79 79 response.mustcontain(
80 80 '"name_raw": "%s"' % repo.follows_repository.repo_name)
81 81
82 82 @pytest.mark.backends("git", "hg")
83 83 def test_my_account_my_pullrequests(self, pr_util):
84 84 self.log_user()
85 85 response = self.app.get(url('my_account_pullrequests'))
86 86 response.mustcontain('You currently have no open pull requests.')
87 87
88 pr = pr_util.create_pull_request()
88 pr = pr_util.create_pull_request(title='TestMyAccountPR')
89 89 response = self.app.get(url('my_account_pullrequests'))
90 response.mustcontain('Pull request #%d opened' % pr.pull_request_id)
90 response.mustcontain('There are currently no open pull requests '
91 'requiring your participation')
92
93 response.mustcontain('#%s: TestMyAccountPR' % pr.pull_request_id)
91 94
92 95 def test_my_account_my_emails(self):
93 96 self.log_user()
94 97 response = self.app.get(url('my_account_emails'))
95 98 response.mustcontain('No additional emails specified')
96 99
97 100 def test_my_account_my_emails_add_existing_email(self):
98 101 self.log_user()
99 102 response = self.app.get(url('my_account_emails'))
100 103 response.mustcontain('No additional emails specified')
101 104 response = self.app.post(url('my_account_emails'),
102 105 {'new_email': TEST_USER_REGULAR_EMAIL,
103 106 'csrf_token': self.csrf_token})
104 107 assert_session_flash(response, 'This e-mail address is already taken')
105 108
106 109 def test_my_account_my_emails_add_mising_email_in_form(self):
107 110 self.log_user()
108 111 response = self.app.get(url('my_account_emails'))
109 112 response.mustcontain('No additional emails specified')
110 113 response = self.app.post(url('my_account_emails'),
111 114 {'csrf_token': self.csrf_token})
112 115 assert_session_flash(response, 'Please enter an email address')
113 116
114 117 def test_my_account_my_emails_add_remove(self):
115 118 self.log_user()
116 119 response = self.app.get(url('my_account_emails'))
117 120 response.mustcontain('No additional emails specified')
118 121
119 122 response = self.app.post(url('my_account_emails'),
120 123 {'new_email': 'foo@barz.com',
121 124 'csrf_token': self.csrf_token})
122 125
123 126 response = self.app.get(url('my_account_emails'))
124 127
125 128 from rhodecode.model.db import UserEmailMap
126 129 email_id = UserEmailMap.query().filter(
127 130 UserEmailMap.user == User.get_by_username(
128 131 TEST_USER_ADMIN_LOGIN)).filter(
129 132 UserEmailMap.email == 'foo@barz.com').one().email_id
130 133
131 134 response.mustcontain('foo@barz.com')
132 135 response.mustcontain('<input id="del_email_id" name="del_email_id" '
133 136 'type="hidden" value="%s" />' % email_id)
134 137
135 138 response = self.app.post(
136 139 url('my_account_emails'), {
137 140 'del_email_id': email_id, '_method': 'delete',
138 141 'csrf_token': self.csrf_token})
139 142 assert_session_flash(response, 'Removed email address from user account')
140 143 response = self.app.get(url('my_account_emails'))
141 144 response.mustcontain('No additional emails specified')
142 145
143 146 @pytest.mark.parametrize(
144 147 "name, attrs", [
145 148 ('firstname', {'firstname': 'new_username'}),
146 149 ('lastname', {'lastname': 'new_username'}),
147 150 ('admin', {'admin': True}),
148 151 ('admin', {'admin': False}),
149 152 ('extern_type', {'extern_type': 'ldap'}),
150 153 ('extern_type', {'extern_type': None}),
151 154 # ('extern_name', {'extern_name': 'test'}),
152 155 # ('extern_name', {'extern_name': None}),
153 156 ('active', {'active': False}),
154 157 ('active', {'active': True}),
155 158 ('email', {'email': 'some@email.com'}),
156 159 ])
157 160 def test_my_account_update(self, name, attrs):
158 161 usr = fixture.create_user(self.test_user_1, password='qweqwe',
159 162 email='testme@rhodecode.org',
160 163 extern_type='rhodecode',
161 164 extern_name=self.test_user_1,
162 165 skip_if_exists=True)
163 166 self.destroy_users.add(self.test_user_1)
164 167
165 168 params = usr.get_api_data() # current user data
166 169 user_id = usr.user_id
167 170 self.log_user(username=self.test_user_1, password='qweqwe')
168 171
169 172 params.update({'password_confirmation': ''})
170 173 params.update({'new_password': ''})
171 174 params.update({'extern_type': 'rhodecode'})
172 175 params.update({'extern_name': self.test_user_1})
173 176 params.update({'csrf_token': self.csrf_token})
174 177
175 178 params.update(attrs)
176 179 # my account page cannot set language param yet, only for admins
177 180 del params['language']
178 181 response = self.app.post(url('my_account'), params)
179 182
180 183 assert_session_flash(
181 184 response, 'Your account was updated successfully')
182 185
183 186 del params['csrf_token']
184 187
185 188 updated_user = User.get_by_username(self.test_user_1)
186 189 updated_params = updated_user.get_api_data()
187 190 updated_params.update({'password_confirmation': ''})
188 191 updated_params.update({'new_password': ''})
189 192
190 193 params['last_login'] = updated_params['last_login']
191 194 # my account page cannot set language param yet, only for admins
192 195 # but we get this info from API anyway
193 196 params['language'] = updated_params['language']
194 197
195 198 if name == 'email':
196 199 params['emails'] = [attrs['email']]
197 200 if name == 'extern_type':
198 201 # cannot update this via form, expected value is original one
199 202 params['extern_type'] = "rhodecode"
200 203 if name == 'extern_name':
201 204 # cannot update this via form, expected value is original one
202 205 params['extern_name'] = str(user_id)
203 206 if name == 'active':
204 207 # my account cannot deactivate account
205 208 params['active'] = True
206 209 if name == 'admin':
207 210 # my account cannot make you an admin !
208 211 params['admin'] = False
209 212
210 213 assert params == updated_params
211 214
212 215 def test_my_account_update_err_email_exists(self):
213 216 self.log_user()
214 217
215 218 new_email = 'test_regular@mail.com' # already exisitn email
216 219 response = self.app.post(url('my_account'),
217 220 params={
218 221 'username': 'test_admin',
219 222 'new_password': 'test12',
220 223 'password_confirmation': 'test122',
221 224 'firstname': 'NewName',
222 225 'lastname': 'NewLastname',
223 226 'email': new_email,
224 227 'csrf_token': self.csrf_token,
225 228 })
226 229
227 230 response.mustcontain('This e-mail address is already taken')
228 231
229 232 def test_my_account_update_err(self):
230 233 self.log_user('test_regular2', 'test12')
231 234
232 235 new_email = 'newmail.pl'
233 236 response = self.app.post(url('my_account'),
234 237 params={
235 238 'username': 'test_admin',
236 239 'new_password': 'test12',
237 240 'password_confirmation': 'test122',
238 241 'firstname': 'NewName',
239 242 'lastname': 'NewLastname',
240 243 'email': new_email,
241 244 'csrf_token': self.csrf_token,
242 245 })
243 246
244 247 response.mustcontain('An email address must contain a single @')
245 248 from rhodecode.model import validators
246 249 msg = validators.ValidUsername(
247 250 edit=False, old_data={})._messages['username_exists']
248 251 msg = h.html_escape(msg % {'username': 'test_admin'})
249 252 response.mustcontain(u"%s" % msg)
250 253
251 254 def test_my_account_auth_tokens(self):
252 255 usr = self.log_user('test_regular2', 'test12')
253 256 user = User.get(usr['user_id'])
254 257 response = self.app.get(url('my_account_auth_tokens'))
255 258 response.mustcontain(user.api_key)
256 259 response.mustcontain('expires: never')
257 260
258 261 @pytest.mark.parametrize("desc, lifetime", [
259 262 ('forever', -1),
260 263 ('5mins', 60*5),
261 264 ('30days', 60*60*24*30),
262 265 ])
263 266 def test_my_account_add_auth_tokens(self, desc, lifetime):
264 267 usr = self.log_user('test_regular2', 'test12')
265 268 user = User.get(usr['user_id'])
266 269 response = self.app.post(url('my_account_auth_tokens'),
267 270 {'description': desc, 'lifetime': lifetime,
268 271 'csrf_token': self.csrf_token})
269 272 assert_session_flash(response, 'Auth token successfully created')
270 273 try:
271 274 response = response.follow()
272 275 user = User.get(usr['user_id'])
273 276 for auth_token in user.auth_tokens:
274 277 response.mustcontain(auth_token)
275 278 finally:
276 279 for auth_token in UserApiKeys.query().all():
277 280 Session().delete(auth_token)
278 281 Session().commit()
279 282
280 283 def test_my_account_remove_auth_token(self):
281 284 # TODO: without this cleanup it fails when run with the whole
282 285 # test suite, so there must be some interference with other tests.
283 286 UserApiKeys.query().delete()
284 287
285 288 usr = self.log_user('test_regular2', 'test12')
286 289 User.get(usr['user_id'])
287 290 response = self.app.post(url('my_account_auth_tokens'),
288 291 {'description': 'desc', 'lifetime': -1,
289 292 'csrf_token': self.csrf_token})
290 293 assert_session_flash(response, 'Auth token successfully created')
291 294 response = response.follow()
292 295
293 296 # now delete our key
294 297 keys = UserApiKeys.query().all()
295 298 assert 1 == len(keys)
296 299
297 300 response = self.app.post(
298 301 url('my_account_auth_tokens'),
299 302 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
300 303 'csrf_token': self.csrf_token})
301 304 assert_session_flash(response, 'Auth token successfully deleted')
302 305 keys = UserApiKeys.query().all()
303 306 assert 0 == len(keys)
304 307
305 308 def test_my_account_reset_main_auth_token(self):
306 309 usr = self.log_user('test_regular2', 'test12')
307 310 user = User.get(usr['user_id'])
308 311 api_key = user.api_key
309 312 response = self.app.get(url('my_account_auth_tokens'))
310 313 response.mustcontain(api_key)
311 314 response.mustcontain('expires: never')
312 315
313 316 response = self.app.post(
314 317 url('my_account_auth_tokens'),
315 318 {'_method': 'delete', 'del_auth_token_builtin': api_key,
316 319 'csrf_token': self.csrf_token})
317 320 assert_session_flash(response, 'Auth token successfully reset')
318 321 response = response.follow()
319 322 response.mustcontain(no=[api_key])
320 323
321 324 def test_password_is_updated_in_session_on_password_change(
322 325 self, user_util):
323 326 old_password = 'abcdef123'
324 327 new_password = 'abcdef124'
325 328
326 329 user = user_util.create_user(password=old_password)
327 330 session = self.log_user(user.username, old_password)
328 331 old_password_hash = session['password']
329 332
330 333 form_data = {
331 334 'current_password': old_password,
332 335 'new_password': new_password,
333 336 'new_password_confirmation': new_password,
334 337 'csrf_token': self.csrf_token
335 338 }
336 339 self.app.post(url('my_account_password'), form_data)
337 340
338 341 response = self.app.get(url('home'))
339 342 new_password_hash = response.session['rhodecode_user']['password']
340 343
341 344 assert old_password_hash != new_password_hash
@@ -1,622 +1,624 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 import rhodecode
25 25 from rhodecode.config.routing import ADMIN_PREFIX
26 26 from rhodecode.lib.utils2 import md5
27 27 from rhodecode.model.db import RhodeCodeUi
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 30 from rhodecode.tests import url, assert_session_flash
31 31 from rhodecode.tests.utils import AssertResponse
32 32
33 33
34 34 UPDATE_DATA_QUALNAME = (
35 35 'rhodecode.controllers.admin.settings.SettingsController.get_update_data')
36 36
37 37
38 38 @pytest.mark.usefixtures('autologin_user', 'app')
39 39 class TestAdminSettingsController:
40 40
41 41 @pytest.mark.parametrize('urlname', [
42 42 'admin_settings_vcs',
43 43 'admin_settings_mapping',
44 44 'admin_settings_global',
45 45 'admin_settings_visual',
46 46 'admin_settings_email',
47 47 'admin_settings_hooks',
48 48 'admin_settings_search',
49 49 'admin_settings_system',
50 50 ])
51 51 def test_simple_get(self, urlname, app):
52 52 app.get(url(urlname))
53 53
54 54 def test_create_custom_hook(self, csrf_token):
55 55 response = self.app.post(
56 56 url('admin_settings_hooks'),
57 57 params={
58 58 'new_hook_ui_key': 'test_hooks_1',
59 59 'new_hook_ui_value': 'cd /tmp',
60 60 'csrf_token': csrf_token})
61 61
62 62 response = response.follow()
63 63 response.mustcontain('test_hooks_1')
64 64 response.mustcontain('cd /tmp')
65 65
66 66 def test_create_custom_hook_delete(self, csrf_token):
67 67 response = self.app.post(
68 68 url('admin_settings_hooks'),
69 69 params={
70 70 'new_hook_ui_key': 'test_hooks_2',
71 71 'new_hook_ui_value': 'cd /tmp2',
72 72 'csrf_token': csrf_token})
73 73
74 74 response = response.follow()
75 75 response.mustcontain('test_hooks_2')
76 76 response.mustcontain('cd /tmp2')
77 77
78 78 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
79 79
80 80 # delete
81 81 self.app.post(
82 82 url('admin_settings_hooks'),
83 83 params={'hook_id': hook_id, 'csrf_token': csrf_token})
84 84 response = self.app.get(url('admin_settings_hooks'))
85 85 response.mustcontain(no=['test_hooks_2'])
86 86 response.mustcontain(no=['cd /tmp2'])
87 87
88 88 def test_system_update_new_version(self):
89 89 update_data = {
90 90 'versions': [
91 91 {
92 92 'version': '100.3.1415926535',
93 93 'general': 'The latest version we are ever going to ship'
94 94 },
95 95 {
96 96 'version': '0.0.0',
97 97 'general': 'The first version we ever shipped'
98 98 }
99 99 ]
100 100 }
101 101 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
102 102 response = self.app.get(url('admin_settings_system_update'))
103 103 response.mustcontain('A <b>new version</b> is available')
104 104
105 105 def test_system_update_nothing_new(self):
106 106 update_data = {
107 107 'versions': [
108 108 {
109 109 'version': '0.0.0',
110 110 'general': 'The first version we ever shipped'
111 111 }
112 112 ]
113 113 }
114 114 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
115 115 response = self.app.get(url('admin_settings_system_update'))
116 116 response.mustcontain(
117 117 'You already have the <b>latest</b> stable version.')
118 118
119 119 def test_system_update_bad_response(self):
120 120 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
121 121 response = self.app.get(url('admin_settings_system_update'))
122 122 response.mustcontain(
123 123 'Bad data sent from update server')
124 124
125 125
126 126 @pytest.mark.usefixtures('autologin_user', 'app')
127 127 class TestAdminSettingsGlobal:
128 128
129 129 def test_pre_post_code_code_active(self, csrf_token):
130 130 pre_code = 'rc-pre-code-187652122'
131 131 post_code = 'rc-postcode-98165231'
132 132
133 133 response = self.post_and_verify_settings({
134 134 'rhodecode_pre_code': pre_code,
135 135 'rhodecode_post_code': post_code,
136 136 'csrf_token': csrf_token,
137 137 })
138 138
139 139 response = response.follow()
140 140 response.mustcontain(pre_code, post_code)
141 141
142 142 def test_pre_post_code_code_inactive(self, csrf_token):
143 143 pre_code = 'rc-pre-code-187652122'
144 144 post_code = 'rc-postcode-98165231'
145 145 response = self.post_and_verify_settings({
146 146 'rhodecode_pre_code': '',
147 147 'rhodecode_post_code': '',
148 148 'csrf_token': csrf_token,
149 149 })
150 150
151 151 response = response.follow()
152 152 response.mustcontain(no=[pre_code, post_code])
153 153
154 154 def test_captcha_activate(self, csrf_token):
155 155 self.post_and_verify_settings({
156 156 'rhodecode_captcha_private_key': '1234567890',
157 157 'rhodecode_captcha_public_key': '1234567890',
158 158 'csrf_token': csrf_token,
159 159 })
160 160
161 161 response = self.app.get(ADMIN_PREFIX + '/register')
162 162 response.mustcontain('captcha')
163 163
164 164 def test_captcha_deactivate(self, csrf_token):
165 165 self.post_and_verify_settings({
166 166 'rhodecode_captcha_private_key': '',
167 167 'rhodecode_captcha_public_key': '1234567890',
168 168 'csrf_token': csrf_token,
169 169 })
170 170
171 171 response = self.app.get(ADMIN_PREFIX + '/register')
172 172 response.mustcontain(no=['captcha'])
173 173
174 174 def test_title_change(self, csrf_token):
175 175 old_title = 'RhodeCode'
176 176 new_title = old_title + '_changed'
177 177
178 178 for new_title in ['Changed', 'Żółwik', old_title]:
179 179 response = self.post_and_verify_settings({
180 180 'rhodecode_title': new_title,
181 181 'csrf_token': csrf_token,
182 182 })
183 183
184 184 response = response.follow()
185 185 response.mustcontain(
186 186 """<div class="branding">- %s</div>""" % new_title)
187 187
188 188 def post_and_verify_settings(self, settings):
189 189 old_title = 'RhodeCode'
190 190 old_realm = 'RhodeCode authentication'
191 191 params = {
192 192 'rhodecode_title': old_title,
193 193 'rhodecode_realm': old_realm,
194 194 'rhodecode_pre_code': '',
195 195 'rhodecode_post_code': '',
196 196 'rhodecode_captcha_private_key': '',
197 197 'rhodecode_captcha_public_key': '',
198 198 }
199 199 params.update(settings)
200 200 response = self.app.post(url('admin_settings_global'), params=params)
201 201
202 202 assert_session_flash(response, 'Updated application settings')
203 203 app_settings = SettingsModel().get_all_settings()
204 204 del settings['csrf_token']
205 205 for key, value in settings.iteritems():
206 206 assert app_settings[key] == value.decode('utf-8')
207 207
208 208 return response
209 209
210 210
211 211 @pytest.mark.usefixtures('autologin_user', 'app')
212 212 class TestAdminSettingsVcs:
213 213
214 214 def test_contains_svn_default_patterns(self, app):
215 215 response = app.get(url('admin_settings_vcs'))
216 216 expected_patterns = [
217 217 '/trunk',
218 218 '/branches/*',
219 219 '/tags/*',
220 220 ]
221 221 for pattern in expected_patterns:
222 222 response.mustcontain(pattern)
223 223
224 224 def test_add_new_svn_branch_and_tag_pattern(
225 225 self, app, backend_svn, form_defaults, disable_sql_cache,
226 226 csrf_token):
227 227 form_defaults.update({
228 228 'new_svn_branch': '/exp/branches/*',
229 229 'new_svn_tag': '/important_tags/*',
230 230 'csrf_token': csrf_token,
231 231 })
232 232
233 233 response = app.post(
234 234 url('admin_settings_vcs'), params=form_defaults, status=302)
235 235 response = response.follow()
236 236
237 237 # Expect to find the new values on the page
238 238 response.mustcontain('/exp/branches/*')
239 239 response.mustcontain('/important_tags/*')
240 240
241 241 # Expect that those patterns are used to match branches and tags now
242 242 repo = backend_svn['svn-simple-layout'].scm_instance()
243 243 assert 'exp/branches/exp-sphinx-docs' in repo.branches
244 244 assert 'important_tags/v0.5' in repo.tags
245 245
246 246 def test_add_same_svn_value_twice_shows_an_error_message(
247 247 self, app, form_defaults, csrf_token, settings_util):
248 248 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
249 249 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
250 250
251 251 response = app.post(
252 252 url('admin_settings_vcs'),
253 253 params={
254 254 'paths_root_path': form_defaults['paths_root_path'],
255 255 'new_svn_branch': '/test',
256 256 'new_svn_tag': '/test',
257 257 'csrf_token': csrf_token,
258 258 },
259 259 status=200)
260 260
261 261 response.mustcontain("Pattern already exists")
262 262 response.mustcontain("Some form inputs contain invalid data.")
263 263
264 264 @pytest.mark.parametrize('section', [
265 265 'vcs_svn_branch',
266 266 'vcs_svn_tag',
267 267 ])
268 268 def test_delete_svn_patterns(
269 269 self, section, app, csrf_token, settings_util):
270 270 setting = settings_util.create_rhodecode_ui(
271 271 section, '/test_delete', cleanup=False)
272 272
273 273 app.post(
274 274 url('admin_settings_vcs'),
275 275 params={
276 276 '_method': 'delete',
277 277 'delete_svn_pattern': setting.ui_id,
278 278 'csrf_token': csrf_token},
279 279 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
280 280
281 281 @pytest.mark.parametrize('section', [
282 282 'vcs_svn_branch',
283 283 'vcs_svn_tag',
284 284 ])
285 285 def test_delete_svn_patterns_raises_400_when_no_xhr(
286 286 self, section, app, csrf_token, settings_util):
287 287 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
288 288
289 289 app.post(
290 290 url('admin_settings_vcs'),
291 291 params={
292 292 '_method': 'delete',
293 293 'delete_svn_pattern': setting.ui_id,
294 294 'csrf_token': csrf_token},
295 295 status=400)
296 296
297 297 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
298 298 form_defaults.update({
299 299 'csrf_token': csrf_token,
300 300 'extensions_hgsubversion': 'True',
301 301 })
302 302 response = app.post(
303 303 url('admin_settings_vcs'),
304 304 params=form_defaults,
305 305 status=302)
306 306
307 307 response = response.follow()
308 308 extensions_input = (
309 309 '<input id="extensions_hgsubversion" '
310 310 'name="extensions_hgsubversion" type="checkbox" '
311 311 'value="True" checked="checked" />')
312 312 response.mustcontain(extensions_input)
313 313
314 314 def test_has_a_section_for_pull_request_settings(self, app):
315 315 response = app.get(url('admin_settings_vcs'))
316 316 response.mustcontain('Pull Request Settings')
317 317
318 318 def test_has_an_input_for_invalidation_of_inline_comments(
319 319 self, app):
320 320 response = app.get(url('admin_settings_vcs'))
321 321 assert_response = AssertResponse(response)
322 322 assert_response.one_element_exists(
323 323 '[name=rhodecode_use_outdated_comments]')
324 324
325 325 @pytest.mark.parametrize('new_value', [True, False])
326 326 def test_allows_to_change_invalidation_of_inline_comments(
327 327 self, app, form_defaults, csrf_token, new_value):
328 328 setting_key = 'use_outdated_comments'
329 329 setting = SettingsModel().create_or_update_setting(
330 330 setting_key, not new_value, 'bool')
331 331 Session().add(setting)
332 332 Session().commit()
333 333
334 334 form_defaults.update({
335 335 'csrf_token': csrf_token,
336 336 'rhodecode_use_outdated_comments': str(new_value),
337 337 })
338 338 response = app.post(
339 339 url('admin_settings_vcs'),
340 340 params=form_defaults,
341 341 status=302)
342 342 response = response.follow()
343 343 setting = SettingsModel().get_setting_by_name(setting_key)
344 344 assert setting.app_settings_value is new_value
345 345
346 346 @pytest.fixture
347 347 def disable_sql_cache(self, request):
348 348 patcher = mock.patch(
349 349 'rhodecode.lib.caching_query.FromCache.process_query')
350 350 request.addfinalizer(patcher.stop)
351 351 patcher.start()
352 352
353 353 @pytest.fixture
354 354 def form_defaults(self):
355 355 from rhodecode.controllers.admin.settings import SettingsController
356 356 controller = SettingsController()
357 357 return controller._form_defaults()
358 358
359 359 # TODO: johbo: What we really want is to checkpoint before a test run and
360 360 # reset the session afterwards.
361 361 @pytest.fixture(scope='class', autouse=True)
362 362 def cleanup_settings(self, request, pylonsapp):
363 363 ui_id = RhodeCodeUi.ui_id
364 364 original_ids = list(
365 365 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
366 366
367 367 @request.addfinalizer
368 368 def cleanup():
369 369 RhodeCodeUi.query().filter(
370 370 ui_id.notin_(original_ids)).delete(False)
371 371
372 372
373 373 @pytest.mark.usefixtures('autologin_user', 'app')
374 374 class TestLabsSettings(object):
375 375 def test_get_settings_page_disabled(self):
376 376 with mock.patch.dict(rhodecode.CONFIG,
377 377 {'labs_settings_active': 'false'}):
378 378 response = self.app.get(url('admin_settings_labs'), status=302)
379 379
380 380 assert response.location.endswith(url('admin_settings'))
381 381
382 382 def test_get_settings_page_enabled(self):
383 383 from rhodecode.controllers.admin import settings
384 384 lab_settings = [
385 385 settings.LabSetting(
386 386 key='rhodecode_bool',
387 387 type='bool',
388 388 group='bool group',
389 389 label='bool label',
390 390 help='bool help'
391 391 ),
392 392 settings.LabSetting(
393 393 key='rhodecode_text',
394 394 type='unicode',
395 395 group='text group',
396 396 label='text label',
397 397 help='text help'
398 398 ),
399 399 ]
400 400 with mock.patch.dict(rhodecode.CONFIG,
401 401 {'labs_settings_active': 'true'}):
402 402 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
403 403 response = self.app.get(url('admin_settings_labs'))
404 404
405 405 assert '<label>bool group:</label>' in response
406 406 assert '<label for="rhodecode_bool">bool label</label>' in response
407 407 assert '<p class="help-block">bool help</p>' in response
408 408 assert 'name="rhodecode_bool" type="checkbox"' in response
409 409
410 410 assert '<label>text group:</label>' in response
411 411 assert '<label for="rhodecode_text">text label</label>' in response
412 412 assert '<p class="help-block">text help</p>' in response
413 413 assert 'name="rhodecode_text" size="60" type="text"' in response
414 414
415 415 @pytest.mark.parametrize('setting_name', [
416 416 'proxy_subversion_http_requests',
417 417 'hg_use_rebase_for_merging',
418 418 ])
419 419 def test_update_boolean_settings(self, csrf_token, setting_name):
420 420 self.app.post(
421 421 url('admin_settings_labs'),
422 422 params={
423 423 'rhodecode_{}'.format(setting_name): 'true',
424 424 'csrf_token': csrf_token,
425 425 })
426 426 setting = SettingsModel().get_setting_by_name(setting_name)
427 427 assert setting.app_settings_value
428 428
429 429 self.app.post(
430 430 url('admin_settings_labs'),
431 431 params={
432 432 'rhodecode_{}'.format(setting_name): 'false',
433 433 'csrf_token': csrf_token,
434 434 })
435 435 setting = SettingsModel().get_setting_by_name(setting_name)
436 436 assert not setting.app_settings_value
437 437
438 438 @pytest.mark.parametrize('setting_name', [
439 439 'subversion_http_server_url',
440 440 ])
441 441 def test_update_string_settings(self, csrf_token, setting_name):
442 442 self.app.post(
443 443 url('admin_settings_labs'),
444 444 params={
445 445 'rhodecode_{}'.format(setting_name): 'Test 1',
446 446 'csrf_token': csrf_token,
447 447 })
448 448 setting = SettingsModel().get_setting_by_name(setting_name)
449 449 assert setting.app_settings_value == 'Test 1'
450 450
451 451 self.app.post(
452 452 url('admin_settings_labs'),
453 453 params={
454 454 'rhodecode_{}'.format(setting_name): ' Test 2 ',
455 455 'csrf_token': csrf_token,
456 456 })
457 457 setting = SettingsModel().get_setting_by_name(setting_name)
458 458 assert setting.app_settings_value == 'Test 2'
459 459
460 460
461 461 @pytest.mark.usefixtures('app')
462 462 class TestOpenSourceLicenses(object):
463
464 def _get_url(self):
465 return ADMIN_PREFIX + '/settings/open_source'
466
463 467 def test_records_are_displayed(self, autologin_user):
464 468 sample_licenses = {
465 469 "python2.7-pytest-2.7.1": {
466 470 "UNKNOWN": None
467 471 },
468 472 "python2.7-Markdown-2.6.2": {
469 473 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
470 474 }
471 475 }
472 476 read_licenses_patch = mock.patch(
473 'rhodecode.controllers.admin.settings.read_opensource_licenses',
477 'rhodecode.admin.views.read_opensource_licenses',
474 478 return_value=sample_licenses)
475 479 with read_licenses_patch:
476 response = self.app.get(
477 url('admin_settings_open_source'), status=200)
480 response = self.app.get(self._get_url(), status=200)
478 481
479 482 assert_response = AssertResponse(response)
480 483 assert_response.element_contains(
481 484 '.panel-heading', 'Licenses of Third Party Packages')
482 485 for name in sample_licenses:
483 486 response.mustcontain(name)
484 487 for license in sample_licenses[name]:
485 488 assert_response.element_contains('.panel-body', license)
486 489
487 490 def test_records_can_be_read(self, autologin_user):
488 response = self.app.get(url('admin_settings_open_source'), status=200)
491 response = self.app.get(self._get_url(), status=200)
489 492 assert_response = AssertResponse(response)
490 493 assert_response.element_contains(
491 494 '.panel-heading', 'Licenses of Third Party Packages')
492 495
493 496 def test_forbidden_when_normal_user(self, autologin_regular_user):
494 self.app.get(
495 url('admin_settings_open_source'), status=403)
497 self.app.get(self._get_url(), status=403)
496 498
497 499
498 500 @pytest.mark.usefixtures("app")
499 501 class TestAdminSettingsIssueTracker:
500 502 RC_PREFIX = 'rhodecode_'
501 503 SHORT_PATTERN_KEY = 'issuetracker_pat_'
502 504 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
503 505
504 506 def test_issuetracker_index(self, autologin_user):
505 507 response = self.app.get(url('admin_settings_issuetracker'))
506 508 assert response.status_code == 200
507 509
508 510 def test_add_issuetracker_pattern(
509 511 self, request, autologin_user, csrf_token):
510 512 pattern = 'issuetracker_pat'
511 513 another_pattern = pattern+'1'
512 514 post_url = url('admin_settings_issuetracker_save')
513 515 post_data = {
514 516 'new_pattern_pattern_0': pattern,
515 517 'new_pattern_url_0': 'url',
516 518 'new_pattern_prefix_0': 'prefix',
517 519 'new_pattern_description_0': 'description',
518 520 'new_pattern_pattern_1': another_pattern,
519 521 'new_pattern_url_1': 'url1',
520 522 'new_pattern_prefix_1': 'prefix1',
521 523 'new_pattern_description_1': 'description1',
522 524 'csrf_token': csrf_token
523 525 }
524 526 self.app.post(post_url, post_data, status=302)
525 527 settings = SettingsModel().get_all_settings()
526 528 self.uid = md5(pattern)
527 529 assert settings[self.PATTERN_KEY+self.uid] == pattern
528 530 self.another_uid = md5(another_pattern)
529 531 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
530 532
531 533 @request.addfinalizer
532 534 def cleanup():
533 535 defaults = SettingsModel().get_all_settings()
534 536
535 537 entries = [name for name in defaults if (
536 538 (self.uid in name) or (self.another_uid) in name)]
537 539 start = len(self.RC_PREFIX)
538 540 for del_key in entries:
539 541 # TODO: anderson: get_by_name needs name without prefix
540 542 entry = SettingsModel().get_setting_by_name(del_key[start:])
541 543 Session().delete(entry)
542 544
543 545 Session().commit()
544 546
545 547 def test_edit_issuetracker_pattern(
546 548 self, autologin_user, backend, csrf_token, request):
547 549 old_pattern = 'issuetracker_pat'
548 550 old_uid = md5(old_pattern)
549 551 pattern = 'issuetracker_pat_new'
550 552 self.new_uid = md5(pattern)
551 553
552 554 SettingsModel().create_or_update_setting(
553 555 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
554 556
555 557 post_url = url('admin_settings_issuetracker_save')
556 558 post_data = {
557 559 'new_pattern_pattern_0': pattern,
558 560 'new_pattern_url_0': 'url',
559 561 'new_pattern_prefix_0': 'prefix',
560 562 'new_pattern_description_0': 'description',
561 563 'uid': old_uid,
562 564 'csrf_token': csrf_token
563 565 }
564 566 self.app.post(post_url, post_data, status=302)
565 567 settings = SettingsModel().get_all_settings()
566 568 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
567 569 assert self.PATTERN_KEY+old_uid not in settings
568 570
569 571 @request.addfinalizer
570 572 def cleanup():
571 573 IssueTrackerSettingsModel().delete_entries(self.new_uid)
572 574
573 575 def test_replace_issuetracker_pattern_description(
574 576 self, autologin_user, csrf_token, request, settings_util):
575 577 prefix = 'issuetracker'
576 578 pattern = 'issuetracker_pat'
577 579 self.uid = md5(pattern)
578 580 pattern_key = '_'.join([prefix, 'pat', self.uid])
579 581 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
580 582 desc_key = '_'.join([prefix, 'desc', self.uid])
581 583 rc_desc_key = '_'.join(['rhodecode', desc_key])
582 584 new_description = 'new_description'
583 585
584 586 settings_util.create_rhodecode_setting(
585 587 pattern_key, pattern, 'unicode', cleanup=False)
586 588 settings_util.create_rhodecode_setting(
587 589 desc_key, 'old description', 'unicode', cleanup=False)
588 590
589 591 post_url = url('admin_settings_issuetracker_save')
590 592 post_data = {
591 593 'new_pattern_pattern_0': pattern,
592 594 'new_pattern_url_0': 'url',
593 595 'new_pattern_prefix_0': 'prefix',
594 596 'new_pattern_description_0': new_description,
595 597 'uid': self.uid,
596 598 'csrf_token': csrf_token
597 599 }
598 600 self.app.post(post_url, post_data, status=302)
599 601 settings = SettingsModel().get_all_settings()
600 602 assert settings[rc_pattern_key] == pattern
601 603 assert settings[rc_desc_key] == new_description
602 604
603 605 @request.addfinalizer
604 606 def cleanup():
605 607 IssueTrackerSettingsModel().delete_entries(self.uid)
606 608
607 609 def test_delete_issuetracker_pattern(
608 610 self, autologin_user, backend, csrf_token, settings_util):
609 611 pattern = 'issuetracker_pat'
610 612 uid = md5(pattern)
611 613 settings_util.create_rhodecode_setting(
612 614 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
613 615
614 616 post_url = url('admin_issuetracker_delete')
615 617 post_data = {
616 618 '_method': 'delete',
617 619 'uid': uid,
618 620 'csrf_token': csrf_token
619 621 }
620 622 self.app.post(post_url, post_data, status=302)
621 623 settings = SettingsModel().get_all_settings()
622 624 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,54 +1,54 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from rhodecode.model.db import User
22 22 from rhodecode.tests import *
23 23
24 24
25 25 class TestFeedController(TestController):
26 26
27 27 def test_rss(self, backend):
28 28 self.log_user()
29 29 response = self.app.get(url(controller='feed', action='rss',
30 30 repo_name=backend.repo_name))
31 31
32 32 assert response.content_type == "application/rss+xml"
33 33 assert """<rss version="2.0">""" in response
34 34
35 35 def test_rss_with_auth_token(self, backend):
36 auth_token = User.get_first_admin().feed_token
36 auth_token = User.get_first_super_admin().feed_token
37 37 assert auth_token != ''
38 38 response = self.app.get(url(controller='feed', action='rss',
39 39 repo_name=backend.repo_name, auth_token=auth_token))
40 40
41 41 assert response.content_type == "application/rss+xml"
42 42 assert """<rss version="2.0">""" in response
43 43
44 44 def test_atom(self, backend):
45 45 self.log_user()
46 46 response = self.app.get(url(controller='feed', action='atom',
47 47 repo_name=backend.repo_name))
48 48
49 49 assert response.content_type == """application/atom+xml"""
50 50 assert """<?xml version="1.0" encoding="utf-8"?>""" in response
51 51
52 52 tag1 = '<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">'
53 53 tag2 = '<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">'
54 54 assert tag1 in response or tag2 in response
@@ -1,365 +1,381 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import json
22 22
23 23 from mock import patch
24 24 import pytest
25 25
26 26 import rhodecode
27 27 from rhodecode.lib.utils import map_groups
28 28 from rhodecode.model.db import Repository, User, RepoGroup
29 29 from rhodecode.model.meta import Session
30 30 from rhodecode.model.repo import RepoModel
31 31 from rhodecode.model.repo_group import RepoGroupModel
32 32 from rhodecode.model.settings import SettingsModel
33 33 from rhodecode.tests import TestController, url, TEST_USER_ADMIN_LOGIN
34 34 from rhodecode.tests.fixture import Fixture
35 35
36 36
37 37 fixture = Fixture()
38 38
39 39
40 40 class TestHomeController(TestController):
41 41
42 42 def test_index(self):
43 43 self.log_user()
44 44 response = self.app.get(url(controller='home', action='index'))
45 45 # if global permission is set
46 46 response.mustcontain('Add Repository')
47 47
48 48 # search for objects inside the JavaScript JSON
49 49 for repo in Repository.getAll():
50 50 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
51 51
52 52 def test_index_contains_backend_specific_details(self, backend):
53 53 self.log_user()
54 54 response = self.app.get(url(controller='home', action='index'))
55 55 tip = backend.repo.get_commit().raw_id
56 56
57 57 # html in javascript variable:
58 58 response.mustcontain(r'<i class=\"icon-%s\"' % (backend.alias, ))
59 59 response.mustcontain(r'href=\"/%s\"' % (backend.repo_name, ))
60 60
61 61 response.mustcontain("""/%s/changeset/%s""" % (backend.repo_name, tip))
62 62 response.mustcontain("""Added a symlink""")
63 63
64 64 def test_index_with_anonymous_access_disabled(self):
65 65 with fixture.anon_access(False):
66 66 response = self.app.get(url(controller='home', action='index'),
67 67 status=302)
68 68 assert 'login' in response.location
69 69
70 70 def test_index_page_on_groups(self, autologin_user, repo_group):
71 71 response = self.app.get(url('repo_group_home', group_name='gr1'))
72 72 response.mustcontain("gr1/repo_in_group")
73 73
74 74 def test_index_page_on_group_with_trailing_slash(
75 75 self, autologin_user, repo_group):
76 76 response = self.app.get(url('repo_group_home', group_name='gr1') + '/')
77 77 response.mustcontain("gr1/repo_in_group")
78 78
79 79 @pytest.fixture(scope='class')
80 80 def repo_group(self, request):
81 81 gr = fixture.create_repo_group('gr1')
82 82 fixture.create_repo(name='gr1/repo_in_group', repo_group=gr)
83 83
84 84 @request.addfinalizer
85 85 def cleanup():
86 86 RepoModel().delete('gr1/repo_in_group')
87 87 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
88 88 Session().commit()
89 89
90 90 def test_index_with_name_with_tags(self, autologin_user):
91 91 user = User.get_by_username('test_admin')
92 92 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
93 93 user.lastname = (
94 94 '<img src="/image2" onload="alert(\'Hello, World!\');">')
95 95 Session().add(user)
96 96 Session().commit()
97 97
98 98 response = self.app.get(url(controller='home', action='index'))
99 99 response.mustcontain(
100 100 '&lt;img src=&#34;/image1&#34; onload=&#34;'
101 101 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
102 102 response.mustcontain(
103 103 '&lt;img src=&#34;/image2&#34; onload=&#34;'
104 104 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
105 105
106 106 @pytest.mark.parametrize("name, state", [
107 107 ('Disabled', False),
108 108 ('Enabled', True),
109 109 ])
110 110 def test_index_show_version(self, autologin_user, name, state):
111 111 version_string = 'RhodeCode Enterprise %s' % rhodecode.__version__
112 112
113 show = SettingsModel().get_setting_by_name('show_version')
114 show.app_settings_value = state
115 Session().add(show)
113 sett = SettingsModel().create_or_update_setting(
114 'show_version', state, 'bool')
115 Session().add(sett)
116 116 Session().commit()
117 SettingsModel().invalidate_settings_cache()
118
117 119 response = self.app.get(url(controller='home', action='index'))
118 120 if state is True:
119 121 response.mustcontain(version_string)
120 122 if state is False:
121 123 response.mustcontain(no=[version_string])
122 124
123 125
124 126 class TestUserAutocompleteData(TestController):
125 127 def test_returns_list_of_users(self, user_util):
126 128 self.log_user()
127 129 user = user_util.create_user(is_active=True)
128 130 user_name = user.username
129 131 response = self.app.get(
130 132 url(controller='home', action='user_autocomplete_data'),
131 133 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
132 134 result = json.loads(response.body)
133 135 values = [suggestion['value'] for suggestion in result['suggestions']]
134 136 assert user_name in values
135 137
138 def test_returns_inactive_users_when_active_flag_sent(self, user_util):
139 self.log_user()
140 user = user_util.create_user(is_active=False)
141 user_name = user.username
142 response = self.app.get(
143 url(controller='home', action='user_autocomplete_data',
144 user_groups='true', active='0'),
145 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
146 result = json.loads(response.body)
147 values = [suggestion['value'] for suggestion in result['suggestions']]
148 assert user_name in values
149
136 150 def test_returns_groups_when_user_groups_sent(self, user_util):
137 151 self.log_user()
138 152 group = user_util.create_user_group(user_groups_active=True)
139 153 group_name = group.users_group_name
140 154 response = self.app.get(
141 155 url(controller='home', action='user_autocomplete_data',
142 156 user_groups='true'),
143 157 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
144 158 result = json.loads(response.body)
145 159 values = [suggestion['value'] for suggestion in result['suggestions']]
146 160 assert group_name in values
147 161
148 162 def test_result_is_limited_when_query_is_sent(self):
149 163 self.log_user()
150 164 fake_result = [
151 165 {
152 166 'first_name': 'John',
153 167 'value_display': 'hello{} (John Smith)'.format(i),
154 168 'icon_link': '/images/user14.png',
155 169 'value': 'hello{}'.format(i),
156 170 'last_name': 'Smith',
157 171 'username': 'hello{}'.format(i),
158 172 'id': i,
159 173 'value_type': u'user'
160 174 }
161 175 for i in range(10)
162 176 ]
163 177 users_patcher = patch.object(
164 178 RepoModel, 'get_users', return_value=fake_result)
165 179 groups_patcher = patch.object(
166 180 RepoModel, 'get_user_groups', return_value=fake_result)
167 181
168 182 query = 'hello'
169 183 with users_patcher as users_mock, groups_patcher as groups_mock:
170 184 response = self.app.get(
171 185 url(controller='home', action='user_autocomplete_data',
172 186 user_groups='true', query=query),
173 187 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
174 188
175 189 result = json.loads(response.body)
176 users_mock.assert_called_once_with(name_contains=query)
177 groups_mock.assert_called_once_with(name_contains=query)
190 users_mock.assert_called_once_with(
191 name_contains=query, only_active=True)
192 groups_mock.assert_called_once_with(
193 name_contains=query, only_active=True)
178 194 assert len(result['suggestions']) == 20
179 195
180 196
181 197 def assert_and_get_content(result):
182 198 repos = []
183 199 groups = []
184 200 commits = []
185 201 for data in result:
186 202 for data_item in data['children']:
187 203 assert data_item['id']
188 204 assert data_item['text']
189 205 assert data_item['url']
190 206 if data_item['type'] == 'repo':
191 207 repos.append(data_item)
192 208 elif data_item['type'] == 'group':
193 209 groups.append(data_item)
194 210 elif data_item['type'] == 'commit':
195 211 commits.append(data_item)
196 212 else:
197 213 raise Exception('invalid type %s' % data_item['type'])
198 214
199 215 return repos, groups, commits
200 216
201 217
202 218 class TestGotoSwitcherData(TestController):
203 219 required_repos_with_groups = [
204 220 'abc',
205 221 'abc-fork',
206 222 'forks/abcd',
207 223 'abcd',
208 224 'abcde',
209 225 'a/abc',
210 226 'aa/abc',
211 227 'aaa/abc',
212 228 'aaaa/abc',
213 229 'repos_abc/aaa/abc',
214 230 'abc_repos/abc',
215 231 'abc_repos/abcd',
216 232 'xxx/xyz',
217 233 'forked-abc/a/abc'
218 234 ]
219 235
220 236 @pytest.fixture(autouse=True, scope='class')
221 237 def prepare(self, request, pylonsapp):
222 238 for repo_and_group in self.required_repos_with_groups:
223 239 # create structure of groups and return the last group
224 240
225 241 repo_group = map_groups(repo_and_group)
226 242
227 243 RepoModel()._create_repo(
228 244 repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN,
229 245 repo_group=getattr(repo_group, 'group_id', None))
230 246
231 247 Session().commit()
232 248
233 249 request.addfinalizer(self.cleanup)
234 250
235 251 def cleanup(self):
236 252 # first delete all repos
237 253 for repo_and_groups in self.required_repos_with_groups:
238 254 repo = Repository.get_by_repo_name(repo_and_groups)
239 255 if repo:
240 256 RepoModel().delete(repo)
241 257 Session().commit()
242 258
243 259 # then delete all empty groups
244 260 for repo_and_groups in self.required_repos_with_groups:
245 261 if '/' in repo_and_groups:
246 262 r_group = repo_and_groups.rsplit('/', 1)[0]
247 263 repo_group = RepoGroup.get_by_group_name(r_group)
248 264 if not repo_group:
249 265 continue
250 266 parents = repo_group.parents
251 267 RepoGroupModel().delete(repo_group, force_delete=True)
252 268 Session().commit()
253 269
254 270 for el in reversed(parents):
255 271 RepoGroupModel().delete(el, force_delete=True)
256 272 Session().commit()
257 273
258 274 def test_returns_list_of_repos_and_groups(self):
259 275 self.log_user()
260 276
261 277 response = self.app.get(
262 278 url(controller='home', action='goto_switcher_data'),
263 279 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
264 280 result = json.loads(response.body)['results']
265 281
266 282 repos, groups, commits = assert_and_get_content(result)
267 283
268 284 assert len(repos) == len(Repository.get_all())
269 285 assert len(groups) == len(RepoGroup.get_all())
270 286 assert len(commits) == 0
271 287
272 288 def test_returns_list_of_repos_and_groups_filtered(self):
273 289 self.log_user()
274 290
275 291 response = self.app.get(
276 292 url(controller='home', action='goto_switcher_data'),
277 293 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
278 294 params={'query': 'abc'}, status=200)
279 295 result = json.loads(response.body)['results']
280 296
281 297 repos, groups, commits = assert_and_get_content(result)
282 298
283 299 assert len(repos) == 13
284 300 assert len(groups) == 5
285 301 assert len(commits) == 0
286 302
287 303 def test_returns_list_of_properly_sorted_and_filtered(self):
288 304 self.log_user()
289 305
290 306 response = self.app.get(
291 307 url(controller='home', action='goto_switcher_data'),
292 308 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
293 309 params={'query': 'abc'}, status=200)
294 310 result = json.loads(response.body)['results']
295 311
296 312 repos, groups, commits = assert_and_get_content(result)
297 313
298 314 test_repos = [x['text'] for x in repos[:4]]
299 315 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
300 316
301 317 test_groups = [x['text'] for x in groups[:4]]
302 318 assert ['abc_repos', 'repos_abc',
303 319 'forked-abc', 'forked-abc/a'] == test_groups
304 320
305 321
306 322 class TestRepoListData(TestController):
307 323 def test_returns_list_of_repos_and_groups(self, user_util):
308 324 self.log_user()
309 325
310 326 response = self.app.get(
311 327 url(controller='home', action='repo_list_data'),
312 328 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
313 329 result = json.loads(response.body)['results']
314 330
315 331 repos, groups, commits = assert_and_get_content(result)
316 332
317 333 assert len(repos) == len(Repository.get_all())
318 334 assert len(groups) == 0
319 335 assert len(commits) == 0
320 336
321 337 def test_returns_list_of_repos_and_groups_filtered(self):
322 338 self.log_user()
323 339
324 340 response = self.app.get(
325 341 url(controller='home', action='repo_list_data'),
326 342 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
327 343 params={'query': 'vcs_test_git'}, status=200)
328 344 result = json.loads(response.body)['results']
329 345
330 346 repos, groups, commits = assert_and_get_content(result)
331 347
332 348 assert len(repos) == len(Repository.query().filter(
333 349 Repository.repo_name.ilike('%vcs_test_git%')).all())
334 350 assert len(groups) == 0
335 351 assert len(commits) == 0
336 352
337 353 def test_returns_list_of_repos_and_groups_filtered_with_type(self):
338 354 self.log_user()
339 355
340 356 response = self.app.get(
341 357 url(controller='home', action='repo_list_data'),
342 358 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
343 359 params={'query': 'vcs_test_git', 'repo_type': 'git'}, status=200)
344 360 result = json.loads(response.body)['results']
345 361
346 362 repos, groups, commits = assert_and_get_content(result)
347 363
348 364 assert len(repos) == len(Repository.query().filter(
349 365 Repository.repo_name.ilike('%vcs_test_git%')).all())
350 366 assert len(groups) == 0
351 367 assert len(commits) == 0
352 368
353 369 def test_returns_list_of_repos_non_ascii_query(self):
354 370 self.log_user()
355 371 response = self.app.get(
356 372 url(controller='home', action='repo_list_data'),
357 373 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
358 374 params={'query': 'ć_vcs_test_ą', 'repo_type': 'git'}, status=200)
359 375 result = json.loads(response.body)['results']
360 376
361 377 repos, groups, commits = assert_and_get_content(result)
362 378
363 379 assert len(repos) == 0
364 380 assert len(groups) == 0
365 381 assert len(commits) == 0
@@ -1,519 +1,519 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import urlparse
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.config.routing import ADMIN_PREFIX
27 27 from rhodecode.tests import (
28 28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN)
29 29 from rhodecode.tests.fixture import Fixture
30 30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
31 31 from rhodecode.lib.auth import check_password, generate_auth_token
32 32 from rhodecode.lib import helpers as h
33 33 from rhodecode.model.auth_token import AuthTokenModel
34 34 from rhodecode.model import validators
35 35 from rhodecode.model.db import User, Notification
36 36 from rhodecode.model.meta import Session
37 37
38 38 fixture = Fixture()
39 39
40 40 # Hardcode URLs because we don't have a request object to use
41 41 # pyramids URL generation methods.
42 42 login_url = ADMIN_PREFIX + '/login'
43 43 logut_url = ADMIN_PREFIX + '/logout'
44 44 register_url = ADMIN_PREFIX + '/register'
45 45 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
46 46 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
47 47
48 48
49 49 @pytest.mark.usefixtures('app')
50 50 class TestLoginController:
51 51 destroy_users = set()
52 52
53 53 @classmethod
54 54 def teardown_class(cls):
55 55 fixture.destroy_users(cls.destroy_users)
56 56
57 57 def teardown_method(self, method):
58 58 for n in Notification.query().all():
59 59 Session().delete(n)
60 60
61 61 Session().commit()
62 62 assert Notification.query().all() == []
63 63
64 64 def test_index(self):
65 65 response = self.app.get(login_url)
66 66 assert response.status == '200 OK'
67 67 # Test response...
68 68
69 69 def test_login_admin_ok(self):
70 70 response = self.app.post(login_url,
71 71 {'username': 'test_admin',
72 72 'password': 'test12'})
73 73 assert response.status == '302 Found'
74 74 session = get_session_from_response(response)
75 75 username = session['rhodecode_user'].get('username')
76 76 assert username == 'test_admin'
77 77 response = response.follow()
78 78 response.mustcontain('/%s' % HG_REPO)
79 79
80 80 def test_login_regular_ok(self):
81 81 response = self.app.post(login_url,
82 82 {'username': 'test_regular',
83 83 'password': 'test12'})
84 84
85 85 assert response.status == '302 Found'
86 86 session = get_session_from_response(response)
87 87 username = session['rhodecode_user'].get('username')
88 88 assert username == 'test_regular'
89 89 response = response.follow()
90 90 response.mustcontain('/%s' % HG_REPO)
91 91
92 92 def test_login_ok_came_from(self):
93 93 test_came_from = '/_admin/users?branch=stable'
94 94 _url = '{}?came_from={}'.format(login_url, test_came_from)
95 95 response = self.app.post(
96 96 _url, {'username': 'test_admin', 'password': 'test12'})
97 97 assert response.status == '302 Found'
98 98 assert 'branch=stable' in response.location
99 99 response = response.follow()
100 100
101 101 assert response.status == '200 OK'
102 102 response.mustcontain('Users administration')
103 103
104 104 def test_redirect_to_login_with_get_args(self):
105 105 with fixture.anon_access(False):
106 106 kwargs = {'branch': 'stable'}
107 107 response = self.app.get(
108 108 url('summary_home', repo_name=HG_REPO, **kwargs))
109 109 assert response.status == '302 Found'
110 110 response_query = urlparse.parse_qsl(response.location)
111 111 assert 'branch=stable' in response_query[0][1]
112 112
113 113 def test_login_form_with_get_args(self):
114 114 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
115 115 response = self.app.get(_url)
116 116 assert 'branch%3Dstable' in response.form.action
117 117
118 118 @pytest.mark.parametrize("url_came_from", [
119 119 'data:text/html,<script>window.alert("xss")</script>',
120 120 'mailto:test@rhodecode.org',
121 121 'file:///etc/passwd',
122 122 'ftp://some.ftp.server',
123 123 'http://other.domain',
124 124 '/\r\nX-Forwarded-Host: http://example.org',
125 125 ])
126 126 def test_login_bad_came_froms(self, url_came_from):
127 127 _url = '{}?came_from={}'.format(login_url, url_came_from)
128 128 response = self.app.post(
129 129 _url,
130 130 {'username': 'test_admin', 'password': 'test12'})
131 131 assert response.status == '302 Found'
132 132 response = response.follow()
133 133 assert response.status == '200 OK'
134 134 assert response.request.path == '/'
135 135
136 136 def test_login_short_password(self):
137 137 response = self.app.post(login_url,
138 138 {'username': 'test_admin',
139 139 'password': 'as'})
140 140 assert response.status == '200 OK'
141 141
142 142 response.mustcontain('Enter 3 characters or more')
143 143
144 144 def test_login_wrong_non_ascii_password(self, user_regular):
145 145 response = self.app.post(
146 146 login_url,
147 147 {'username': user_regular.username,
148 148 'password': u'invalid-non-asci\xe4'.encode('utf8')})
149 149
150 150 response.mustcontain('invalid user name')
151 151 response.mustcontain('invalid password')
152 152
153 153 def test_login_with_non_ascii_password(self, user_util):
154 154 password = u'valid-non-ascii\xe4'
155 155 user = user_util.create_user(password=password)
156 156 response = self.app.post(
157 157 login_url,
158 158 {'username': user.username,
159 159 'password': password.encode('utf-8')})
160 160 assert response.status_code == 302
161 161
162 162 def test_login_wrong_username_password(self):
163 163 response = self.app.post(login_url,
164 164 {'username': 'error',
165 165 'password': 'test12'})
166 166
167 167 response.mustcontain('invalid user name')
168 168 response.mustcontain('invalid password')
169 169
170 170 def test_login_admin_ok_password_migration(self, real_crypto_backend):
171 171 from rhodecode.lib import auth
172 172
173 173 # create new user, with sha256 password
174 174 temp_user = 'test_admin_sha256'
175 175 user = fixture.create_user(temp_user)
176 176 user.password = auth._RhodeCodeCryptoSha256().hash_create(
177 177 b'test123')
178 178 Session().add(user)
179 179 Session().commit()
180 180 self.destroy_users.add(temp_user)
181 181 response = self.app.post(login_url,
182 182 {'username': temp_user,
183 183 'password': 'test123'})
184 184
185 185 assert response.status == '302 Found'
186 186 session = get_session_from_response(response)
187 187 username = session['rhodecode_user'].get('username')
188 188 assert username == temp_user
189 189 response = response.follow()
190 190 response.mustcontain('/%s' % HG_REPO)
191 191
192 192 # new password should be bcrypted, after log-in and transfer
193 193 user = User.get_by_username(temp_user)
194 194 assert user.password.startswith('$')
195 195
196 196 # REGISTRATIONS
197 197 def test_register(self):
198 198 response = self.app.get(register_url)
199 199 response.mustcontain('Create an Account')
200 200
201 201 def test_register_err_same_username(self):
202 202 uname = 'test_admin'
203 203 response = self.app.post(
204 204 register_url,
205 205 {
206 206 'username': uname,
207 207 'password': 'test12',
208 208 'password_confirmation': 'test12',
209 209 'email': 'goodmail@domain.com',
210 210 'firstname': 'test',
211 211 'lastname': 'test'
212 212 }
213 213 )
214 214
215 215 assertr = AssertResponse(response)
216 216 msg = validators.ValidUsername()._messages['username_exists']
217 217 msg = msg % {'username': uname}
218 218 assertr.element_contains('#username+.error-message', msg)
219 219
220 220 def test_register_err_same_email(self):
221 221 response = self.app.post(
222 222 register_url,
223 223 {
224 224 'username': 'test_admin_0',
225 225 'password': 'test12',
226 226 'password_confirmation': 'test12',
227 227 'email': 'test_admin@mail.com',
228 228 'firstname': 'test',
229 229 'lastname': 'test'
230 230 }
231 231 )
232 232
233 233 assertr = AssertResponse(response)
234 234 msg = validators.UniqSystemEmail()()._messages['email_taken']
235 235 assertr.element_contains('#email+.error-message', msg)
236 236
237 237 def test_register_err_same_email_case_sensitive(self):
238 238 response = self.app.post(
239 239 register_url,
240 240 {
241 241 'username': 'test_admin_1',
242 242 'password': 'test12',
243 243 'password_confirmation': 'test12',
244 244 'email': 'TesT_Admin@mail.COM',
245 245 'firstname': 'test',
246 246 'lastname': 'test'
247 247 }
248 248 )
249 249 assertr = AssertResponse(response)
250 250 msg = validators.UniqSystemEmail()()._messages['email_taken']
251 251 assertr.element_contains('#email+.error-message', msg)
252 252
253 253 def test_register_err_wrong_data(self):
254 254 response = self.app.post(
255 255 register_url,
256 256 {
257 257 'username': 'xs',
258 258 'password': 'test',
259 259 'password_confirmation': 'test',
260 260 'email': 'goodmailm',
261 261 'firstname': 'test',
262 262 'lastname': 'test'
263 263 }
264 264 )
265 265 assert response.status == '200 OK'
266 266 response.mustcontain('An email address must contain a single @')
267 267 response.mustcontain('Enter a value 6 characters long or more')
268 268
269 269 def test_register_err_username(self):
270 270 response = self.app.post(
271 271 register_url,
272 272 {
273 273 'username': 'error user',
274 274 'password': 'test12',
275 275 'password_confirmation': 'test12',
276 276 'email': 'goodmailm',
277 277 'firstname': 'test',
278 278 'lastname': 'test'
279 279 }
280 280 )
281 281
282 282 response.mustcontain('An email address must contain a single @')
283 283 response.mustcontain(
284 284 'Username may only contain '
285 285 'alphanumeric characters underscores, '
286 286 'periods or dashes and must begin with '
287 287 'alphanumeric character')
288 288
289 289 def test_register_err_case_sensitive(self):
290 290 usr = 'Test_Admin'
291 291 response = self.app.post(
292 292 register_url,
293 293 {
294 294 'username': usr,
295 295 'password': 'test12',
296 296 'password_confirmation': 'test12',
297 297 'email': 'goodmailm',
298 298 'firstname': 'test',
299 299 'lastname': 'test'
300 300 }
301 301 )
302 302
303 303 assertr = AssertResponse(response)
304 304 msg = validators.ValidUsername()._messages['username_exists']
305 305 msg = msg % {'username': usr}
306 306 assertr.element_contains('#username+.error-message', msg)
307 307
308 308 def test_register_special_chars(self):
309 309 response = self.app.post(
310 310 register_url,
311 311 {
312 312 'username': 'xxxaxn',
313 313 'password': 'ąćźżąśśśś',
314 314 'password_confirmation': 'ąćźżąśśśś',
315 315 'email': 'goodmailm@test.plx',
316 316 'firstname': 'test',
317 317 'lastname': 'test'
318 318 }
319 319 )
320 320
321 321 msg = validators.ValidPassword()._messages['invalid_password']
322 322 response.mustcontain(msg)
323 323
324 324 def test_register_password_mismatch(self):
325 325 response = self.app.post(
326 326 register_url,
327 327 {
328 328 'username': 'xs',
329 329 'password': '123qwe',
330 330 'password_confirmation': 'qwe123',
331 331 'email': 'goodmailm@test.plxa',
332 332 'firstname': 'test',
333 333 'lastname': 'test'
334 334 }
335 335 )
336 336 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
337 337 response.mustcontain(msg)
338 338
339 339 def test_register_ok(self):
340 340 username = 'test_regular4'
341 341 password = 'qweqwe'
342 342 email = 'marcin@test.com'
343 343 name = 'testname'
344 344 lastname = 'testlastname'
345 345
346 346 response = self.app.post(
347 347 register_url,
348 348 {
349 349 'username': username,
350 350 'password': password,
351 351 'password_confirmation': password,
352 352 'email': email,
353 353 'firstname': name,
354 354 'lastname': lastname,
355 355 'admin': True
356 356 }
357 357 ) # This should be overriden
358 358 assert response.status == '302 Found'
359 359 assert_session_flash(
360 360 response, 'You have successfully registered with RhodeCode')
361 361
362 362 ret = Session().query(User).filter(
363 363 User.username == 'test_regular4').one()
364 364 assert ret.username == username
365 365 assert check_password(password, ret.password)
366 366 assert ret.email == email
367 367 assert ret.name == name
368 368 assert ret.lastname == lastname
369 369 assert ret.api_key is not None
370 370 assert not ret.admin
371 371
372 372 def test_forgot_password_wrong_mail(self):
373 373 bad_email = 'marcin@wrongmail.org'
374 374 response = self.app.post(
375 375 pwd_reset_url,
376 376 {'email': bad_email, }
377 377 )
378 378
379 379 msg = validators.ValidSystemEmail()._messages['non_existing_email']
380 380 msg = h.html_escape(msg % {'email': bad_email})
381 381 response.mustcontain()
382 382
383 383 def test_forgot_password(self):
384 384 response = self.app.get(pwd_reset_url)
385 385 assert response.status == '200 OK'
386 386
387 387 username = 'test_password_reset_1'
388 388 password = 'qweqwe'
389 389 email = 'marcin@python-works.com'
390 390 name = 'passwd'
391 391 lastname = 'reset'
392 392
393 393 new = User()
394 394 new.username = username
395 395 new.password = password
396 396 new.email = email
397 397 new.name = name
398 398 new.lastname = lastname
399 399 new.api_key = generate_auth_token(username)
400 400 Session().add(new)
401 401 Session().commit()
402 402
403 403 response = self.app.post(pwd_reset_url,
404 404 {'email': email, })
405 405
406 406 assert_session_flash(
407 407 response, 'Your password reset link was sent')
408 408
409 409 response = response.follow()
410 410
411 411 # BAD KEY
412 412
413 413 key = "bad"
414 414 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
415 415 response = self.app.get(confirm_url)
416 416 assert response.status == '302 Found'
417 417 assert response.location.endswith(pwd_reset_url)
418 418
419 419 # GOOD KEY
420 420
421 421 key = User.get_by_username(username).api_key
422 422 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
423 423 response = self.app.get(confirm_url)
424 424 assert response.status == '302 Found'
425 425 assert response.location.endswith(login_url)
426 426
427 427 assert_session_flash(
428 428 response,
429 429 'Your password reset was successful, '
430 430 'a new password has been sent to your email')
431 431
432 432 response = response.follow()
433 433
434 434 def _get_api_whitelist(self, values=None):
435 435 config = {'api_access_controllers_whitelist': values or []}
436 436 return config
437 437
438 438 @pytest.mark.parametrize("test_name, auth_token", [
439 439 ('none', None),
440 440 ('empty_string', ''),
441 441 ('fake_number', '123456'),
442 442 ('proper_auth_token', None)
443 443 ])
444 444 def test_access_not_whitelisted_page_via_auth_token(self, test_name,
445 445 auth_token):
446 446 whitelist = self._get_api_whitelist([])
447 447 with mock.patch.dict('rhodecode.CONFIG', whitelist):
448 448 assert [] == whitelist['api_access_controllers_whitelist']
449 449 if test_name == 'proper_auth_token':
450 450 # use builtin if api_key is None
451 auth_token = User.get_first_admin().api_key
451 auth_token = User.get_first_super_admin().api_key
452 452
453 453 with fixture.anon_access(False):
454 454 self.app.get(url(controller='changeset',
455 455 action='changeset_raw',
456 456 repo_name=HG_REPO, revision='tip',
457 457 api_key=auth_token),
458 458 status=302)
459 459
460 460 @pytest.mark.parametrize("test_name, auth_token, code", [
461 461 ('none', None, 302),
462 462 ('empty_string', '', 302),
463 463 ('fake_number', '123456', 302),
464 464 ('proper_auth_token', None, 200)
465 465 ])
466 466 def test_access_whitelisted_page_via_auth_token(self, test_name,
467 467 auth_token, code):
468 468 whitelist = self._get_api_whitelist(
469 469 ['ChangesetController:changeset_raw'])
470 470 with mock.patch.dict('rhodecode.CONFIG', whitelist):
471 471 assert ['ChangesetController:changeset_raw'] == \
472 472 whitelist['api_access_controllers_whitelist']
473 473 if test_name == 'proper_auth_token':
474 auth_token = User.get_first_admin().api_key
474 auth_token = User.get_first_super_admin().api_key
475 475
476 476 with fixture.anon_access(False):
477 477 self.app.get(url(controller='changeset',
478 478 action='changeset_raw',
479 479 repo_name=HG_REPO, revision='tip',
480 480 api_key=auth_token),
481 481 status=code)
482 482
483 483 def test_access_page_via_extra_auth_token(self):
484 484 whitelist = self._get_api_whitelist(
485 485 ['ChangesetController:changeset_raw'])
486 486 with mock.patch.dict('rhodecode.CONFIG', whitelist):
487 487 assert ['ChangesetController:changeset_raw'] == \
488 488 whitelist['api_access_controllers_whitelist']
489 489
490 490 new_auth_token = AuthTokenModel().create(
491 491 TEST_USER_ADMIN_LOGIN, 'test')
492 492 Session().commit()
493 493 with fixture.anon_access(False):
494 494 self.app.get(url(controller='changeset',
495 495 action='changeset_raw',
496 496 repo_name=HG_REPO, revision='tip',
497 497 api_key=new_auth_token.api_key),
498 498 status=200)
499 499
500 500 def test_access_page_via_expired_auth_token(self):
501 501 whitelist = self._get_api_whitelist(
502 502 ['ChangesetController:changeset_raw'])
503 503 with mock.patch.dict('rhodecode.CONFIG', whitelist):
504 504 assert ['ChangesetController:changeset_raw'] == \
505 505 whitelist['api_access_controllers_whitelist']
506 506
507 507 new_auth_token = AuthTokenModel().create(
508 508 TEST_USER_ADMIN_LOGIN, 'test')
509 509 Session().commit()
510 510 # patch the api key and make it expired
511 511 new_auth_token.expires = 0
512 512 Session().add(new_auth_token)
513 513 Session().commit()
514 514 with fixture.anon_access(False):
515 515 self.app.get(url(controller='changeset',
516 516 action='changeset_raw',
517 517 repo_name=HG_REPO, revision='tip',
518 518 api_key=new_auth_token.api_key),
519 519 status=302)
@@ -1,54 +1,53 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
22 from pyramid.response import Response
23 from pyramid.testing import DummyRequest
24 from rhodecode.lib.middleware.disable_vcs import (
25 DisableVCSPagesWrapper, VCSServerUnavailable)
23 26
24 27
25 @pytest.mark.parametrize('url, expected_url', [
26 ('/', '/'),
27 ('/_admin/settings', '/_admin/settings'),
28 ('/_admin/i_am_fine', '/_admin/i_am_fine'),
29 ('/_admin/settings/mappings', '/error/vcs_unavailable'),
30 ('/_admin/my_account/repos', '/error/vcs_unavailable'),
31 ('/_admin/create_repository', '/error/vcs_unavailable'),
32 ('/_admin/gists/1', '/error/vcs_unavailable'),
33 ('/_admin/notifications/1', '/error/vcs_unavailable'),
28 @pytest.mark.parametrize('url, should_raise', [
29 ('/', False),
30 ('/_admin/settings', False),
31 ('/_admin/i_am_fine', False),
32 ('/_admin/settings/mappings', True),
33 ('/_admin/my_account/repos', True),
34 ('/_admin/create_repository', True),
35 ('/_admin/gists/1', True),
36 ('/_admin/notifications/1', True),
34 37 ])
35 def test_vcs_disabled(url, expected_url):
36 app = DisableVCSPagesWrapper(app=SimpleApp())
37 assert expected_url == app(get_environ(url), None)
38
38 def test_vcs_disabled(url, should_raise):
39 wrapped_view = DisableVCSPagesWrapper(pyramid_view)
40 request = DummyRequest(path=url)
39 41
40 def get_environ(url):
41 """Construct a minimum WSGI environ based on the URL."""
42 environ = {
43 'PATH_INFO': url,
44 }
45 return environ
46
42 if should_raise:
43 with pytest.raises(VCSServerUnavailable):
44 response = wrapped_view(None, request)
45 else:
46 response = wrapped_view(None, request)
47 assert response.status_int == 200
47 48
48 class SimpleApp(object):
49 def pyramid_view(context, request):
49 50 """
50 A mock app to be used in the wrapper that returns the modified URL
51 from the middleware
51 A mock pyramid view to be used in the wrapper
52 52 """
53 def __call__(self, environ, start_response):
54 return environ['PATH_INFO']
53 return Response('success')
@@ -1,40 +1,76 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 from rhodecode.lib.encrypt import AESCipher
23 from rhodecode.lib.encrypt import (
24 AESCipher, SignatureVerificationError, InvalidDecryptedValue)
24 25
25 26
26 27 class TestEncryptModule(object):
27 28
28 29 @pytest.mark.parametrize(
29 30 "key, text",
30 31 [
31 32 ('a', 'short'),
32 33 ('a'*64, 'too long(trimmed to 32)'),
33 34 ('a'*32, 'just enough'),
34 35 ('ąćęćę', 'non asci'),
35 36 ('$asa$asa', 'special $ used'),
36 37 ]
37 38 )
38 39 def test_encryption(self, key, text):
39 40 enc = AESCipher(key).encrypt(text)
40 41 assert AESCipher(key).decrypt(enc) == text
42
43 def test_encryption_with_hmac(self):
44 key = 'secret'
45 text = 'ihatemysql'
46 enc = AESCipher(key, hmac=True).encrypt(text)
47 assert AESCipher(key, hmac=True).decrypt(enc) == text
48
49 def test_encryption_with_hmac_with_bad_key(self):
50 key = 'secretstring'
51 text = 'ihatemysql'
52 enc = AESCipher(key, hmac=True).encrypt(text)
53
54 with pytest.raises(SignatureVerificationError) as e:
55 assert AESCipher('differentsecret', hmac=True).decrypt(enc) == ''
56
57 assert 'Encryption signature verification failed' in str(e)
58
59 def test_encryption_with_hmac_with_bad_data(self):
60 key = 'secret'
61 text = 'ihatemysql'
62 enc = AESCipher(key, hmac=True).encrypt(text)
63 enc = 'xyz' + enc[3:]
64 with pytest.raises(SignatureVerificationError) as e:
65 assert AESCipher(key, hmac=True).decrypt(enc) == text
66
67 assert 'Encryption signature verification failed' in str(e)
68
69 def test_encryption_with_hmac_with_bad_key_not_strict(self):
70 key = 'secretstring'
71 text = 'ihatemysql'
72 enc = AESCipher(key, hmac=True).encrypt(text)
73
74 assert isinstance(AESCipher(
75 'differentsecret', hmac=True, strict_verification=False
76 ).decrypt(enc), InvalidDecryptedValue)
@@ -1,196 +1,210 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import copy
22 22 import mock
23 23 import pytest
24 24
25 25 from pylons.util import ContextObj
26 26
27 27 from rhodecode.lib import helpers
28 28 from rhodecode.lib.utils2 import AttributeDict
29 29 from rhodecode.model.settings import IssueTrackerSettingsModel
30 30
31 31
32 32 @pytest.mark.parametrize('url, expected_url', [
33 33 ('http://rc.rc/test', '<a href="http://rc.rc/test">http://rc.rc/test</a>'),
34 34 ('http://rc.rc/@foo', '<a href="http://rc.rc/@foo">http://rc.rc/@foo</a>'),
35 35 ('http://rc.rc/!foo', '<a href="http://rc.rc/!foo">http://rc.rc/!foo</a>'),
36 36 ('http://rc.rc/&foo', '<a href="http://rc.rc/&foo">http://rc.rc/&foo</a>'),
37 37 ('http://rc.rc/#foo', '<a href="http://rc.rc/#foo">http://rc.rc/#foo</a>'),
38 38 ])
39 39 def test_urlify_text(url, expected_url):
40 40 assert helpers.urlify_text(url) == expected_url
41 41
42 42
43 43 @pytest.mark.parametrize('repo_name, commit_id, path, expected_result', [
44 44 ('rX<X', 'cX<X', 'pX<X/aX<X/bX<X',
45 45 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/">rX&lt;X</a>/'
46 46 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/pX%3CX">pX&lt;X</a>/'
47 47 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/pX%3CX/aX%3CX">aX&lt;X'
48 48 '</a>/bX&lt;X'),
49 49 # Path with only one segment
50 50 ('rX<X', 'cX<X', 'pX<X',
51 51 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/">rX&lt;X</a>/pX&lt;X'),
52 52 # Empty path
53 53 ('rX<X', 'cX<X', '', 'rX&lt;X'),
54 54 ('rX"X', 'cX"X', 'pX"X/aX"X/bX"X',
55 55 '<a class="pjax-link" href="/rX%22X/files/cX%22X/">rX&#34;X</a>/'
56 56 '<a class="pjax-link" href="/rX%22X/files/cX%22X/pX%22X">pX&#34;X</a>/'
57 57 '<a class="pjax-link" href="/rX%22X/files/cX%22X/pX%22X/aX%22X">aX&#34;X'
58 58 '</a>/bX&#34;X'),
59 59 ], ids=['simple', 'one_segment', 'empty_path', 'simple_quote'])
60 60 def test_files_breadcrumbs_xss(
61 61 repo_name, commit_id, path, pylonsapp, expected_result):
62 62 result = helpers.files_breadcrumbs(repo_name, commit_id, path)
63 63 # Expect it to encode all path fragments properly. This is important
64 64 # because it returns an instance of `literal`.
65 65 assert result == expected_result
66 66
67 67
68 68 def test_format_binary():
69 69 assert helpers.format_byte_size_binary(298489462784) == '278.0 GiB'
70 70
71 71
72 72 @pytest.mark.parametrize('text_string, pattern, expected_text', [
73 73 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
74 74 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'
75 75 ),
76 76 ('Fix #42', '(?:#)?<issue_id>\d+)', 'Fix #42'), # Broken regex
77 77 ])
78 78 def test_process_patterns_repo(backend, text_string, pattern, expected_text):
79 79 repo = backend.create_repo()
80 80 config = {'123': {
81 81 'uid': '123',
82 82 'pat': pattern,
83 83 'url': 'http://r.io/${repo}/i/${issue_id}',
84 84 'pref': '#',
85 85 }
86 86 }
87
88 def get_settings_mock(self, cache=True):
89 return config
90
87 91 with mock.patch.object(IssueTrackerSettingsModel,
88 'get_settings', lambda s: config):
92 'get_settings', get_settings_mock):
89 93 processed_text = helpers.process_patterns(
90 94 text_string, repo.repo_name, config)
91 95
92 96 assert processed_text == expected_text.format(repo=repo.repo_name)
93 97
94 98
95 99 @pytest.mark.parametrize('text_string, pattern, expected_text', [
96 100 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
97 101 'Fix <a class="issue-tracker-link" href="http://r.io/i/42">#42</a>'
98 102 ),
99 103 ('Fix #42', '(?:#)?<issue_id>\d+)', 'Fix #42'), # Broken regex
100 104 ])
101 105 def test_process_patterns_no_repo(text_string, pattern, expected_text):
102 106 config = {'123': {
103 107 'uid': '123',
104 108 'pat': pattern,
105 109 'url': 'http://r.io/i/${issue_id}',
106 110 'pref': '#',
107 111 }
108 112 }
113
114 def get_settings_mock(self, cache=True):
115 return config
116
109 117 with mock.patch.object(IssueTrackerSettingsModel,
110 'get_global_settings', lambda s, cache: config):
118 'get_global_settings', get_settings_mock):
111 119 processed_text = helpers.process_patterns(
112 120 text_string, '', config)
113 121
114 122 assert processed_text == expected_text
115 123
116 124
117 125 def test_process_patterns_non_existent_repo_name(backend):
118 126 text_string = 'Fix #42'
119 127 pattern = '(?:#)(?P<issue_id>\d+)'
120 128 expected_text = ('Fix <a class="issue-tracker-link" '
121 129 'href="http://r.io/do-not-exist/i/42">#42</a>')
122 130 config = {'123': {
123 131 'uid': '123',
124 132 'pat': pattern,
125 133 'url': 'http://r.io/${repo}/i/${issue_id}',
126 134 'pref': '#',
127 135 }
128 136 }
137
138 def get_settings_mock(self, cache=True):
139 return config
140
129 141 with mock.patch.object(IssueTrackerSettingsModel,
130 'get_global_settings', lambda s, cache: config):
142 'get_global_settings', get_settings_mock):
131 143 processed_text = helpers.process_patterns(
132 144 text_string, 'do-not-exist', config)
133 145
134 146 assert processed_text == expected_text
135 147
136 148
137 149 def test_get_visual_attr(pylonsapp):
138 150 c = ContextObj()
139 151 assert None is helpers.get_visual_attr(c, 'fakse')
140 152
141 153 # emulate the c.visual behaviour
142 154 c.visual = AttributeDict({})
143 155 assert None is helpers.get_visual_attr(c, 'some_var')
144 156
145 157 c.visual.some_var = 'foobar'
146 158 assert 'foobar' == helpers.get_visual_attr(c, 'some_var')
147 159
148 160
149 161 @pytest.mark.parametrize('test_text, inclusive, expected_text', [
150 162 ('just a string', False, 'just a string'),
151 163 ('just a string\n', False, 'just a string'),
152 164 ('just a string\n next line', False, 'just a string...'),
153 165 ('just a string\n next line', True, 'just a string\n...'),
154 166 ])
155 167 def test_chop_at(test_text, inclusive, expected_text):
156 168 assert helpers.chop_at_smart(
157 169 test_text, '\n', inclusive, '...') == expected_text
158 170
159 171
160 172 @pytest.mark.parametrize('test_text, expected_output', [
161 173 ('some text', ['some', 'text']),
162 174 ('some text', ['some', 'text']),
163 175 ('some text "with a phrase"', ['some', 'text', 'with a phrase']),
164 176 ('"a phrase" "another phrase"', ['a phrase', 'another phrase']),
165 177 ('"justphrase"', ['justphrase']),
166 178 ('""', []),
167 179 ('', []),
168 180 (' ', []),
169 181 ('" "', []),
170 182 ])
171 183 def test_extract_phrases(test_text, expected_output):
172 184 assert helpers.extract_phrases(test_text) == expected_output
173 185
174 186
175 187 @pytest.mark.parametrize('test_text, text_phrases, expected_output', [
176 188 ('some text here', ['some', 'here'], [(0, 4), (10, 14)]),
177 189 ('here here there', ['here'], [(0, 4), (5, 9), (11, 15)]),
178 190 ('irrelevant', ['not found'], []),
179 191 ('irrelevant', ['not found'], []),
180 192 ])
181 193 def test_get_matching_offsets(test_text, text_phrases, expected_output):
182 194 assert helpers.get_matching_offsets(
183 195 test_text, text_phrases) == expected_output
184 196
197
185 198 def test_normalize_text_for_matching():
186 199 assert helpers.normalize_text_for_matching(
187 200 'OJjfe)*#$*@)$JF*)3r2f80h') == 'ojjfe jf 3r2f80h'
188 201
202
189 203 def test_get_matching_line_offsets():
190 204 assert helpers.get_matching_line_offsets([
191 205 'words words words',
192 206 'words words words',
193 207 'some text some',
194 208 'words words words',
195 209 'words words words',
196 'text here what'], 'text') == {3: [(5, 9)], 6: [(0, 4)]} No newline at end of file
210 'text here what'], 'text') == {3: [(5, 9)], 6: [(0, 4)]}
@@ -1,466 +1,467 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import json
22 22 import multiprocessing
23 23 import os
24 24
25 25 import mock
26 26 import py
27 27 import pytest
28 28
29 29 from rhodecode.lib import caching_query
30 30 from rhodecode.lib import utils
31 31 from rhodecode.lib.utils2 import md5
32 from rhodecode.model import settings
32 33 from rhodecode.model import db
33 34 from rhodecode.model import meta
34 35 from rhodecode.model.repo import RepoModel
35 36 from rhodecode.model.repo_group import RepoGroupModel
36 37 from rhodecode.model.scm import ScmModel
37 38 from rhodecode.model.settings import UiSetting, SettingsModel
38 39 from rhodecode.tests.fixture import Fixture
39 40 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
40 41
41 42
42 43 fixture = Fixture()
43 44
44 45
45 46 def extract_hooks(config):
46 47 """Return a dictionary with the hook entries of the given config."""
47 48 hooks = {}
48 49 config_items = config.serialize()
49 50 for section, name, value in config_items:
50 51 if section != 'hooks':
51 52 continue
52 53 hooks[name] = value
53 54
54 55 return hooks
55 56
56 57
57 58 def disable_hooks(request, hooks):
58 59 """Disables the given hooks from the UI settings."""
59 60 session = meta.Session()
60 61
61 62 model = SettingsModel()
62 63 for hook_key in hooks:
63 64 sett = model.get_ui_by_key(hook_key)
64 65 sett.ui_active = False
65 66 session.add(sett)
66 67
67 68 # Invalidate cache
68 69 ui_settings = session.query(db.RhodeCodeUi).options(
69 70 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
70 71 ui_settings.invalidate()
71 72
72 73 ui_settings = session.query(db.RhodeCodeUi).options(
73 74 caching_query.FromCache(
74 75 'sql_cache_short', 'get_hook_settings', 'get_hook_settings'))
75 76 ui_settings.invalidate()
76 77
77 78 @request.addfinalizer
78 79 def rollback():
79 80 session.rollback()
80 81
81 82
82 83 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
83 84 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
84 85 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
85 86 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
86 87 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
87 88
88 89 HG_HOOKS = frozenset(
89 90 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PUSH, HOOK_REPO_SIZE))
90 91
91 92
92 93 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
93 94 ([], HG_HOOKS),
94 95 ([HOOK_PRE_PUSH, HOOK_REPO_SIZE], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
95 96 (HG_HOOKS, []),
96 97 # When a pull/push hook is disabled, its pre-pull/push counterpart should
97 98 # be disabled too.
98 99 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
99 100 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PUSH, HOOK_REPO_SIZE]),
100 101 ])
101 102 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
102 103 expected_hooks):
103 104 disable_hooks(request, disabled_hooks)
104 105
105 106 config = utils.make_db_config()
106 107 hooks = extract_hooks(config)
107 108
108 109 assert set(hooks.iterkeys()).intersection(HG_HOOKS) == set(expected_hooks)
109 110
110 111
111 112 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
112 113 ([], ['pull', 'push']),
113 114 ([HOOK_PUSH], ['pull']),
114 115 ([HOOK_PULL], ['push']),
115 116 ([HOOK_PULL, HOOK_PUSH], []),
116 117 ])
117 118 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
118 119 hook_keys = (HOOK_PUSH, HOOK_PULL)
119 120 ui_settings = [
120 121 ('hooks', key, 'some value', key not in disabled_hooks)
121 122 for key in hook_keys]
122 123
123 124 result = utils.get_enabled_hook_classes(ui_settings)
124 125 assert sorted(result) == expected_hooks
125 126
126 127
127 128 def test_get_filesystem_repos_finds_repos(tmpdir, pylonsapp):
128 129 _stub_git_repo(tmpdir.ensure('repo', dir=True))
129 130 repos = list(utils.get_filesystem_repos(str(tmpdir)))
130 131 assert repos == [('repo', ('git', tmpdir.join('repo')))]
131 132
132 133
133 134 def test_get_filesystem_repos_skips_directories(tmpdir, pylonsapp):
134 135 tmpdir.ensure('not-a-repo', dir=True)
135 136 repos = list(utils.get_filesystem_repos(str(tmpdir)))
136 137 assert repos == []
137 138
138 139
139 140 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, pylonsapp):
140 141 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
141 142 repos = list(utils.get_filesystem_repos(str(tmpdir)))
142 143 assert repos == []
143 144
144 145
145 146 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, pylonsapp):
146 147 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
147 148 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
148 149 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
149 150
150 151
151 152 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
152 153 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
153 154 repos = list(utils.get_filesystem_repos(str(tmpdir)))
154 155 assert repos == []
155 156
156 157
157 158 def test_get_filesystem_repos_skips_files(tmpdir):
158 159 tmpdir.ensure('test-file')
159 160 repos = list(utils.get_filesystem_repos(str(tmpdir)))
160 161 assert repos == []
161 162
162 163
163 164 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
164 165 removed_repo_name = 'rm__00000000_000000_000000__.stub'
165 166 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
166 167 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
167 168 repos = list(utils.get_filesystem_repos(str(tmpdir)))
168 169 assert repos == []
169 170
170 171
171 172 def _stub_git_repo(repo_path):
172 173 """
173 174 Make `repo_path` look like a Git repository.
174 175 """
175 176 repo_path.ensure('.git', dir=True)
176 177
177 178
178 179 @pytest.mark.parametrize('str_class', [str, unicode], ids=['str', 'unicode'])
179 180 def test_get_dirpaths_returns_all_paths(tmpdir, str_class):
180 181 tmpdir.ensure('test-file')
181 182 dirpaths = utils._get_dirpaths(str_class(tmpdir))
182 183 assert dirpaths == ['test-file']
183 184
184 185
185 186 def test_get_dirpaths_returns_all_paths_bytes(
186 187 tmpdir, platform_encodes_filenames):
187 188 if platform_encodes_filenames:
188 189 pytest.skip("This platform seems to encode filenames.")
189 190 tmpdir.ensure('repo-a-umlaut-\xe4')
190 191 dirpaths = utils._get_dirpaths(str(tmpdir))
191 192 assert dirpaths == ['repo-a-umlaut-\xe4']
192 193
193 194
194 195 def test_get_dirpaths_skips_paths_it_cannot_decode(
195 196 tmpdir, platform_encodes_filenames):
196 197 if platform_encodes_filenames:
197 198 pytest.skip("This platform seems to encode filenames.")
198 199 path_with_latin1 = 'repo-a-umlaut-\xe4'
199 200 tmpdir.ensure(path_with_latin1)
200 201 dirpaths = utils._get_dirpaths(unicode(tmpdir))
201 202 assert dirpaths == []
202 203
203 204
204 205 @pytest.fixture(scope='session')
205 206 def platform_encodes_filenames():
206 207 """
207 208 Boolean indicator if the current platform changes filename encodings.
208 209 """
209 210 path_with_latin1 = 'repo-a-umlaut-\xe4'
210 211 tmpdir = py.path.local.mkdtemp()
211 212 tmpdir.ensure(path_with_latin1)
212 213 read_path = tmpdir.listdir()[0].basename
213 214 tmpdir.remove()
214 215 return path_with_latin1 != read_path
215 216
216 217
217 218 def test_action_logger_action_size(pylonsapp, test_repo):
218 219 action = 'x' * 1200001
219 220 utils.action_logger(TEST_USER_ADMIN_LOGIN, action, test_repo, commit=True)
220 221
221 222
222 223 @pytest.fixture
223 224 def repo_groups(request):
224 225 session = meta.Session()
225 226 zombie_group = fixture.create_repo_group('zombie')
226 227 parent_group = fixture.create_repo_group('parent')
227 228 child_group = fixture.create_repo_group('parent/child')
228 229 groups_in_db = session.query(db.RepoGroup).all()
229 230 assert len(groups_in_db) == 3
230 231 assert child_group.group_parent_id == parent_group.group_id
231 232
232 233 @request.addfinalizer
233 234 def cleanup():
234 235 fixture.destroy_repo_group(zombie_group)
235 236 fixture.destroy_repo_group(child_group)
236 237 fixture.destroy_repo_group(parent_group)
237 238
238 239 return (zombie_group, parent_group, child_group)
239 240
240 241
241 242 def test_repo2db_mapper_groups(repo_groups):
242 243 session = meta.Session()
243 244 zombie_group, parent_group, child_group = repo_groups
244 245 zombie_path = os.path.join(
245 246 RepoGroupModel().repos_path, zombie_group.full_path)
246 247 os.rmdir(zombie_path)
247 248
248 249 # Avoid removing test repos when calling repo2db_mapper
249 250 repo_list = {
250 251 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
251 252 }
252 253 utils.repo2db_mapper(repo_list, remove_obsolete=True)
253 254
254 255 groups_in_db = session.query(db.RepoGroup).all()
255 256 assert child_group in groups_in_db
256 257 assert parent_group in groups_in_db
257 258 assert zombie_path not in groups_in_db
258 259
259 260
260 261 def test_repo2db_mapper_enables_largefiles(backend):
261 262 repo = backend.create_repo()
262 263 repo_list = {repo.repo_name: 'test'}
263 264 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
264 265 with mock.patch.multiple('rhodecode.model.scm.ScmModel',
265 266 install_git_hook=mock.DEFAULT,
266 267 install_svn_hooks=mock.DEFAULT):
267 268 utils.repo2db_mapper(repo_list, remove_obsolete=False)
268 269 _, kwargs = scm_mock.call_args
269 270 assert kwargs['config'].get('extensions', 'largefiles') == ''
270 271
271 272
272 273 @pytest.mark.backends("git", "svn")
273 274 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
274 275 repo = backend.create_repo()
275 276 repo_list = {repo.repo_name: 'test'}
276 277 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
277 278 utils.repo2db_mapper(repo_list, remove_obsolete=False)
278 279 install_hooks_mock.assert_called_once_with(
279 280 repo.scm_instance(), repo_type=backend.alias)
280 281
281 282
282 283 @pytest.mark.backends("git", "svn")
283 284 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
284 285 repo = backend.create_repo()
285 286 RepoModel().delete(repo, fs_remove=False)
286 287 meta.Session().commit()
287 288 repo_list = {repo.repo_name: repo.scm_instance()}
288 289 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
289 290 utils.repo2db_mapper(repo_list, remove_obsolete=False)
290 291 assert install_hooks_mock.call_count == 1
291 292 install_hooks_args, _ = install_hooks_mock.call_args
292 293 assert install_hooks_args[0].name == repo.repo_name
293 294
294 295
295 296 class TestPasswordChanged(object):
296 297 def setup(self):
297 298 self.session = {
298 299 'rhodecode_user': {
299 300 'password': '0cc175b9c0f1b6a831c399e269772661'
300 301 }
301 302 }
302 303 self.auth_user = mock.Mock()
303 304 self.auth_user.userame = 'test'
304 305 self.auth_user.password = 'abc123'
305 306
306 307 def test_returns_false_for_default_user(self):
307 308 self.auth_user.username = db.User.DEFAULT_USER
308 309 result = utils.password_changed(self.auth_user, self.session)
309 310 assert result is False
310 311
311 312 def test_returns_false_if_password_was_not_changed(self):
312 313 self.session['rhodecode_user']['password'] = md5(
313 314 self.auth_user.password)
314 315 result = utils.password_changed(self.auth_user, self.session)
315 316 assert result is False
316 317
317 318 def test_returns_true_if_password_was_changed(self):
318 319 result = utils.password_changed(self.auth_user, self.session)
319 320 assert result is True
320 321
321 322 def test_returns_true_if_auth_user_password_is_empty(self):
322 323 self.auth_user.password = None
323 324 result = utils.password_changed(self.auth_user, self.session)
324 325 assert result is True
325 326
326 327 def test_returns_true_if_session_password_is_empty(self):
327 328 self.session['rhodecode_user'].pop('password')
328 329 result = utils.password_changed(self.auth_user, self.session)
329 330 assert result is True
330 331
331 332
332 333 class TestReadOpensourceLicenses(object):
333 334 def test_success(self):
334 335 utils._license_cache = None
335 336 json_data = '''
336 337 {
337 338 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
338 339 "python2.7-Markdown-2.6.2": {
339 340 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
340 341 }
341 342 }
342 343 '''
343 344 resource_string_patch = mock.patch.object(
344 345 utils.pkg_resources, 'resource_string', return_value=json_data)
345 346 with resource_string_patch:
346 347 result = utils.read_opensource_licenses()
347 348 assert result == json.loads(json_data)
348 349
349 350 def test_caching(self):
350 351 utils._license_cache = {
351 352 "python2.7-pytest-2.7.1": {
352 353 "UNKNOWN": None
353 354 },
354 355 "python2.7-Markdown-2.6.2": {
355 356 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
356 357 }
357 358 }
358 359 resource_patch = mock.patch.object(
359 360 utils.pkg_resources, 'resource_string', side_effect=Exception)
360 361 json_patch = mock.patch.object(
361 362 utils.json, 'loads', side_effect=Exception)
362 363
363 364 with resource_patch as resource_mock, json_patch as json_mock:
364 365 result = utils.read_opensource_licenses()
365 366
366 367 assert resource_mock.call_count == 0
367 368 assert json_mock.call_count == 0
368 369 assert result == utils._license_cache
369 370
370 371 def test_licenses_file_contains_no_unknown_licenses(self):
371 372 utils._license_cache = None
372 373 result = utils.read_opensource_licenses()
373 374 license_names = []
374 375 for licenses in result.values():
375 376 license_names.extend(licenses.keys())
376 377 assert 'UNKNOWN' not in license_names
377 378
378 379
379 380 class TestMakeDbConfig(object):
380 381 def test_data_from_config_data_from_db_returned(self):
381 382 test_data = [
382 383 ('section1', 'option1', 'value1'),
383 384 ('section2', 'option2', 'value2'),
384 385 ('section3', 'option3', 'value3'),
385 386 ]
386 387 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
387 388 config_mock.return_value = test_data
388 389 kwargs = {'clear_session': False, 'repo': 'test_repo'}
389 390 result = utils.make_db_config(**kwargs)
390 391 config_mock.assert_called_once_with(**kwargs)
391 392 for section, option, expected_value in test_data:
392 393 value = result.get(section, option)
393 394 assert value == expected_value
394 395
395 396
396 397 class TestConfigDataFromDb(object):
397 398 def test_config_data_from_db_returns_active_settings(self):
398 399 test_data = [
399 400 UiSetting('section1', 'option1', 'value1', True),
400 401 UiSetting('section2', 'option2', 'value2', True),
401 402 UiSetting('section3', 'option3', 'value3', False),
402 403 ]
403 404 repo_name = 'test_repo'
404 405
405 model_patch = mock.patch.object(utils, 'VcsSettingsModel')
406 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
406 407 hooks_patch = mock.patch.object(
407 408 utils, 'get_enabled_hook_classes',
408 409 return_value=['pull', 'push', 'repo_size'])
409 410 with model_patch as model_mock, hooks_patch:
410 411 instance_mock = mock.Mock()
411 412 model_mock.return_value = instance_mock
412 413 instance_mock.get_ui_settings.return_value = test_data
413 414 result = utils.config_data_from_db(
414 415 clear_session=False, repo=repo_name)
415 416
416 417 self._assert_repo_name_passed(model_mock, repo_name)
417 418
418 419 expected_result = [
419 420 ('section1', 'option1', 'value1'),
420 421 ('section2', 'option2', 'value2'),
421 422 ]
422 423 assert result == expected_result
423 424
424 425 def _assert_repo_name_passed(self, model_mock, repo_name):
425 426 assert model_mock.call_count == 1
426 427 call_args, call_kwargs = model_mock.call_args
427 428 assert call_kwargs['repo'] == repo_name
428 429
429 430
430 431 class TestIsDirWritable(object):
431 432 def test_returns_false_when_not_writable(self):
432 433 with mock.patch('__builtin__.open', side_effect=OSError):
433 434 assert not utils._is_dir_writable('/stub-path')
434 435
435 436 def test_returns_true_when_writable(self, tmpdir):
436 437 assert utils._is_dir_writable(str(tmpdir))
437 438
438 439 def test_is_safe_against_race_conditions(self, tmpdir):
439 440 workers = multiprocessing.Pool()
440 441 directories = [str(tmpdir)] * 10
441 442 workers.map(utils._is_dir_writable, directories)
442 443
443 444
444 445 class TestGetEnabledHooks(object):
445 446 def test_only_active_hooks_are_enabled(self):
446 447 ui_settings = [
447 448 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
448 449 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
449 450 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
450 451 ]
451 452 result = utils.get_enabled_hook_classes(ui_settings)
452 453 assert result == ['push', 'repo_size']
453 454
454 455 def test_all_hooks_are_enabled(self):
455 456 ui_settings = [
456 457 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
457 458 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
458 459 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
459 460 ]
460 461 result = utils.get_enabled_hook_classes(ui_settings)
461 462 assert result == ['push', 'repo_size', 'pull']
462 463
463 464 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
464 465 ui_settings = []
465 466 result = utils.get_enabled_hook_classes(ui_settings)
466 467 assert result == []
@@ -1,510 +1,518 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Package for testing various lib/helper functions in rhodecode
24 24 """
25 25
26 26 import datetime
27 27 import string
28 28 import mock
29 29 import pytest
30 30 from rhodecode.tests.utils import run_test_concurrently
31 31 from rhodecode.lib.helpers import InitialsGravatar
32 32
33 33 from rhodecode.lib.utils2 import AttributeDict
34 34 from rhodecode.model.db import Repository
35 35
36 36
37 37 def _urls_for_proto(proto):
38 38 return [
39 39 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
40 40 '%s://127.0.0.1' % proto),
41 41 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
42 42 '%s://127.0.0.1' % proto),
43 43 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
44 44 '%s://127.0.0.1' % proto),
45 45 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
46 46 '%s://127.0.0.1:8080' % proto),
47 47 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
48 48 '%s://domain.org' % proto),
49 49 ('%s://user:pass@domain.org:8080' % proto,
50 50 ['%s://' % proto, 'domain.org', '8080'],
51 51 '%s://domain.org:8080' % proto),
52 52 ]
53 53
54 54 TEST_URLS = _urls_for_proto('http') + _urls_for_proto('https')
55 55
56 56
57 57 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
58 58 def test_uri_filter(test_url, expected, expected_creds):
59 59 from rhodecode.lib.utils2 import uri_filter
60 60 assert uri_filter(test_url) == expected
61 61
62 62
63 63 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
64 64 def test_credentials_filter(test_url, expected, expected_creds):
65 65 from rhodecode.lib.utils2 import credentials_filter
66 66 assert credentials_filter(test_url) == expected_creds
67 67
68 68
69 69 @pytest.mark.parametrize("str_bool, expected", [
70 70 ('t', True),
71 71 ('true', True),
72 72 ('y', True),
73 73 ('yes', True),
74 74 ('on', True),
75 75 ('1', True),
76 76 ('Y', True),
77 77 ('yeS', True),
78 78 ('Y', True),
79 79 ('TRUE', True),
80 80 ('T', True),
81 81 ('False', False),
82 82 ('F', False),
83 83 ('FALSE', False),
84 84 ('0', False),
85 85 ('-1', False),
86 86 ('', False)
87 87 ])
88 88 def test_str2bool(str_bool, expected):
89 89 from rhodecode.lib.utils2 import str2bool
90 90 assert str2bool(str_bool) == expected
91 91
92 92
93 93 @pytest.mark.parametrize("text, expected", reduce(lambda a1,a2:a1+a2, [
94 94 [
95 95 (pref+"", []),
96 96 (pref+"Hi there @marcink", ['marcink']),
97 97 (pref+"Hi there @marcink and @bob", ['bob', 'marcink']),
98 98 (pref+"Hi there @marcink\n", ['marcink']),
99 99 (pref+"Hi there @marcink and @bob\n", ['bob', 'marcink']),
100 100 (pref+"Hi there marcin@rhodecode.com", []),
101 101 (pref+"Hi there @john.malcovic and @bob\n", ['bob', 'john.malcovic']),
102 102 (pref+"This needs to be reviewed: (@marcink,@john)", ["john", "marcink"]),
103 103 (pref+"This needs to be reviewed: (@marcink, @john)", ["john", "marcink"]),
104 104 (pref+"This needs to be reviewed: [@marcink,@john]", ["john", "marcink"]),
105 105 (pref+"This needs to be reviewed: (@marcink @john)", ["john", "marcink"]),
106 106 (pref+"@john @mary, please review", ["john", "mary"]),
107 107 (pref+"@john,@mary, please review", ["john", "mary"]),
108 108 (pref+"Hej @123, @22john,@mary, please review", ['123', '22john', 'mary']),
109 109 (pref+"@first hi there @marcink here's my email marcin@email.com "
110 110 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three ", ['first', 'lukaszb', 'marcink', 'one', 'one_more22']),
111 111 (pref+"@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl", ['2one_more22', 'john', 'MARCIN', 'maRCiN']),
112 112 (pref+"@marian.user just do it @marco-polo and next extract @marco_polo", ['marco-polo', 'marco_polo', 'marian.user']),
113 113 (pref+"user.dot hej ! not-needed maril@domain.org", []),
114 114 (pref+"\n@marcin", ['marcin']),
115 115 ]
116 116 for pref in ['', '\n', 'hi !', '\t', '\n\n']]))
117 117 def test_mention_extractor(text, expected):
118 118 from rhodecode.lib.utils2 import extract_mentioned_users
119 119 got = extract_mentioned_users(text)
120 120 assert sorted(got, key=lambda x: x.lower()) == got
121 121 assert set(expected) == set(got)
122 122
123 123 @pytest.mark.parametrize("age_args, expected, kw", [
124 124 ({}, u'just now', {}),
125 125 ({'seconds': -1}, u'1 second ago', {}),
126 126 ({'seconds': -60 * 2}, u'2 minutes ago', {}),
127 127 ({'hours': -1}, u'1 hour ago', {}),
128 128 ({'hours': -24}, u'1 day ago', {}),
129 129 ({'hours': -24 * 5}, u'5 days ago', {}),
130 130 ({'months': -1}, u'1 month ago', {}),
131 131 ({'months': -1, 'days': -2}, u'1 month and 2 days ago', {}),
132 132 ({'years': -1, 'months': -1}, u'1 year and 1 month ago', {}),
133 133 ({}, u'just now', {'short_format': True}),
134 134 ({'seconds': -1}, u'1sec ago', {'short_format': True}),
135 135 ({'seconds': -60 * 2}, u'2min ago', {'short_format': True}),
136 136 ({'hours': -1}, u'1h ago', {'short_format': True}),
137 137 ({'hours': -24}, u'1d ago', {'short_format': True}),
138 138 ({'hours': -24 * 5}, u'5d ago', {'short_format': True}),
139 139 ({'months': -1}, u'1m ago', {'short_format': True}),
140 140 ({'months': -1, 'days': -2}, u'1m, 2d ago', {'short_format': True}),
141 141 ({'years': -1, 'months': -1}, u'1y, 1m ago', {'short_format': True}),
142 142 ])
143 143 def test_age(age_args, expected, kw, pylonsapp):
144 144 from rhodecode.lib.utils2 import age
145 145 from dateutil import relativedelta
146 146 n = datetime.datetime(year=2012, month=5, day=17)
147 147 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
148 148 assert age(n + delt(**age_args), now=n, **kw) == expected
149 149
150 150 @pytest.mark.parametrize("age_args, expected, kw", [
151 151 ({}, u'just now', {}),
152 152 ({'seconds': 1}, u'in 1 second', {}),
153 153 ({'seconds': 60 * 2}, u'in 2 minutes', {}),
154 154 ({'hours': 1}, u'in 1 hour', {}),
155 155 ({'hours': 24}, u'in 1 day', {}),
156 156 ({'hours': 24 * 5}, u'in 5 days', {}),
157 157 ({'months': 1}, u'in 1 month', {}),
158 158 ({'months': 1, 'days': 1}, u'in 1 month and 1 day', {}),
159 159 ({'years': 1, 'months': 1}, u'in 1 year and 1 month', {}),
160 160 ({}, u'just now', {'short_format': True}),
161 161 ({'seconds': 1}, u'in 1sec', {'short_format': True}),
162 162 ({'seconds': 60 * 2}, u'in 2min', {'short_format': True}),
163 163 ({'hours': 1}, u'in 1h', {'short_format': True}),
164 164 ({'hours': 24}, u'in 1d', {'short_format': True}),
165 165 ({'hours': 24 * 5}, u'in 5d', {'short_format': True}),
166 166 ({'months': 1}, u'in 1m', {'short_format': True}),
167 167 ({'months': 1, 'days': 1}, u'in 1m, 1d', {'short_format': True}),
168 168 ({'years': 1, 'months': 1}, u'in 1y, 1m', {'short_format': True}),
169 169 ])
170 170 def test_age_in_future(age_args, expected, kw, pylonsapp):
171 171 from rhodecode.lib.utils2 import age
172 172 from dateutil import relativedelta
173 173 n = datetime.datetime(year=2012, month=5, day=17)
174 174 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
175 175 assert age(n + delt(**age_args), now=n, **kw) == expected
176 176
177 177
178 178 def test_tag_exctrator():
179 179 sample = (
180 180 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
181 181 "[requires] [stale] [see<>=>] [see => http://url.com]"
182 182 "[requires => url] [lang => python] [just a tag] <html_tag first='abc' attr=\"my.url?attr=&another=\"></html_tag>"
183 183 "[,d] [ => ULR ] [obsolete] [desc]]"
184 184 )
185 185 from rhodecode.lib.helpers import desc_stylize, escaped_stylize
186 186 res = desc_stylize(sample)
187 187 assert '<div class="metatag" tag="tag">tag</div>' in res
188 188 assert '<div class="metatag" tag="obsolete">obsolete</div>' in res
189 189 assert '<div class="metatag" tag="stale">stale</div>' in res
190 190 assert '<div class="metatag" tag="lang">python</div>' in res
191 191 assert '<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res
192 192 assert '<div class="metatag" tag="tag">tag</div>' in res
193 193 assert '<html_tag first=\'abc\' attr=\"my.url?attr=&another=\"></html_tag>' in res
194 194
195 195 res_encoded = escaped_stylize(sample)
196 196 assert '<div class="metatag" tag="tag">tag</div>' in res_encoded
197 197 assert '<div class="metatag" tag="obsolete">obsolete</div>' in res_encoded
198 198 assert '<div class="metatag" tag="stale">stale</div>' in res_encoded
199 199 assert '<div class="metatag" tag="lang">python</div>' in res_encoded
200 200 assert '<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res_encoded
201 201 assert '<div class="metatag" tag="tag">tag</div>' in res_encoded
202 202 assert '&lt;html_tag first=&#39;abc&#39; attr=&#34;my.url?attr=&amp;another=&#34;&gt;&lt;/html_tag&gt;' in res_encoded
203 203
204 204
205 205 @pytest.mark.parametrize("tmpl_url, email, expected", [
206 206 ('http://test.com/{email}', 'test@foo.com', 'http://test.com/test@foo.com'),
207 207
208 208 ('http://test.com/{md5email}', 'test@foo.com', 'http://test.com/3cb7232fcc48743000cb86d0d5022bd9'),
209 209 ('http://test.com/{md5email}', 'testąć@foo.com', 'http://test.com/978debb907a3c55cd741872ab293ef30'),
210 210
211 211 ('http://testX.com/{md5email}?s={size}', 'test@foo.com', 'http://testX.com/3cb7232fcc48743000cb86d0d5022bd9?s=24'),
212 212 ('http://testX.com/{md5email}?s={size}', 'testąć@foo.com', 'http://testX.com/978debb907a3c55cd741872ab293ef30?s=24'),
213 213
214 214 ('{scheme}://{netloc}/{md5email}/{size}', 'test@foo.com', 'https://server.com/3cb7232fcc48743000cb86d0d5022bd9/24'),
215 215 ('{scheme}://{netloc}/{md5email}/{size}', 'testąć@foo.com', 'https://server.com/978debb907a3c55cd741872ab293ef30/24'),
216 216
217 217 ('http://test.com/{email}', 'testąć@foo.com', 'http://test.com/testąć@foo.com'),
218 218 ('http://test.com/{email}?size={size}', 'test@foo.com', 'http://test.com/test@foo.com?size=24'),
219 219 ('http://test.com/{email}?size={size}', 'testąć@foo.com', 'http://test.com/testąć@foo.com?size=24'),
220 220 ])
221 221 def test_gravatar_url_builder(tmpl_url, email, expected, request_stub):
222 222 from rhodecode.lib.helpers import gravatar_url
223 223
224 224 # mock pyramid.threadlocals
225 225 def fake_get_current_request():
226 226 request_stub.scheme = 'https'
227 227 request_stub.host = 'server.com'
228 228 return request_stub
229 229
230 230 # mock pylons.tmpl_context
231 231 def fake_tmpl_context(_url):
232 232 _c = AttributeDict()
233 233 _c.visual = AttributeDict()
234 234 _c.visual.use_gravatar = True
235 235 _c.visual.gravatar_url = _url
236 236
237 237 return _c
238 238
239 239 with mock.patch('rhodecode.lib.helpers.get_current_request',
240 240 fake_get_current_request):
241 241 fake = fake_tmpl_context(_url=tmpl_url)
242 242 with mock.patch('pylons.tmpl_context', fake):
243 243 grav = gravatar_url(email_address=email, size=24)
244 244 assert grav == expected
245 245
246 246
247 247 @pytest.mark.parametrize(
248 248 "email, first_name, last_name, expected_initials, expected_color", [
249 249
250 250 ('test@rhodecode.com', '', '', 'TR', '#8a994d'),
251 251 ('marcin.kuzminski@rhodecode.com', '', '', 'MK', '#6559b3'),
252 252 # special cases of email
253 253 ('john.van.dam@rhodecode.com', '', '', 'JD', '#526600'),
254 254 ('Guido.van.Rossum@rhodecode.com', '', '', 'GR', '#990052'),
255 255 ('Guido.van.Rossum@rhodecode.com', 'Guido', 'Van Rossum', 'GR', '#990052'),
256 256
257 257 ('rhodecode+Guido.van.Rossum@rhodecode.com', '', '', 'RR', '#46598c'),
258 258 ('pclouds@rhodecode.com', 'Nguyễn Thái', 'Tgọc Duy', 'ND', '#665200'),
259 259
260 260 ('john-brown@foo.com', '', '', 'JF', '#73006b'),
261 261 ('admin@rhodecode.com', 'Marcin', 'Kuzminski', 'MK', '#104036'),
262 262 # partials
263 263 ('admin@rhodecode.com', 'Marcin', '', 'MR', '#104036'), # fn+email
264 264 ('admin@rhodecode.com', '', 'Kuzminski', 'AK', '#104036'), # em+ln
265 265 # non-ascii
266 266 ('admin@rhodecode.com', 'Marcin', 'Śuzminski', 'MS', '#104036'),
267 267 ('marcin.śuzminski@rhodecode.com', '', '', 'MS', '#73000f'),
268 268
269 269 # special cases, LDAP can provide those...
270 270 ('admin@', 'Marcin', 'Śuzminski', 'MS', '#aa00ff'),
271 271 ('marcin.śuzminski', '', '', 'MS', '#402020'),
272 272 ('null', '', '', 'NL', '#8c4646'),
273 273 ])
274 274 def test_initials_gravatar_pick_of_initials_and_color_algo(
275 275 email, first_name, last_name, expected_initials, expected_color):
276 276 instance = InitialsGravatar(email, first_name, last_name)
277 277 assert instance.get_initials() == expected_initials
278 278 assert instance.str2color(email) == expected_color
279 279
280 280
281 281 def test_initials_gravatar_mapping_algo():
282 282 pos = set()
283 283 instance = InitialsGravatar('', '', '')
284 284 iterations = 0
285 285
286 286 variations = []
287 287 for letter1 in string.ascii_letters:
288 288 for letter2 in string.ascii_letters[::-1][:10]:
289 289 for letter3 in string.ascii_letters[:10]:
290 290 variations.append(
291 291 '%s@rhodecode.com' % (letter1+letter2+letter3))
292 292
293 293 max_variations = 4096
294 294 for email in variations[:max_variations]:
295 295 iterations += 1
296 296 pos.add(
297 297 instance.pick_color_bank_index(email,
298 298 instance.get_color_bank()))
299 299
300 300 # we assume that we have match all 256 possible positions,
301 301 # in reasonable amount of different email addresses
302 302 assert len(pos) == 256
303 303 assert iterations == max_variations
304 304
305 305
306 306 @pytest.mark.parametrize("tmpl, repo_name, overrides, prefix, expected", [
307 307 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
308 308 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
309 309 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
310 310 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
311 311 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
312 312 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
313 313 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
314 314 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
315 315 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
316 316 ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
317 317 ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
318 318 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
319 319 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
320 320 ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
321 321 ])
322 322 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
323 323 from rhodecode.lib.utils2 import get_clone_url
324 324 clone_url = get_clone_url(uri_tmpl=tmpl, qualifed_home_url='http://vps1:8000'+prefix,
325 325 repo_name=repo_name, repo_id=23, **overrides)
326 326 assert clone_url == expected
327 327
328 328
329 329 def _quick_url(text, tmpl="""<a class="revision-link" href="%s">%s</a>""", url_=None):
330 330 """
331 331 Changes `some text url[foo]` => `some text <a href="/">foo</a>
332 332
333 333 :param text:
334 334 """
335 335 import re
336 336 # quickly change expected url[] into a link
337 337 URL_PAT = re.compile(r'(?:url\[)(.+?)(?:\])')
338 338
339 339 def url_func(match_obj):
340 340 _url = match_obj.groups()[0]
341 341 return tmpl % (url_ or '/some-url', _url)
342 342 return URL_PAT.sub(url_func, text)
343 343
344 344
345 345 @pytest.mark.parametrize("sample, expected", [
346 346 ("",
347 347 ""),
348 348 ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
349 349 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68"),
350 350 ("from rev 000000000000",
351 351 "from rev url[000000000000]"),
352 352 ("from rev 000000000000123123 also rev 000000000000",
353 353 "from rev url[000000000000123123] also rev url[000000000000]"),
354 354 ("this should-000 00",
355 355 "this should-000 00"),
356 356 ("longtextffffffffff rev 123123123123",
357 357 "longtextffffffffff rev url[123123123123]"),
358 358 ("rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
359 359 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff"),
360 360 ("ffffffffffff some text traalaa",
361 361 "url[ffffffffffff] some text traalaa"),
362 362 ("""Multi line
363 363 123123123123
364 364 some text 123123123123
365 365 sometimes !
366 366 """,
367 367 """Multi line
368 368 url[123123123123]
369 369 some text url[123123123123]
370 370 sometimes !
371 371 """)
372 372 ])
373 373 def test_urlify_commits(sample, expected):
374 374 def fake_url(self, *args, **kwargs):
375 375 return '/some-url'
376 376
377 377 expected = _quick_url(expected)
378 378
379 379 with mock.patch('pylons.url', fake_url):
380 380 from rhodecode.lib.helpers import urlify_commits
381 381 assert urlify_commits(sample, 'repo_name') == expected
382 382
383 383
384 384 @pytest.mark.parametrize("sample, expected, url_", [
385 385 ("",
386 386 "",
387 387 ""),
388 388 ("https://svn.apache.org/repos",
389 389 "url[https://svn.apache.org/repos]",
390 390 "https://svn.apache.org/repos"),
391 391 ("http://svn.apache.org/repos",
392 392 "url[http://svn.apache.org/repos]",
393 393 "http://svn.apache.org/repos"),
394 394 ("from rev a also rev http://google.com",
395 395 "from rev a also rev url[http://google.com]",
396 396 "http://google.com"),
397 397 ("""Multi line
398 398 https://foo.bar.com
399 399 some text lalala""",
400 400 """Multi line
401 401 url[https://foo.bar.com]
402 402 some text lalala""",
403 403 "https://foo.bar.com")
404 404 ])
405 405 def test_urlify_test(sample, expected, url_):
406 406 from rhodecode.lib.helpers import urlify_text
407 407 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
408 408 assert urlify_text(sample) == expected
409 409
410 410
411 411 @pytest.mark.parametrize("test, expected", [
412 412 ("", None),
413 413 ("/_2", '2'),
414 414 ("_2", '2'),
415 415 ("/_2/", '2'),
416 416 ("_2/", '2'),
417 417
418 418 ("/_21", '21'),
419 419 ("_21", '21'),
420 420 ("/_21/", '21'),
421 421 ("_21/", '21'),
422 422
423 423 ("/_21/foobar", '21'),
424 424 ("_21/121", '21'),
425 425 ("/_21/_12", '21'),
426 426 ("_21/rc/foo", '21'),
427 427
428 428 ])
429 429 def test_get_repo_by_id(test, expected):
430 430 from rhodecode.model.repo import RepoModel
431 431 _test = RepoModel()._extract_id_from_repo_name(test)
432 432 assert _test == expected
433 433
434 434
435 def test_invalidation_context(pylonsapp):
435 @pytest.mark.parametrize("test_repo_name, repo_type", [
436 ("test_repo_1", None),
437 ("repo_group/foobar", None),
438 ("test_non_asci_ąćę", None),
439 (u"test_non_asci_unicode_ąćę", None),
440 ])
441 def test_invalidation_context(pylonsapp, test_repo_name, repo_type):
436 442 from beaker.cache import cache_region
437 443 from rhodecode.lib import caches
438 444 from rhodecode.model.db import CacheKey
439 445
440 446 @cache_region('long_term')
441 447 def _dummy_func(cache_key):
442 448 return 'result'
443 449
444 450 invalidator_context = CacheKey.repo_context_cache(
445 _dummy_func, 'test_repo_1', 'repo')
451 _dummy_func, test_repo_name, 'repo')
446 452
447 453 with invalidator_context as context:
448 454 invalidated = context.invalidate()
449 455 result = context.compute()
450 456
451 457 assert invalidated == True
452 458 assert 'result' == result
453 459 assert isinstance(context, caches.FreshRegionCache)
454 460
461 assert 'InvalidationContext' in repr(invalidator_context)
462
455 463 with invalidator_context as context:
456 464 context.invalidate()
457 465 result = context.compute()
458 466
459 467 assert 'result' == result
460 468 assert isinstance(context, caches.ActiveRegionCache)
461 469
462 470
463 471 def test_invalidation_context_exception_in_compute(pylonsapp):
464 472 from rhodecode.model.db import CacheKey
465 473 from beaker.cache import cache_region
466 474
467 475 @cache_region('long_term')
468 476 def _dummy_func(cache_key):
469 477 # this causes error since it doesn't get any params
470 478 raise Exception('ups')
471 479
472 480 invalidator_context = CacheKey.repo_context_cache(
473 481 _dummy_func, 'test_repo_2', 'repo')
474 482
475 483 with pytest.raises(Exception):
476 484 with invalidator_context as context:
477 485 context.invalidate()
478 486 context.compute()
479 487
480 488
481 489 @pytest.mark.parametrize('execution_number', range(5))
482 490 def test_cache_invalidation_race_condition(execution_number, pylonsapp):
483 491 import time
484 492 from beaker.cache import cache_region
485 493 from rhodecode.model.db import CacheKey
486 494
487 495 if CacheKey.metadata.bind.url.get_backend_name() == "mysql":
488 496 reason = (
489 497 'Fails on MariaDB due to some locking issues. Investigation'
490 498 ' needed')
491 499 pytest.xfail(reason=reason)
492 500
493 501 @run_test_concurrently(25)
494 502 def test_create_and_delete_cache_keys():
495 503 time.sleep(0.2)
496 504
497 505 @cache_region('long_term')
498 506 def _dummy_func(cache_key):
499 507 return 'result'
500 508
501 509 invalidator_context = CacheKey.repo_context_cache(
502 510 _dummy_func, 'test_repo_1', 'repo')
503 511
504 512 with invalidator_context as context:
505 513 context.invalidate()
506 514 context.compute()
507 515
508 516 CacheKey.set_invalidate('test_repo_1', delete=True)
509 517
510 518 test_create_and_delete_cache_keys()
@@ -1,87 +1,100 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import mock
24 24 import msgpack
25 25 import pytest
26 26
27 27 from rhodecode.lib import vcs
28 28 from rhodecode.lib.vcs import client_http
29 29
30 30
31 31 def test_uses_persistent_http_connections(caplog, vcsbackend_hg):
32 32 repo = vcsbackend_hg.repo
33 33 remote_call = repo._remote.branches
34 34
35 35 with caplog.at_level(logging.INFO):
36 36 for x in range(5):
37 37 remote_call(normal=True, closed=False)
38 38
39 39 new_connections = [
40 40 r for r in caplog.record_tuples() if is_new_connection(*r)]
41 41 assert len(new_connections) <= 1
42 42
43 43
44 44 def is_new_connection(logger, level, message):
45 45 return (
46 46 logger == 'requests.packages.urllib3.connectionpool' and
47 47 message.startswith('Starting new HTTP'))
48 48
49 49
50 50 @pytest.fixture
51 51 def stub_session():
52 52 """
53 53 Stub of `requests.Session()`.
54 54 """
55 55 session = mock.Mock()
56 56 session.post().content = msgpack.packb({})
57 57 session.reset_mock()
58 58 return session
59 59
60 60
61 def test_repo_maker_uses_session_for_classmethods(stub_session):
61 @pytest.fixture
62 def stub_session_factory(stub_session):
63 """
64 Stub of `rhodecode.lib.vcs.client_http.ThreadlocalSessionFactory`.
65 """
66 session_factory = mock.Mock()
67 session_factory.return_value = stub_session
68 return session_factory
69
70
71 def test_repo_maker_uses_session_for_classmethods(stub_session_factory):
62 72 repo_maker = client_http.RepoMaker(
63 'server_and_port', 'endpoint', stub_session)
73 'server_and_port', 'endpoint', stub_session_factory)
64 74 repo_maker.example_call()
65 stub_session.post.assert_called_with(
75 stub_session_factory().post.assert_called_with(
66 76 'http://server_and_port/endpoint', data=mock.ANY)
67 77
68 78
69 79 def test_repo_maker_uses_session_for_instance_methods(
70 stub_session, config):
80 stub_session_factory, config):
71 81 repo_maker = client_http.RepoMaker(
72 'server_and_port', 'endpoint', stub_session)
82 'server_and_port', 'endpoint', stub_session_factory)
73 83 repo = repo_maker('stub_path', config)
74 84 repo.example_call()
75 stub_session.post.assert_called_with(
85 stub_session_factory().post.assert_called_with(
76 86 'http://server_and_port/endpoint', data=mock.ANY)
77 87
78 88
89 @mock.patch('rhodecode.lib.vcs.client_http.ThreadlocalSessionFactory')
79 90 @mock.patch('rhodecode.lib.vcs.connection')
80 def test_connect_passes_in_the_same_session(connection, stub_session):
81 session_factory_patcher = mock.patch.object(
82 vcs, '_create_http_rpc_session', return_value=stub_session)
83 with session_factory_patcher:
84 vcs.connect_http('server_and_port')
85 assert connection.Hg._session == stub_session
86 assert connection.Svn._session == stub_session
87 assert connection.Git._session == stub_session
91 def test_connect_passes_in_the_same_session(
92 connection, session_factory_class, stub_session):
93 session_factory = session_factory_class.return_value
94 session_factory.return_value = stub_session
95
96 vcs.connect_http('server_and_port')
97
98 assert connection.Hg._session_factory() == stub_session
99 assert connection.Svn._session_factory() == stub_session
100 assert connection.Git._session_factory() == stub_session
@@ -1,87 +1,86 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import pylons
24 24 import rhodecode
25 25
26 26 from pylons.i18n.translation import _get_translator
27 27 from pylons.util import ContextObj
28 28 from routes.util import URLGenerator
29 29
30 30 from rhodecode.lib.base import attach_context_attributes, get_auth_user
31 31 from rhodecode.model import meta
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 def pylons_compatibility_tween_factory(handler, registry):
37 37 def pylons_compatibility_tween(request):
38 38 """
39 39 While migrating from pylons to pyramid we need to call some pylons code
40 40 from pyramid. For example while rendering an old template that uses the
41 41 'c' or 'h' objects. This tween sets up the needed pylons globals.
42 42 """
43 43 try:
44 44 config = rhodecode.CONFIG
45 45 environ = request.environ
46 46 session = request.session
47 47 session_key = (config['pylons.environ_config']
48 48 .get('session', 'beaker.session'))
49 49
50 50 # Setup pylons globals.
51 51 pylons.config._push_object(config)
52 52 pylons.request._push_object(request)
53 53 pylons.session._push_object(session)
54 54 environ[session_key] = session
55 55 pylons.url._push_object(URLGenerator(config['routes.map'],
56 56 environ))
57 57
58 58 # TODO: Maybe we should use the language from pyramid.
59 59 translator = _get_translator(config.get('lang'))
60 60 pylons.translator._push_object(translator)
61 61
62 62 # Get the rhodecode auth user object and make it available.
63 63 auth_user = get_auth_user(environ)
64 64 request.user = auth_user
65 65 environ['rc_auth_user'] = auth_user
66 66
67 67 # Setup the pylons context object ('c')
68 68 context = ContextObj()
69 69 context.rhodecode_user = auth_user
70 70 attach_context_attributes(context)
71 71 pylons.tmpl_context._push_object(context)
72
73 72 return handler(request)
74 73 finally:
75 74 # Dispose current database session and rollback uncommitted
76 75 # transactions.
77 76 meta.Session.remove()
78 77
79 78 return pylons_compatibility_tween
80 79
81 80
82 81 def includeme(config):
83 82 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
84 83 'pyramid.events.BeforeRender')
85 84 config.add_subscriber('rhodecode.subscribers.add_localizer',
86 85 'pyramid.events.NewRequest')
87 86 config.add_tween('rhodecode.tweens.pylons_compatibility_tween_factory')
@@ -1,612 +1,606 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode Enterprise - configuration file #
4 4 # Built-in functions and variables #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 # #
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10 debug = true
11 11 pdebug = false
12 12 ################################################################################
13 13 ## Uncomment and replace with the email address which should receive ##
14 14 ## any error reports after an application crash ##
15 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 16 ################################################################################
17 17 #email_to = admin@localhost
18 18 #error_email_from = paste_error@localhost
19 19 #app_email_from = rhodecode-noreply@localhost
20 20 #error_message =
21 21 #email_prefix = [RhodeCode]
22 22
23 23 #smtp_server = mail.server.com
24 24 #smtp_username =
25 25 #smtp_password =
26 26 #smtp_port =
27 27 #smtp_use_tls = false
28 28 #smtp_use_ssl = true
29 29 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
30 30 #smtp_auth =
31 31
32 32 [server:main]
33 33 ## COMMON ##
34 34 host = 0.0.0.0
35 35 port = 5000
36 36
37 37 ##########################
38 38 ## WAITRESS WSGI SERVER ##
39 39 ##########################
40 40 use = egg:waitress#main
41 41 ## number of worker threads
42 42 threads = 5
43 43 ## MAX BODY SIZE 100GB
44 44 max_request_body_size = 107374182400
45 45 ## Use poll instead of select, fixes file descriptors limits problems.
46 46 ## May not work on old windows systems.
47 47 asyncore_use_poll = true
48 48
49 49 ## PASTE HTTP ##
50 50 #use = egg:Paste#http
51 51 ## nr of worker threads to spawn
52 52 #threadpool_workers = 5
53 53 ## max request before thread respawn
54 54 #threadpool_max_requests = 10
55 55 ## option to use threads of process
56 56 #use_threadpool = true
57 57
58 58 ##########################
59 59 ## GUNICORN WSGI SERVER ##
60 60 ##########################
61 61 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
62 62 #use = egg:gunicorn#main
63 63 ## Sets the number of process workers. You must set `instance_id = *`
64 64 ## when this option is set to more than one worker, recommended
65 65 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
66 66 ## The `instance_id = *` must be set in the [app:main] section below
67 67 #workers = 1
68 68 ## number of threads for each of the worker, must be set to 1 for gevent
69 69 ## generally recommened to be at 1
70 70 #threads = 1
71 71 ## process name
72 72 #proc_name = rhodecode
73 73 ## type of worker class, one of sync, gevent
74 74 ## recommended for bigger setup is using of of other than sync one
75 75 #worker_class = sync
76 76 ## max number of requests that worker will handle before being gracefully
77 77 ## restarted, could prevent memory leaks
78 78 #max_requests = 1000
79 79 #max_requests_jitter = 30
80 80 ## ammount of time a worker can spend with handling a request before it
81 81 ## gets killed and restarted. Set to 6hrs
82 82 #timeout = 21600
83 83
84 84 ## UWSGI ##
85 85 ## run with uwsgi --ini-paste-logged <inifile.ini>
86 86 #[uwsgi]
87 87 #socket = /tmp/uwsgi.sock
88 88 #master = true
89 89 #http = 127.0.0.1:5000
90 90
91 91 ## set as deamon and redirect all output to file
92 92 #daemonize = ./uwsgi_rhodecode.log
93 93
94 94 ## master process PID
95 95 #pidfile = ./uwsgi_rhodecode.pid
96 96
97 97 ## stats server with workers statistics, use uwsgitop
98 98 ## for monitoring, `uwsgitop 127.0.0.1:1717`
99 99 #stats = 127.0.0.1:1717
100 100 #memory-report = true
101 101
102 102 ## log 5XX errors
103 103 #log-5xx = true
104 104
105 105 ## Set the socket listen queue size.
106 106 #listen = 256
107 107
108 108 ## Gracefully Reload workers after the specified amount of managed requests
109 109 ## (avoid memory leaks).
110 110 #max-requests = 1000
111 111
112 112 ## enable large buffers
113 113 #buffer-size=65535
114 114
115 115 ## socket and http timeouts ##
116 116 #http-timeout=3600
117 117 #socket-timeout=3600
118 118
119 119 ## Log requests slower than the specified number of milliseconds.
120 120 #log-slow = 10
121 121
122 122 ## Exit if no app can be loaded.
123 123 #need-app = true
124 124
125 125 ## Set lazy mode (load apps in workers instead of master).
126 126 #lazy = true
127 127
128 128 ## scaling ##
129 129 ## set cheaper algorithm to use, if not set default will be used
130 130 #cheaper-algo = spare
131 131
132 132 ## minimum number of workers to keep at all times
133 133 #cheaper = 1
134 134
135 135 ## number of workers to spawn at startup
136 136 #cheaper-initial = 1
137 137
138 138 ## maximum number of workers that can be spawned
139 139 #workers = 4
140 140
141 141 ## how many workers should be spawned at a time
142 142 #cheaper-step = 1
143 143
144 144 ## prefix middleware for RhodeCode, disables force_https flag.
145 145 ## allows to set RhodeCode under a prefix in server.
146 146 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
147 147 #[filter:proxy-prefix]
148 148 #use = egg:PasteDeploy#prefix
149 149 #prefix = /<your-prefix>
150 150
151 151 [app:main]
152 152 is_test = True
153 153 use = egg:rhodecode-enterprise-ce
154 154 ## enable proxy prefix middleware, defined below
155 155 #filter-with = proxy-prefix
156 156
157 157 rhodecode.includes = rhodecode.api
158 158
159 159 # api prefix url
160 160 rhodecode.api.url = /_admin/api
161 161
162 162
163 163 full_stack = true
164 164
165 165 ## Serve static files via RhodeCode, disable to serve them via HTTP server
166 166 static_files = true
167 167
168 168 ## Optional Languages
169 169 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
170 170 lang = en
171 171
172 172 ## perform a full repository scan on each server start, this should be
173 173 ## set to false after first startup, to allow faster server restarts.
174 174 startup.import_repos = true
175 175
176 176 ## Uncomment and set this path to use archive download cache.
177 177 ## Once enabled, generated archives will be cached at this location
178 178 ## and served from the cache during subsequent requests for the same archive of
179 179 ## the repository.
180 180 #archive_cache_dir = /tmp/tarballcache
181 181
182 182 ## change this to unique ID for security
183 183 app_instance_uuid = rc-production
184 184
185 185 ## cut off limit for large diffs (size in bytes)
186 186 cut_off_limit_diff = 1024000
187 187 cut_off_limit_file = 256000
188 188
189 189 ## use cache version of scm repo everywhere
190 190 vcs_full_cache = false
191 191
192 192 ## force https in RhodeCode, fixes https redirects, assumes it's always https
193 193 ## Normally this is controlled by proper http flags sent from http server
194 194 force_https = false
195 195
196 196 ## use Strict-Transport-Security headers
197 197 use_htsts = false
198 198
199 199 ## number of commits stats will parse on each iteration
200 200 commit_parse_limit = 25
201 201
202 202 ## git rev filter option, --all is the default filter, if you need to
203 203 ## hide all refs in changelog switch this to --branches --tags
204 204 git_rev_filter = --all
205 205
206 206 # Set to true if your repos are exposed using the dumb protocol
207 207 git_update_server_info = false
208 208
209 209 ## RSS/ATOM feed options
210 210 rss_cut_off_limit = 256000
211 211 rss_items_per_page = 10
212 212 rss_include_diff = false
213 213
214 214 ## gist URL alias, used to create nicer urls for gist. This should be an
215 215 ## url that does rewrites to _admin/gists/<gistid>.
216 216 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
217 217 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
218 218 gist_alias_url =
219 219
220 220 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
221 221 ## used for access.
222 222 ## Adding ?auth_token = <token> to the url authenticates this request as if it
223 223 ## came from the the logged in user who own this authentication token.
224 224 ##
225 225 ## Syntax is <ControllerClass>:<function_pattern>.
226 226 ## To enable access to raw_files put `FilesController:raw`.
227 227 ## To enable access to patches add `ChangesetController:changeset_patch`.
228 228 ## The list should be "," separated and on a single line.
229 229 ##
230 230 ## Recommended controllers to enable:
231 231 # ChangesetController:changeset_patch,
232 232 # ChangesetController:changeset_raw,
233 233 # FilesController:raw,
234 234 # FilesController:archivefile,
235 235 # GistsController:*,
236 236 api_access_controllers_whitelist =
237 237
238 238 ## default encoding used to convert from and to unicode
239 239 ## can be also a comma separated list of encoding in case of mixed encodings
240 240 default_encoding = UTF-8
241 241
242 242 ## instance-id prefix
243 243 ## a prefix key for this instance used for cache invalidation when running
244 244 ## multiple instances of rhodecode, make sure it's globally unique for
245 245 ## all running rhodecode instances. Leave empty if you don't use it
246 246 instance_id =
247 247
248 248 ## alternative return HTTP header for failed authentication. Default HTTP
249 249 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
250 250 ## handling that causing a series of failed authentication calls.
251 251 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
252 252 ## This will be served instead of default 401 on bad authnetication
253 253 auth_ret_code =
254 254
255 255 ## use special detection method when serving auth_ret_code, instead of serving
256 256 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
257 257 ## and then serve auth_ret_code to clients
258 258 auth_ret_code_detection = false
259 259
260 260 ## locking return code. When repository is locked return this HTTP code. 2XX
261 261 ## codes don't break the transactions while 4XX codes do
262 262 lock_ret_code = 423
263 263
264 264 ## allows to change the repository location in settings page
265 265 allow_repo_location_change = true
266 266
267 267 ## allows to setup custom hooks in settings page
268 268 allow_custom_hooks_settings = true
269 269
270 270 ## generated license token, goto license page in RhodeCode settings to obtain
271 271 ## new token
272 272 license_token = abra-cada-bra1-rce3
273 273
274 274 ## supervisor connection uri, for managing supervisor and logs.
275 275 supervisor.uri =
276 276 ## supervisord group name/id we only want this RC instance to handle
277 277 supervisor.group_id = dev
278 278
279 279 ## Display extended labs settings
280 280 labs_settings_active = true
281 281
282 282 ####################################
283 283 ### CELERY CONFIG ####
284 284 ####################################
285 285 use_celery = false
286 286 broker.host = localhost
287 287 broker.vhost = rabbitmqhost
288 288 broker.port = 5672
289 289 broker.user = rabbitmq
290 290 broker.password = qweqwe
291 291
292 292 celery.imports = rhodecode.lib.celerylib.tasks
293 293
294 294 celery.result.backend = amqp
295 295 celery.result.dburi = amqp://
296 296 celery.result.serialier = json
297 297
298 298 #celery.send.task.error.emails = true
299 299 #celery.amqp.task.result.expires = 18000
300 300
301 301 celeryd.concurrency = 2
302 302 #celeryd.log.file = celeryd.log
303 303 celeryd.log.level = debug
304 304 celeryd.max.tasks.per.child = 1
305 305
306 306 ## tasks will never be sent to the queue, but executed locally instead.
307 307 celery.always.eager = false
308 308
309 309 ####################################
310 310 ### BEAKER CACHE ####
311 311 ####################################
312 312 # default cache dir for templates. Putting this into a ramdisk
313 313 ## can boost performance, eg. %(here)s/data_ramdisk
314 314 cache_dir = %(here)s/data
315 315
316 316 ## locking and default file storage for Beaker. Putting this into a ramdisk
317 317 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
318 318 beaker.cache.data_dir = %(here)s/rc/data/cache/beaker_data
319 319 beaker.cache.lock_dir = %(here)s/rc/data/cache/beaker_lock
320 320
321 321 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
322 322
323 323 beaker.cache.super_short_term.type = memory
324 324 beaker.cache.super_short_term.expire = 1
325 325 beaker.cache.super_short_term.key_length = 256
326 326
327 327 beaker.cache.short_term.type = memory
328 328 beaker.cache.short_term.expire = 60
329 329 beaker.cache.short_term.key_length = 256
330 330
331 331 beaker.cache.long_term.type = memory
332 332 beaker.cache.long_term.expire = 36000
333 333 beaker.cache.long_term.key_length = 256
334 334
335 335 beaker.cache.sql_cache_short.type = memory
336 336 beaker.cache.sql_cache_short.expire = 1
337 337 beaker.cache.sql_cache_short.key_length = 256
338 338
339 339 # default is memory cache, configure only if required
340 340 # using multi-node or multi-worker setup
341 341 #beaker.cache.auth_plugins.type = ext:database
342 342 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
343 343 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
344 344 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
345 345 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
346 346 #beaker.cache.auth_plugins.sa.pool_size = 10
347 347 #beaker.cache.auth_plugins.sa.max_overflow = 0
348 348
349 349 beaker.cache.repo_cache_long.type = memorylru_base
350 350 beaker.cache.repo_cache_long.max_items = 4096
351 351 beaker.cache.repo_cache_long.expire = 2592000
352 352
353 353 # default is memorylru_base cache, configure only if required
354 354 # using multi-node or multi-worker setup
355 355 #beaker.cache.repo_cache_long.type = ext:memcached
356 356 #beaker.cache.repo_cache_long.url = localhost:11211
357 357 #beaker.cache.repo_cache_long.expire = 1209600
358 358 #beaker.cache.repo_cache_long.key_length = 256
359 359
360 360 ####################################
361 361 ### BEAKER SESSION ####
362 362 ####################################
363 363
364 364 ## .session.type is type of storage options for the session, current allowed
365 365 ## types are file(default), ext:memcached, ext:database, and memory.
366 366 #beaker.session.type = file
367 367
368 368 ## db based session, fast, and allows easy management over logged in users ##
369 369 #beaker.session.type = ext:database
370 370 #beaker.session.lock_dir = %(here)s/data/cache/session_db_lock
371 371 #beaker.session.table_name = db_session
372 372 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
373 373 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
374 374 #beaker.session.sa.pool_recycle = 3600
375 375 #beaker.session.sa.echo = false
376 376
377 377 beaker.session.key = rhodecode
378 378 beaker.session.secret = test-rc-uytcxaz
379 379
380 380 ## Secure encrypted cookie. Requires AES and AES python libraries
381 381 ## you must disable beaker.session.secret to use this
382 382 #beaker.session.encrypt_key = <key_for_encryption>
383 383 #beaker.session.validate_key = <validation_key>
384 384
385 385 ## sets session as invalid(also logging out user) if it haven not been
386 386 ## accessed for given amount of time in seconds
387 387 beaker.session.timeout = 2592000
388 388 beaker.session.httponly = true
389 389 #beaker.session.cookie_path = /<your-prefix>
390 390
391 391 ## uncomment for https secure cookie
392 392 beaker.session.secure = false
393 393
394 394 ## auto save the session to not to use .save()
395 395 beaker.session.auto = false
396 396
397 397 ## default cookie expiration time in seconds, set to `true` to set expire
398 398 ## at browser close
399 399 #beaker.session.cookie_expires = 3600
400 400
401 401 ###################################
402 402 ## SEARCH INDEXING CONFIGURATION ##
403 403 ###################################
404 404
405 405 search.module = rhodecode.lib.index.whoosh
406 406 search.location = %(here)s/data/index
407 407
408 408 ###################################
409 409 ## ERROR AND LOG HANDLING SYSTEM ##
410 410 ###################################
411 411
412 412 ## Appenlight is tailored to work with RhodeCode, see
413 413 ## http://appenlight.com for details how to obtain an account
414 414
415 415 ## appenlight integration enabled
416 416 appenlight = false
417 417
418 418 appenlight.server_url = https://api.appenlight.com
419 419 appenlight.api_key = YOUR_API_KEY
420 420 ;appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
421 421
422 422 # used for JS client
423 423 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
424 424
425 425 ## TWEAK AMOUNT OF INFO SENT HERE
426 426
427 427 ## enables 404 error logging (default False)
428 428 appenlight.report_404 = false
429 429
430 430 ## time in seconds after request is considered being slow (default 1)
431 431 appenlight.slow_request_time = 1
432 432
433 433 ## record slow requests in application
434 434 ## (needs to be enabled for slow datastore recording and time tracking)
435 435 appenlight.slow_requests = true
436 436
437 437 ## enable hooking to application loggers
438 438 appenlight.logging = true
439 439
440 440 ## minimum log level for log capture
441 441 appenlight.logging.level = WARNING
442 442
443 443 ## send logs only from erroneous/slow requests
444 444 ## (saves API quota for intensive logging)
445 445 appenlight.logging_on_error = false
446 446
447 447 ## list of additonal keywords that should be grabbed from environ object
448 448 ## can be string with comma separated list of words in lowercase
449 449 ## (by default client will always send following info:
450 450 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
451 451 ## start with HTTP* this list be extended with additional keywords here
452 452 appenlight.environ_keys_whitelist =
453 453
454 454 ## list of keywords that should be blanked from request object
455 455 ## can be string with comma separated list of words in lowercase
456 456 ## (by default client will always blank keys that contain following words
457 457 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
458 458 ## this list be extended with additional keywords set here
459 459 appenlight.request_keys_blacklist =
460 460
461 461 ## list of namespaces that should be ignores when gathering log entries
462 462 ## can be string with comma separated list of namespaces
463 463 ## (by default the client ignores own entries: appenlight_client.client)
464 464 appenlight.log_namespace_blacklist =
465 465
466 466
467 467 ################################################################################
468 468 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
469 469 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
470 470 ## execute malicious code after an exception is raised. ##
471 471 ################################################################################
472 472 set debug = false
473 473
474 474
475 475 ##############
476 476 ## STYLING ##
477 477 ##############
478 478 debug_style = false
479 479
480 480 #########################################################
481 481 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
482 482 #########################################################
483 483 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db
484 484 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test
485 485 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test
486 486
487 487 # see sqlalchemy docs for other advanced settings
488 488
489 489 ## print the sql statements to output
490 490 sqlalchemy.db1.echo = false
491 491 ## recycle the connections after this ammount of seconds
492 492 sqlalchemy.db1.pool_recycle = 3600
493 493 sqlalchemy.db1.convert_unicode = true
494 494
495 495 ## the number of connections to keep open inside the connection pool.
496 496 ## 0 indicates no limit
497 497 #sqlalchemy.db1.pool_size = 5
498 498
499 499 ## the number of connections to allow in connection pool "overflow", that is
500 500 ## connections that can be opened above and beyond the pool_size setting,
501 501 ## which defaults to five.
502 502 #sqlalchemy.db1.max_overflow = 10
503 503
504 504
505 505 ##################
506 506 ### VCS CONFIG ###
507 507 ##################
508 508 vcs.server.enable = true
509 509 vcs.server = localhost:9901
510 510 # Available protocols: pyro4, http
511 511 vcs.server.protocol = pyro4
512 512 vcs.server.log_level = info
513 513 vcs.start_server = false
514 514 vcs.backends = hg, git, svn
515 515 vcs.connection_timeout = 3600
516 516
517 517 ################################
518 518 ### LOGGING CONFIGURATION ####
519 519 ################################
520 520 [loggers]
521 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
521 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
522 522
523 523 [handlers]
524 524 keys = console, console_sql
525 525
526 526 [formatters]
527 527 keys = generic, color_formatter, color_formatter_sql
528 528
529 529 #############
530 530 ## LOGGERS ##
531 531 #############
532 532 [logger_root]
533 533 level = DEBUG
534 534 handlers = console
535 535
536 536 [logger_routes]
537 537 level = DEBUG
538 538 handlers =
539 539 qualname = routes.middleware
540 540 ## "level = DEBUG" logs the route matched and routing variables.
541 541 propagate = 1
542 542
543 543 [logger_beaker]
544 544 level = DEBUG
545 545 handlers =
546 546 qualname = beaker.container
547 547 propagate = 1
548 548
549 549 [logger_pyro4]
550 550 level = DEBUG
551 551 handlers =
552 552 qualname = Pyro4
553 553 propagate = 1
554 554
555 555 [logger_templates]
556 556 level = INFO
557 557 handlers =
558 558 qualname = pylons.templating
559 559 propagate = 1
560 560
561 561 [logger_rhodecode]
562 562 level = DEBUG
563 563 handlers =
564 564 qualname = rhodecode
565 565 propagate = 1
566 566
567 567 [logger_sqlalchemy]
568 568 level = ERROR
569 569 handlers = console_sql
570 570 qualname = sqlalchemy.engine
571 571 propagate = 0
572 572
573 [logger_whoosh_indexer]
574 level = DEBUG
575 handlers =
576 qualname = whoosh_indexer
577 propagate = 1
578
579 573 ##############
580 574 ## HANDLERS ##
581 575 ##############
582 576
583 577 [handler_console]
584 578 class = StreamHandler
585 579 args = (sys.stderr,)
586 580 level = INFO
587 581 formatter = generic
588 582
589 583 [handler_console_sql]
590 584 class = StreamHandler
591 585 args = (sys.stderr,)
592 586 level = WARN
593 587 formatter = generic
594 588
595 589 ################
596 590 ## FORMATTERS ##
597 591 ################
598 592
599 593 [formatter_generic]
600 594 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
601 595 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
602 596 datefmt = %Y-%m-%d %H:%M:%S
603 597
604 598 [formatter_color_formatter]
605 599 class = rhodecode.lib.logging_formatter.ColorFormatter
606 600 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
607 601 datefmt = %Y-%m-%d %H:%M:%S
608 602
609 603 [formatter_color_formatter_sql]
610 604 class = rhodecode.lib.logging_formatter.ColorFormatterSql
611 605 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
612 606 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,138 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 RhodeCode error controller
23 """
24
25 import cgi
26 import logging
27 import os
28 import paste.fileapp
29
30 from pylons import tmpl_context as c, request, config, url
31 from pylons.i18n.translation import _
32 from pylons.middleware import media_path
33 from webob.exc import HTTPNotFound
34
35 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.utils2 import AttributeDict
37 from rhodecode.model.settings import SettingsModel
38
39 log = logging.getLogger(__name__)
40
41
42 class ErrorController(BaseController):
43 """Generates error documents as and when they are required.
44
45 The ErrorDocuments middleware forwards to ErrorController when error
46 related status codes are returned from the application.
47
48 This behavior can be altered by changing the parameters to the
49 ErrorDocuments middleware in your config/middleware.py file.
50 """
51
52 def __before__(self):
53
54 try:
55 rc_config = SettingsModel().get_all_settings()
56 except Exception:
57 log.exception('failed to fetch settings')
58 rc_config = {}
59
60 c.visual = AttributeDict({})
61 c.visual.rhodecode_support_url = (
62 rc_config.get('rhodecode_support_url') or url('rhodecode_support'))
63 return
64
65 def document(self):
66 resp = request.environ.get('pylons.original_response', None)
67 if not resp:
68 raise HTTPNotFound()
69
70 c.rhodecode_name = config.get('rhodecode_title')
71
72 log.debug('### %s ###' % resp.status)
73
74 e = request.environ
75 c.serv_p = r'%(protocol)s://%(host)s/' % {
76 'protocol': e.get('wsgi.url_scheme'),
77 'host': e.get('HTTP_HOST'),
78 }
79
80 c.error_message = cgi.escape(request.GET.get('code', str(resp.status)))
81 c.error_explanation = self.get_error_explanation(resp.status_int)
82
83 # redirect to when error with given seconds
84 c.redirect_time = 0
85 c.redirect_module = _('Home page')
86 c.url_redirect = "/"
87
88 return render('/errors/error_document.html')
89
90 def img(self, id):
91 """Serve Pylons' stock images"""
92 return self._serve_file(os.path.join(media_path, 'img', id))
93
94 def style(self, id):
95 """Serve Pylons' stock stylesheets"""
96 return self._serve_file(os.path.join(media_path, 'style', id))
97
98 def _serve_file(self, path):
99 """Call Paste's FileApp (a WSGI application) to serve the file
100 at the specified path
101 """
102 fapp = paste.fileapp.FileApp(path)
103 return fapp(request.environ, self.start_response)
104
105 def get_error_explanation(self, code):
106 """ get the error explanations of int codes
107 [400, 401, 403, 404, 500]"""
108 try:
109 code = int(code)
110 except Exception:
111 code = 500
112
113 if code == 400:
114 return _('The request could not be understood by the server'
115 ' due to malformed syntax.')
116 if code == 401:
117 return _('Unauthorized access to resource')
118 if code == 403:
119 return _("You don't have permission to view this page")
120 if code == 404:
121 return _('The resource could not be found')
122 if code == 500:
123 return _('The server encountered an unexpected condition'
124 ' which prevented it from fulfilling the request.')
125
126 def vcs_unavailable(self):
127 c.rhodecode_name = config.get('rhodecode_title')
128 c.error_message = _('VCS Server Required')
129 c.error_explanation = _(
130 'A VCS Server is required for this action. '
131 'There is currently no VCS Server configured.')
132 c.error_docs_link = 'https://docs.rhodecode.com/RhodeCode-Control/'\
133 'tasks/upgrade-from-cli.html#manually-changing-vcs-server-settings'
134 # redirect to when error with given seconds
135 c.redirect_time = 0
136 c.redirect_module = _('Home page')
137 c.url_redirect = "/"
138 return render('/errors/error_document.html')
@@ -1,79 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import gc
23 import objgraph
24 import cProfile
25 import pstats
26 import cgi
27 import pprint
28 import threading
29
30 from StringIO import StringIO
31
32
33 class ProfilingMiddleware(object):
34 def __init__(self, app):
35 self.lock = threading.Lock()
36 self.app = app
37
38 def __call__(self, environ, start_response):
39 with self.lock:
40 profiler = cProfile.Profile()
41
42 def run_app(*a, **kw):
43 self.response = self.app(environ, start_response)
44
45 profiler.runcall(run_app, environ, start_response)
46
47 profiler.snapshot_stats()
48
49 stats = pstats.Stats(profiler)
50 stats.sort_stats('calls') #cummulative
51
52 # Redirect output
53 out = StringIO()
54 stats.stream = out
55
56 stats.print_stats()
57
58 resp = ''.join(self.response)
59
60 # Lets at least only put this on html-like responses.
61 if resp.strip().startswith('<'):
62 ## The profiling info is just appended to the response.
63 ## Browsers don't mind this.
64 resp += ('<pre style="text-align:left; '
65 'border-top: 4px dashed red; padding: 1em;">')
66 resp += cgi.escape(out.getvalue(), True)
67
68 ct = objgraph.show_most_common_types()
69 print ct
70
71 resp += ct if ct else '---'
72
73 output = StringIO()
74 pprint.pprint(environ, output, depth=3)
75
76 resp += cgi.escape(output.getvalue(), True)
77 resp += '</pre>'
78
79 return resp
@@ -1,29 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.tests import url
24
25
26 @pytest.mark.usefixtures('app')
27 class TestErrorController(object):
28 def test_direct_document_call(self):
29 self.app.get(url(controller='error', action='document'), status=404)
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
General Comments 0
You need to be logged in to leave comments. Login now