##// END OF EJS Templates
repo-switcher: new unified search box for filtering/accessing users, repos and repo groups....
marcink -
r2774:a8ecef4e default
parent child Browse files
Show More
@@ -1,40 +1,62 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 def assert_and_get_content(result):
22 def assert_and_get_main_filter_content(result):
23 repos = []
23 repos = []
24 groups = []
24 groups = []
25 commits = []
25 commits = []
26 users = []
27 for data_item in result:
28 assert data_item['id']
29 assert data_item['value']
30 assert data_item['value_display']
31 assert data_item['url']
32
33 if data_item['type'] == 'search':
34 assert data_item['value_display'].startswith('Full text search for:')
35 elif data_item['type'] == 'repo':
36 repos.append(data_item)
37 elif data_item['type'] == 'repo_group':
38 groups.append(data_item)
39 elif data_item['type'] == 'user':
40 users.append(data_item)
41 elif data_item['type'] == 'commit':
42 commits.append(data_item)
43 else:
44 raise Exception('invalid type `%s`' % data_item['type'])
45
46 return repos, groups, users, commits
47
48
49 def assert_and_get_repo_list_content(result):
50 repos = []
26 for data in result:
51 for data in result:
27 for data_item in data['children']:
52 for data_item in data['children']:
28 assert data_item['id']
53 assert data_item['id']
29 assert data_item['text']
54 assert data_item['text']
30 assert data_item['url']
55 assert data_item['url']
56
31 if data_item['type'] == 'repo':
57 if data_item['type'] == 'repo':
32 repos.append(data_item)
58 repos.append(data_item)
33 elif data_item['type'] == 'group':
34 groups.append(data_item)
35 elif data_item['type'] == 'commit':
36 commits.append(data_item)
37 else:
59 else:
38 raise Exception('invalid type %s' % data_item['type'])
60 raise Exception('invalid type %s' % data_item['type'])
39
61
40 return repos, groups, commits No newline at end of file
62 return repos
@@ -1,151 +1,180 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
21 import json
22
22
23 import pytest
23 import pytest
24
24
25 from . import assert_and_get_content
25 from . import assert_and_get_main_filter_content
26 from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29 from rhodecode.lib.utils import map_groups
29 from rhodecode.lib.utils import map_groups
30 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo_group import RepoGroupModel
31 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.db import Session, Repository, RepoGroup
32 from rhodecode.model.db import Session, Repository, RepoGroup
33
33
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36
36
37 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
38 import urllib
38 import urllib
39
39
40 base_url = {
40 base_url = {
41 'goto_switcher_data': '/_goto_data',
41 'goto_switcher_data': '/_goto_data',
42 }[name].format(**kwargs)
42 }[name].format(**kwargs)
43
43
44 if params:
44 if params:
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 return base_url
46 return base_url
47
47
48
48
49 class TestGotoSwitcherData(TestController):
49 class TestGotoSwitcherData(TestController):
50
50
51 required_repos_with_groups = [
51 required_repos_with_groups = [
52 'abc',
52 'abc',
53 'abc-fork',
53 'abc-fork',
54 'forks/abcd',
54 'forks/abcd',
55 'abcd',
55 'abcd',
56 'abcde',
56 'abcde',
57 'a/abc',
57 'a/abc',
58 'aa/abc',
58 'aa/abc',
59 'aaa/abc',
59 'aaa/abc',
60 'aaaa/abc',
60 'aaaa/abc',
61 'repos_abc/aaa/abc',
61 'repos_abc/aaa/abc',
62 'abc_repos/abc',
62 'abc_repos/abc',
63 'abc_repos/abcd',
63 'abc_repos/abcd',
64 'xxx/xyz',
64 'xxx/xyz',
65 'forked-abc/a/abc'
65 'forked-abc/a/abc'
66 ]
66 ]
67
67
68 @pytest.fixture(autouse=True, scope='class')
68 @pytest.fixture(autouse=True, scope='class')
69 def prepare(self, request, baseapp):
69 def prepare(self, request, baseapp):
70 for repo_and_group in self.required_repos_with_groups:
70 for repo_and_group in self.required_repos_with_groups:
71 # create structure of groups and return the last group
71 # create structure of groups and return the last group
72
72
73 repo_group = map_groups(repo_and_group)
73 repo_group = map_groups(repo_and_group)
74
74
75 RepoModel()._create_repo(
75 RepoModel()._create_repo(
76 repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN,
76 repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN,
77 repo_group=getattr(repo_group, 'group_id', None))
77 repo_group=getattr(repo_group, 'group_id', None))
78
78
79 Session().commit()
79 Session().commit()
80
80
81 request.addfinalizer(self.cleanup)
81 request.addfinalizer(self.cleanup)
82
82
83 def cleanup(self):
83 def cleanup(self):
84 # first delete all repos
84 # first delete all repos
85 for repo_and_groups in self.required_repos_with_groups:
85 for repo_and_groups in self.required_repos_with_groups:
86 repo = Repository.get_by_repo_name(repo_and_groups)
86 repo = Repository.get_by_repo_name(repo_and_groups)
87 if repo:
87 if repo:
88 RepoModel().delete(repo)
88 RepoModel().delete(repo)
89 Session().commit()
89 Session().commit()
90
90
91 # then delete all empty groups
91 # then delete all empty groups
92 for repo_and_groups in self.required_repos_with_groups:
92 for repo_and_groups in self.required_repos_with_groups:
93 if '/' in repo_and_groups:
93 if '/' in repo_and_groups:
94 r_group = repo_and_groups.rsplit('/', 1)[0]
94 r_group = repo_and_groups.rsplit('/', 1)[0]
95 repo_group = RepoGroup.get_by_group_name(r_group)
95 repo_group = RepoGroup.get_by_group_name(r_group)
96 if not repo_group:
96 if not repo_group:
97 continue
97 continue
98 parents = repo_group.parents
98 parents = repo_group.parents
99 RepoGroupModel().delete(repo_group, force_delete=True)
99 RepoGroupModel().delete(repo_group, force_delete=True)
100 Session().commit()
100 Session().commit()
101
101
102 for el in reversed(parents):
102 for el in reversed(parents):
103 RepoGroupModel().delete(el, force_delete=True)
103 RepoGroupModel().delete(el, force_delete=True)
104 Session().commit()
104 Session().commit()
105
105
106 def test_returns_list_of_repos_and_groups(self, xhr_header):
106 def test_empty_query(self, xhr_header):
107 self.log_user()
107 self.log_user()
108
108
109 response = self.app.get(
109 response = self.app.get(
110 route_path('goto_switcher_data'),
110 route_path('goto_switcher_data'),
111 extra_environ=xhr_header, status=200)
111 extra_environ=xhr_header, status=200)
112 result = json.loads(response.body)['results']
112 result = json.loads(response.body)['suggestions']
113
114 repos, groups, commits = assert_and_get_content(result)
115
113
116 assert len(repos) == len(Repository.get_all())
114 assert result == []
117 assert len(groups) == len(RepoGroup.get_all())
118 assert len(commits) == 0
119
115
120 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
116 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
121 self.log_user()
117 self.log_user()
122
118
123 response = self.app.get(
119 response = self.app.get(
124 route_path('goto_switcher_data'),
120 route_path('goto_switcher_data'),
125 params={'query': 'abc'},
121 params={'query': 'abc'},
126 extra_environ=xhr_header, status=200)
122 extra_environ=xhr_header, status=200)
127 result = json.loads(response.body)['results']
123 result = json.loads(response.body)['suggestions']
128
124
129 repos, groups, commits = assert_and_get_content(result)
125 repos, groups, users, commits = assert_and_get_main_filter_content(result)
130
126
131 assert len(repos) == 13
127 assert len(repos) == 13
132 assert len(groups) == 5
128 assert len(groups) == 5
129 assert len(users) == 0
133 assert len(commits) == 0
130 assert len(commits) == 0
134
131
132 def test_returns_list_of_users_filtered(self, xhr_header):
133 self.log_user()
134
135 response = self.app.get(
136 route_path('goto_switcher_data'),
137 params={'query': 'user:admin'},
138 extra_environ=xhr_header, status=200)
139 result = json.loads(response.body)['suggestions']
140
141 repos, groups, users, commits = assert_and_get_main_filter_content(result)
142
143 assert len(repos) == 0
144 assert len(groups) == 0
145 assert len(users) == 1
146 assert len(commits) == 0
147
148 def test_returns_list_of_commits_filtered(self, xhr_header):
149 self.log_user()
150
151 response = self.app.get(
152 route_path('goto_switcher_data'),
153 params={'query': 'commit:e8'},
154 extra_environ=xhr_header, status=200)
155 result = json.loads(response.body)['suggestions']
156
157 repos, groups, users, commits = assert_and_get_main_filter_content(result)
158
159 assert len(repos) == 0
160 assert len(groups) == 0
161 assert len(users) == 0
162 assert len(commits) == 5
163
135 def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header):
164 def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header):
136 self.log_user()
165 self.log_user()
137
166
138 response = self.app.get(
167 response = self.app.get(
139 route_path('goto_switcher_data'),
168 route_path('goto_switcher_data'),
140 params={'query': 'abc'},
169 params={'query': 'abc'},
141 extra_environ=xhr_header, status=200)
170 extra_environ=xhr_header, status=200)
142 result = json.loads(response.body)['results']
171 result = json.loads(response.body)['suggestions']
143
172
144 repos, groups, commits = assert_and_get_content(result)
173 repos, groups, users, commits = assert_and_get_main_filter_content(result)
145
174
146 test_repos = [x['text'] for x in repos[:4]]
175 test_repos = [x['value_display'] for x in repos[:4]]
147 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
176 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
148
177
149 test_groups = [x['text'] for x in groups[:4]]
178 test_groups = [x['value_display'] for x in groups[:4]]
150 assert ['abc_repos', 'repos_abc',
179 assert ['abc_repos', 'repos_abc',
151 'forked-abc', 'forked-abc/a'] == test_groups
180 'forked-abc', 'forked-abc/a'] == test_groups
@@ -1,103 +1,95 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
21 import json
22
22
23 from . import assert_and_get_content
23 from . import assert_and_get_repo_list_content
24 from rhodecode.tests import TestController
24 from rhodecode.tests import TestController
25 from rhodecode.tests.fixture import Fixture
25 from rhodecode.tests.fixture import Fixture
26 from rhodecode.model.db import Repository
26 from rhodecode.model.db import Repository
27
27
28 fixture = Fixture()
28 fixture = Fixture()
29
29
30
30
31 def route_path(name, params=None, **kwargs):
31 def route_path(name, params=None, **kwargs):
32 import urllib
32 import urllib
33
33
34 base_url = {
34 base_url = {
35 'repo_list_data': '/_repos',
35 'repo_list_data': '/_repos',
36 }[name].format(**kwargs)
36 }[name].format(**kwargs)
37
37
38 if params:
38 if params:
39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 return base_url
40 return base_url
41
41
42
42
43 class TestRepoListData(TestController):
43 class TestRepoListData(TestController):
44
44
45 def test_returns_list_of_repos_and_groups(self, xhr_header):
45 def test_returns_list_of_repos_and_groups(self, xhr_header):
46 self.log_user()
46 self.log_user()
47
47
48 response = self.app.get(
48 response = self.app.get(
49 route_path('repo_list_data'),
49 route_path('repo_list_data'),
50 extra_environ=xhr_header, status=200)
50 extra_environ=xhr_header, status=200)
51 result = json.loads(response.body)['results']
51 result = json.loads(response.body)['results']
52
52
53 repos, groups, commits = assert_and_get_content(result)
53 repos = assert_and_get_repo_list_content(result)
54
54
55 assert len(repos) == len(Repository.get_all())
55 assert len(repos) == len(Repository.get_all())
56 assert len(groups) == 0
57 assert len(commits) == 0
58
56
59 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
57 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
60 self.log_user()
58 self.log_user()
61
59
62 response = self.app.get(
60 response = self.app.get(
63 route_path('repo_list_data'),
61 route_path('repo_list_data'),
64 params={'query': 'vcs_test_git'},
62 params={'query': 'vcs_test_git'},
65 extra_environ=xhr_header, status=200)
63 extra_environ=xhr_header, status=200)
66 result = json.loads(response.body)['results']
64 result = json.loads(response.body)['results']
67
65
68 repos, groups, commits = assert_and_get_content(result)
66 repos = assert_and_get_repo_list_content(result)
69
67
70 assert len(repos) == len(Repository.query().filter(
68 assert len(repos) == len(Repository.query().filter(
71 Repository.repo_name.ilike('%vcs_test_git%')).all())
69 Repository.repo_name.ilike('%vcs_test_git%')).all())
72 assert len(groups) == 0
73 assert len(commits) == 0
74
70
75 def test_returns_list_of_repos_and_groups_filtered_with_type(self, xhr_header):
71 def test_returns_list_of_repos_and_groups_filtered_with_type(self, xhr_header):
76 self.log_user()
72 self.log_user()
77
73
78 response = self.app.get(
74 response = self.app.get(
79 route_path('repo_list_data'),
75 route_path('repo_list_data'),
80 params={'query': 'vcs_test_git', 'repo_type': 'git'},
76 params={'query': 'vcs_test_git', 'repo_type': 'git'},
81 extra_environ=xhr_header, status=200)
77 extra_environ=xhr_header, status=200)
82 result = json.loads(response.body)['results']
78 result = json.loads(response.body)['results']
83
79
84 repos, groups, commits = assert_and_get_content(result)
80 repos = assert_and_get_repo_list_content(result)
85
81
86 assert len(repos) == len(Repository.query().filter(
82 assert len(repos) == len(Repository.query().filter(
87 Repository.repo_name.ilike('%vcs_test_git%')).all())
83 Repository.repo_name.ilike('%vcs_test_git%')).all())
88 assert len(groups) == 0
89 assert len(commits) == 0
90
84
91 def test_returns_list_of_repos_non_ascii_query(self, xhr_header):
85 def test_returns_list_of_repos_non_ascii_query(self, xhr_header):
92 self.log_user()
86 self.log_user()
93 response = self.app.get(
87 response = self.app.get(
94 route_path('repo_list_data'),
88 route_path('repo_list_data'),
95 params={'query': 'Δ‡_vcs_test_Δ…', 'repo_type': 'git'},
89 params={'query': 'Δ‡_vcs_test_Δ…', 'repo_type': 'git'},
96 extra_environ=xhr_header, status=200)
90 extra_environ=xhr_header, status=200)
97 result = json.loads(response.body)['results']
91 result = json.loads(response.body)['results']
98
92
99 repos, groups, commits = assert_and_get_content(result)
93 repos = assert_and_get_repo_list_content(result)
100
94
101 assert len(repos) == 0
95 assert len(repos) == 0
102 assert len(groups) == 0
103 assert len(commits) == 0
@@ -1,325 +1,374 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22 import logging
22 import logging
23 import collections
23
24
24 from pyramid.view import view_config
25 from pyramid.view import view_config
25
26
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
29 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator)
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator)
30 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.lib.utils2 import safe_unicode, str2bool
32 from rhodecode.lib.utils2 import safe_unicode, str2bool
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.ext_json import json
33 from rhodecode.model.db import (
34 from rhodecode.model.db import (
34 func, or_, in_filter_generator, Repository, RepoGroup)
35 func, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.scm import RepoGroupList, RepoList
38 from rhodecode.model.scm import RepoGroupList, RepoList
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.user_group import UserGroupModel
40 from rhodecode.model.user_group import UserGroupModel
40
41
41 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
42
43
43
44
44 class HomeView(BaseAppView):
45 class HomeView(BaseAppView):
45
46
46 def load_default_context(self):
47 def load_default_context(self):
47 c = self._get_local_tmpl_context()
48 c = self._get_local_tmpl_context()
48 c.user = c.auth_user.get_instance()
49 c.user = c.auth_user.get_instance()
49
50
50 return c
51 return c
51
52
52 @LoginRequired()
53 @LoginRequired()
53 @view_config(
54 @view_config(
54 route_name='user_autocomplete_data', request_method='GET',
55 route_name='user_autocomplete_data', request_method='GET',
55 renderer='json_ext', xhr=True)
56 renderer='json_ext', xhr=True)
56 def user_autocomplete_data(self):
57 def user_autocomplete_data(self):
57 self.load_default_context()
58 self.load_default_context()
58 query = self.request.GET.get('query')
59 query = self.request.GET.get('query')
59 active = str2bool(self.request.GET.get('active') or True)
60 active = str2bool(self.request.GET.get('active') or True)
60 include_groups = str2bool(self.request.GET.get('user_groups'))
61 include_groups = str2bool(self.request.GET.get('user_groups'))
61 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
62 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
62 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
63 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
63
64
64 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
65 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
65 query, active, include_groups)
66 query, active, include_groups)
66
67
67 _users = UserModel().get_users(
68 _users = UserModel().get_users(
68 name_contains=query, only_active=active)
69 name_contains=query, only_active=active)
69
70
70 def maybe_skip_default_user(usr):
71 def maybe_skip_default_user(usr):
71 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
72 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
72 return False
73 return False
73 return True
74 return True
74 _users = filter(maybe_skip_default_user, _users)
75 _users = filter(maybe_skip_default_user, _users)
75
76
76 if include_groups:
77 if include_groups:
77 # extend with user groups
78 # extend with user groups
78 _user_groups = UserGroupModel().get_user_groups(
79 _user_groups = UserGroupModel().get_user_groups(
79 name_contains=query, only_active=active,
80 name_contains=query, only_active=active,
80 expand_groups=expand_groups)
81 expand_groups=expand_groups)
81 _users = _users + _user_groups
82 _users = _users + _user_groups
82
83
83 return {'suggestions': _users}
84 return {'suggestions': _users}
84
85
85 @LoginRequired()
86 @LoginRequired()
86 @NotAnonymous()
87 @NotAnonymous()
87 @view_config(
88 @view_config(
88 route_name='user_group_autocomplete_data', request_method='GET',
89 route_name='user_group_autocomplete_data', request_method='GET',
89 renderer='json_ext', xhr=True)
90 renderer='json_ext', xhr=True)
90 def user_group_autocomplete_data(self):
91 def user_group_autocomplete_data(self):
91 self.load_default_context()
92 self.load_default_context()
92 query = self.request.GET.get('query')
93 query = self.request.GET.get('query')
93 active = str2bool(self.request.GET.get('active') or True)
94 active = str2bool(self.request.GET.get('active') or True)
94 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
95 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
95
96
96 log.debug('generating user group list, query:%s, active:%s',
97 log.debug('generating user group list, query:%s, active:%s',
97 query, active)
98 query, active)
98
99
99 _user_groups = UserGroupModel().get_user_groups(
100 _user_groups = UserGroupModel().get_user_groups(
100 name_contains=query, only_active=active,
101 name_contains=query, only_active=active,
101 expand_groups=expand_groups)
102 expand_groups=expand_groups)
102 _user_groups = _user_groups
103 _user_groups = _user_groups
103
104
104 return {'suggestions': _user_groups}
105 return {'suggestions': _user_groups}
105
106
106 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
107 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
108 org_query = name_contains
107 allowed_ids = self._rhodecode_user.repo_acl_ids(
109 allowed_ids = self._rhodecode_user.repo_acl_ids(
108 ['repository.read', 'repository.write', 'repository.admin'],
110 ['repository.read', 'repository.write', 'repository.admin'],
109 cache=False, name_filter=name_contains) or [-1]
111 cache=False, name_filter=name_contains) or [-1]
110
112
111 query = Repository.query()\
113 query = Repository.query()\
112 .order_by(func.length(Repository.repo_name))\
114 .order_by(func.length(Repository.repo_name))\
113 .order_by(Repository.repo_name)\
115 .order_by(Repository.repo_name)\
114 .filter(or_(
116 .filter(or_(
115 # generate multiple IN to fix limitation problems
117 # generate multiple IN to fix limitation problems
116 *in_filter_generator(Repository.repo_id, allowed_ids)
118 *in_filter_generator(Repository.repo_id, allowed_ids)
117 ))
119 ))
118
120
119 if repo_type:
121 if repo_type:
120 query = query.filter(Repository.repo_type == repo_type)
122 query = query.filter(Repository.repo_type == repo_type)
121
123
122 if name_contains:
124 if name_contains:
123 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
125 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
124 query = query.filter(
126 query = query.filter(
125 Repository.repo_name.ilike(ilike_expression))
127 Repository.repo_name.ilike(ilike_expression))
126 query = query.limit(limit)
128 query = query.limit(limit)
127
129
128 acl_repo_iter = query
130 acl_iter = query
129
131
130 return [
132 return [
131 {
133 {
132 'id': obj.repo_name,
134 'id': obj.repo_name,
135 'value': org_query,
136 'value_display': obj.repo_name,
133 'text': obj.repo_name,
137 'text': obj.repo_name,
134 'type': 'repo',
138 'type': 'repo',
135 'obj': {'repo_type': obj.repo_type, 'private': obj.private,
139 'repo_id': obj.repo_id,
136 'repo_id': obj.repo_id},
140 'repo_type': obj.repo_type,
141 'private': obj.private,
137 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
142 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
138 }
143 }
139 for obj in acl_repo_iter]
144 for obj in acl_iter]
140
145
141 def _get_repo_group_list(self, name_contains=None, limit=20):
146 def _get_repo_group_list(self, name_contains=None, limit=20):
147 org_query = name_contains
142 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
148 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
143 ['group.read', 'group.write', 'group.admin'],
149 ['group.read', 'group.write', 'group.admin'],
144 cache=False, name_filter=name_contains) or [-1]
150 cache=False, name_filter=name_contains) or [-1]
145
151
146 query = RepoGroup.query()\
152 query = RepoGroup.query()\
147 .order_by(func.length(RepoGroup.group_name))\
153 .order_by(func.length(RepoGroup.group_name))\
148 .order_by(RepoGroup.group_name) \
154 .order_by(RepoGroup.group_name) \
149 .filter(or_(
155 .filter(or_(
150 # generate multiple IN to fix limitation problems
156 # generate multiple IN to fix limitation problems
151 *in_filter_generator(RepoGroup.group_id, allowed_ids)
157 *in_filter_generator(RepoGroup.group_id, allowed_ids)
152 ))
158 ))
153
159
154 if name_contains:
160 if name_contains:
155 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
161 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
156 query = query.filter(
162 query = query.filter(
157 RepoGroup.group_name.ilike(ilike_expression))
163 RepoGroup.group_name.ilike(ilike_expression))
158 query = query.limit(limit)
164 query = query.limit(limit)
159
165
160 acl_repo_iter = query
166 acl_iter = query
161
167
162 return [
168 return [
163 {
169 {
164 'id': obj.group_name,
170 'id': obj.group_name,
165 'text': obj.group_name,
171 'value': org_query,
166 'type': 'group',
172 'value_display': obj.group_name,
167 'obj': {},
173 'type': 'repo_group',
168 'url': h.route_path(
174 'url': h.route_path(
169 'repo_group_home', repo_group_name=obj.group_name)
175 'repo_group_home', repo_group_name=obj.group_name)
170 }
176 }
171 for obj in acl_repo_iter]
177 for obj in acl_iter]
178
179 def _get_user_list(self, name_contains=None, limit=20):
180 org_query = name_contains
181 if not name_contains:
182 return []
183
184 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
185 if len(name_contains) != 1:
186 return []
187 name_contains = name_contains[0]
188
189 query = User.query()\
190 .order_by(func.length(User.username))\
191 .order_by(User.username) \
192 .filter(User.username != User.DEFAULT_USER)
172
193
173 def _get_hash_commit_list(self, auth_user, query=None):
194 if name_contains:
195 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
196 query = query.filter(
197 User.username.ilike(ilike_expression))
198 query = query.limit(limit)
199
200 acl_iter = query
201
202 return [
203 {
204 'id': obj.user_id,
205 'value': org_query,
206 'value_display': obj.username,
207 'type': 'user',
208 'icon_link': h.gravatar_url(obj.email, 30),
209 'url': h.route_path(
210 'user_profile', username=obj.username)
211 }
212 for obj in acl_iter]
213
214 def _get_hash_commit_list(self, auth_user, query):
215 org_query = query
174 if not query or len(query) < 3:
216 if not query or len(query) < 3:
175 return []
217 return []
176
218
177 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
219 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
178
220
179 if len(commit_hashes) != 1:
221 if len(commit_hashes) != 1:
180 return []
222 return []
181
223 commit_hash = commit_hashes[0]
182 commit_hash_prefix = commit_hashes[0]
183
224
184 searcher = searcher_from_config(self.request.registry.settings)
225 searcher = searcher_from_config(self.request.registry.settings)
185 result = searcher.search(
226 result = searcher.search(
186 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
227 'commit_id:%s*' % commit_hash, 'commit', auth_user,
187 raise_on_exc=False)
228 raise_on_exc=False)
188
229
189 return [
230 return [
190 {
231 {
191 'id': entry['commit_id'],
232 'id': entry['commit_id'],
192 'text': entry['commit_id'],
233 'value': org_query,
234 'value_display': 'repo `{}` commit: {}'.format(
235 entry['repository'], entry['commit_id']),
193 'type': 'commit',
236 'type': 'commit',
194 'obj': {'repo': entry['repository']},
237 'repo': entry['repository'],
195 'url': h.route_path(
238 'url': h.route_path(
196 'repo_commit',
239 'repo_commit',
197 repo_name=entry['repository'], commit_id=entry['commit_id'])
240 repo_name=entry['repository'], commit_id=entry['commit_id'])
198 }
241 }
199 for entry in result['results']]
242 for entry in result['results']]
200
243
201 @LoginRequired()
244 @LoginRequired()
202 @view_config(
245 @view_config(
203 route_name='repo_list_data', request_method='GET',
246 route_name='repo_list_data', request_method='GET',
204 renderer='json_ext', xhr=True)
247 renderer='json_ext', xhr=True)
205 def repo_list_data(self):
248 def repo_list_data(self):
206 _ = self.request.translate
249 _ = self.request.translate
207 self.load_default_context()
250 self.load_default_context()
208
251
209 query = self.request.GET.get('query')
252 query = self.request.GET.get('query')
210 repo_type = self.request.GET.get('repo_type')
253 repo_type = self.request.GET.get('repo_type')
211 log.debug('generating repo list, query:%s, repo_type:%s',
254 log.debug('generating repo list, query:%s, repo_type:%s',
212 query, repo_type)
255 query, repo_type)
213
256
214 res = []
257 res = []
215 repos = self._get_repo_list(query, repo_type=repo_type)
258 repos = self._get_repo_list(query, repo_type=repo_type)
216 if repos:
259 if repos:
217 res.append({
260 res.append({
218 'text': _('Repositories'),
261 'text': _('Repositories'),
219 'children': repos
262 'children': repos
220 })
263 })
221
264
222 data = {
265 data = {
223 'more': False,
266 'more': False,
224 'results': res
267 'results': res
225 }
268 }
226 return data
269 return data
227
270
228 @LoginRequired()
271 @LoginRequired()
229 @view_config(
272 @view_config(
230 route_name='goto_switcher_data', request_method='GET',
273 route_name='goto_switcher_data', request_method='GET',
231 renderer='json_ext', xhr=True)
274 renderer='json_ext', xhr=True)
232 def goto_switcher_data(self):
275 def goto_switcher_data(self):
233 c = self.load_default_context()
276 c = self.load_default_context()
234
277
235 _ = self.request.translate
278 _ = self.request.translate
236
279
237 query = self.request.GET.get('query')
280 query = self.request.GET.get('query')
238 log.debug('generating goto switcher list, query %s', query)
281 log.debug('generating main filter data, query %s', query)
282
283 default_search_val = 'Full text search for: `{}`'.format(query)
284 res = []
285 if not query:
286 return {'suggestions': res}
239
287
240 res = []
288 res.append({
289 'id': -1,
290 'value': query,
291 'value_display': default_search_val,
292 'type': 'search',
293 'url': h.route_path(
294 'search', _query={'q': query})
295 })
296
241 repo_groups = self._get_repo_group_list(query)
297 repo_groups = self._get_repo_group_list(query)
242 if repo_groups:
298 for serialized_repo_group in repo_groups:
243 res.append({
299 res.append(serialized_repo_group)
244 'text': _('Groups'),
245 'children': repo_groups
246 })
247
300
248 repos = self._get_repo_list(query)
301 repos = self._get_repo_list(query)
249 if repos:
302 for serialized_repo in repos:
250 res.append({
303 res.append(serialized_repo)
251 'text': _('Repositories'),
304
252 'children': repos
305 # TODO(marcink): permissions for that ?
253 })
306 users = self._get_user_list(query)
307 for serialized_user in users:
308 res.append(serialized_user)
254
309
255 commits = self._get_hash_commit_list(c.auth_user, query)
310 commits = self._get_hash_commit_list(c.auth_user, query)
256 if commits:
311 if commits:
257 unique_repos = {}
312 unique_repos = collections.OrderedDict()
258 for commit in commits:
313 for commit in commits:
259 unique_repos.setdefault(commit['obj']['repo'], []
314 repo_name = commit['repo']
260 ).append(commit)
315 unique_repos.setdefault(repo_name, []).append(commit)
261
316
262 for repo in unique_repos:
317 for repo, commits in unique_repos.items():
263 res.append({
318 for commit in commits:
264 'text': _('Commits in %(repo)s') % {'repo': repo},
319 res.append(commit)
265 'children': unique_repos[repo]
266 })
267
320
268 data = {
321 return {'suggestions': res}
269 'more': False,
270 'results': res
271 }
272 return data
273
322
274 def _get_groups_and_repos(self, repo_group_id=None):
323 def _get_groups_and_repos(self, repo_group_id=None):
275 # repo groups groups
324 # repo groups groups
276 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
325 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
277 _perms = ['group.read', 'group.write', 'group.admin']
326 _perms = ['group.read', 'group.write', 'group.admin']
278 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
327 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
279 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
328 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
280 repo_group_list=repo_group_list_acl, admin=False)
329 repo_group_list=repo_group_list_acl, admin=False)
281
330
282 # repositories
331 # repositories
283 repo_list = Repository.get_all_repos(group_id=repo_group_id)
332 repo_list = Repository.get_all_repos(group_id=repo_group_id)
284 _perms = ['repository.read', 'repository.write', 'repository.admin']
333 _perms = ['repository.read', 'repository.write', 'repository.admin']
285 repo_list_acl = RepoList(repo_list, perm_set=_perms)
334 repo_list_acl = RepoList(repo_list, perm_set=_perms)
286 repo_data = RepoModel().get_repos_as_dict(
335 repo_data = RepoModel().get_repos_as_dict(
287 repo_list=repo_list_acl, admin=False)
336 repo_list=repo_list_acl, admin=False)
288
337
289 return repo_data, repo_group_data
338 return repo_data, repo_group_data
290
339
291 @LoginRequired()
340 @LoginRequired()
292 @view_config(
341 @view_config(
293 route_name='home', request_method='GET',
342 route_name='home', request_method='GET',
294 renderer='rhodecode:templates/index.mako')
343 renderer='rhodecode:templates/index.mako')
295 def main_page(self):
344 def main_page(self):
296 c = self.load_default_context()
345 c = self.load_default_context()
297 c.repo_group = None
346 c.repo_group = None
298
347
299 repo_data, repo_group_data = self._get_groups_and_repos()
348 repo_data, repo_group_data = self._get_groups_and_repos()
300 # json used to render the grids
349 # json used to render the grids
301 c.repos_data = json.dumps(repo_data)
350 c.repos_data = json.dumps(repo_data)
302 c.repo_groups_data = json.dumps(repo_group_data)
351 c.repo_groups_data = json.dumps(repo_group_data)
303
352
304 return self._get_template_context(c)
353 return self._get_template_context(c)
305
354
306 @LoginRequired()
355 @LoginRequired()
307 @HasRepoGroupPermissionAnyDecorator(
356 @HasRepoGroupPermissionAnyDecorator(
308 'group.read', 'group.write', 'group.admin')
357 'group.read', 'group.write', 'group.admin')
309 @view_config(
358 @view_config(
310 route_name='repo_group_home', request_method='GET',
359 route_name='repo_group_home', request_method='GET',
311 renderer='rhodecode:templates/index_repo_group.mako')
360 renderer='rhodecode:templates/index_repo_group.mako')
312 @view_config(
361 @view_config(
313 route_name='repo_group_home_slash', request_method='GET',
362 route_name='repo_group_home_slash', request_method='GET',
314 renderer='rhodecode:templates/index_repo_group.mako')
363 renderer='rhodecode:templates/index_repo_group.mako')
315 def repo_group_main_page(self):
364 def repo_group_main_page(self):
316 c = self.load_default_context()
365 c = self.load_default_context()
317 c.repo_group = self.request.db_repo_group
366 c.repo_group = self.request.db_repo_group
318 repo_data, repo_group_data = self._get_groups_and_repos(
367 repo_data, repo_group_data = self._get_groups_and_repos(
319 c.repo_group.group_id)
368 c.repo_group.group_id)
320
369
321 # json used to render the grids
370 # json used to render the grids
322 c.repos_data = json.dumps(repo_data)
371 c.repos_data = json.dumps(repo_data)
323 c.repo_groups_data = json.dumps(repo_group_data)
372 c.repo_groups_data = json.dumps(repo_group_data)
324
373
325 return self._get_template_context(c)
374 return self._get_template_context(c)
@@ -1,1298 +1,1301 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import collections
22 import collections
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
34
34
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
36 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.base import vcs_operation_context
37 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
44 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
45 RepositoryRequirementError, EmptyRepositoryError)
45 RepositoryRequirementError, EmptyRepositoryError)
46 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
49 ChangesetComment, ChangesetStatus, Repository)
49 ChangesetComment, ChangesetStatus, Repository)
50 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.forms import PullRequestForm
51 from rhodecode.model.meta import Session
51 from rhodecode.model.meta import Session
52 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58 class RepoPullRequestsView(RepoAppView, DataGridAppView):
59
59
60 def load_default_context(self):
60 def load_default_context(self):
61 c = self._get_local_tmpl_context(include_app_defaults=True)
61 c = self._get_local_tmpl_context(include_app_defaults=True)
62 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
64
64
65 return c
65 return c
66
66
67 def _get_pull_requests_list(
67 def _get_pull_requests_list(
68 self, repo_name, source, filter_type, opened_by, statuses):
68 self, repo_name, source, filter_type, opened_by, statuses):
69
69
70 draw, start, limit = self._extract_chunk(self.request)
70 draw, start, limit = self._extract_chunk(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 _render = self.request.get_partial_renderer(
72 _render = self.request.get_partial_renderer(
73 'rhodecode:templates/data_table/_dt_elements.mako')
73 'rhodecode:templates/data_table/_dt_elements.mako')
74
74
75 # pagination
75 # pagination
76
76
77 if filter_type == 'awaiting_review':
77 if filter_type == 'awaiting_review':
78 pull_requests = PullRequestModel().get_awaiting_review(
78 pull_requests = PullRequestModel().get_awaiting_review(
79 repo_name, source=source, opened_by=opened_by,
79 repo_name, source=source, opened_by=opened_by,
80 statuses=statuses, offset=start, length=limit,
80 statuses=statuses, offset=start, length=limit,
81 order_by=order_by, order_dir=order_dir)
81 order_by=order_by, order_dir=order_dir)
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 repo_name, source=source, statuses=statuses,
83 repo_name, source=source, statuses=statuses,
84 opened_by=opened_by)
84 opened_by=opened_by)
85 elif filter_type == 'awaiting_my_review':
85 elif filter_type == 'awaiting_my_review':
86 pull_requests = PullRequestModel().get_awaiting_my_review(
86 pull_requests = PullRequestModel().get_awaiting_my_review(
87 repo_name, source=source, opened_by=opened_by,
87 repo_name, source=source, opened_by=opened_by,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 offset=start, length=limit, order_by=order_by,
89 offset=start, length=limit, order_by=order_by,
90 order_dir=order_dir)
90 order_dir=order_dir)
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 repo_name, source=source, user_id=self._rhodecode_user.user_id,
92 repo_name, source=source, user_id=self._rhodecode_user.user_id,
93 statuses=statuses, opened_by=opened_by)
93 statuses=statuses, opened_by=opened_by)
94 else:
94 else:
95 pull_requests = PullRequestModel().get_all(
95 pull_requests = PullRequestModel().get_all(
96 repo_name, source=source, opened_by=opened_by,
96 repo_name, source=source, opened_by=opened_by,
97 statuses=statuses, offset=start, length=limit,
97 statuses=statuses, offset=start, length=limit,
98 order_by=order_by, order_dir=order_dir)
98 order_by=order_by, order_dir=order_dir)
99 pull_requests_total_count = PullRequestModel().count_all(
99 pull_requests_total_count = PullRequestModel().count_all(
100 repo_name, source=source, statuses=statuses,
100 repo_name, source=source, statuses=statuses,
101 opened_by=opened_by)
101 opened_by=opened_by)
102
102
103 data = []
103 data = []
104 comments_model = CommentsModel()
104 comments_model = CommentsModel()
105 for pr in pull_requests:
105 for pr in pull_requests:
106 comments = comments_model.get_all_comments(
106 comments = comments_model.get_all_comments(
107 self.db_repo.repo_id, pull_request=pr)
107 self.db_repo.repo_id, pull_request=pr)
108
108
109 data.append({
109 data.append({
110 'name': _render('pullrequest_name',
110 'name': _render('pullrequest_name',
111 pr.pull_request_id, pr.target_repo.repo_name),
111 pr.pull_request_id, pr.target_repo.repo_name),
112 'name_raw': pr.pull_request_id,
112 'name_raw': pr.pull_request_id,
113 'status': _render('pullrequest_status',
113 'status': _render('pullrequest_status',
114 pr.calculated_review_status()),
114 pr.calculated_review_status()),
115 'title': _render(
115 'title': _render(
116 'pullrequest_title', pr.title, pr.description),
116 'pullrequest_title', pr.title, pr.description),
117 'description': h.escape(pr.description),
117 'description': h.escape(pr.description),
118 'updated_on': _render('pullrequest_updated_on',
118 'updated_on': _render('pullrequest_updated_on',
119 h.datetime_to_time(pr.updated_on)),
119 h.datetime_to_time(pr.updated_on)),
120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 'created_on': _render('pullrequest_updated_on',
121 'created_on': _render('pullrequest_updated_on',
122 h.datetime_to_time(pr.created_on)),
122 h.datetime_to_time(pr.created_on)),
123 'created_on_raw': h.datetime_to_time(pr.created_on),
123 'created_on_raw': h.datetime_to_time(pr.created_on),
124 'author': _render('pullrequest_author',
124 'author': _render('pullrequest_author',
125 pr.author.full_contact, ),
125 pr.author.full_contact, ),
126 'author_raw': pr.author.full_name,
126 'author_raw': pr.author.full_name,
127 'comments': _render('pullrequest_comments', len(comments)),
127 'comments': _render('pullrequest_comments', len(comments)),
128 'comments_raw': len(comments),
128 'comments_raw': len(comments),
129 'closed': pr.is_closed(),
129 'closed': pr.is_closed(),
130 })
130 })
131
131
132 data = ({
132 data = ({
133 'draw': draw,
133 'draw': draw,
134 'data': data,
134 'data': data,
135 'recordsTotal': pull_requests_total_count,
135 'recordsTotal': pull_requests_total_count,
136 'recordsFiltered': pull_requests_total_count,
136 'recordsFiltered': pull_requests_total_count,
137 })
137 })
138 return data
138 return data
139
139
140 @LoginRequired()
140 @LoginRequired()
141 @HasRepoPermissionAnyDecorator(
141 @HasRepoPermissionAnyDecorator(
142 'repository.read', 'repository.write', 'repository.admin')
142 'repository.read', 'repository.write', 'repository.admin')
143 @view_config(
143 @view_config(
144 route_name='pullrequest_show_all', request_method='GET',
144 route_name='pullrequest_show_all', request_method='GET',
145 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
145 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
146 def pull_request_list(self):
146 def pull_request_list(self):
147 c = self.load_default_context()
147 c = self.load_default_context()
148
148
149 req_get = self.request.GET
149 req_get = self.request.GET
150 c.source = str2bool(req_get.get('source'))
150 c.source = str2bool(req_get.get('source'))
151 c.closed = str2bool(req_get.get('closed'))
151 c.closed = str2bool(req_get.get('closed'))
152 c.my = str2bool(req_get.get('my'))
152 c.my = str2bool(req_get.get('my'))
153 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
153 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
154 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
154 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
155
155
156 c.active = 'open'
156 c.active = 'open'
157 if c.my:
157 if c.my:
158 c.active = 'my'
158 c.active = 'my'
159 if c.closed:
159 if c.closed:
160 c.active = 'closed'
160 c.active = 'closed'
161 if c.awaiting_review and not c.source:
161 if c.awaiting_review and not c.source:
162 c.active = 'awaiting'
162 c.active = 'awaiting'
163 if c.source and not c.awaiting_review:
163 if c.source and not c.awaiting_review:
164 c.active = 'source'
164 c.active = 'source'
165 if c.awaiting_my_review:
165 if c.awaiting_my_review:
166 c.active = 'awaiting_my'
166 c.active = 'awaiting_my'
167
167
168 return self._get_template_context(c)
168 return self._get_template_context(c)
169
169
170 @LoginRequired()
170 @LoginRequired()
171 @HasRepoPermissionAnyDecorator(
171 @HasRepoPermissionAnyDecorator(
172 'repository.read', 'repository.write', 'repository.admin')
172 'repository.read', 'repository.write', 'repository.admin')
173 @view_config(
173 @view_config(
174 route_name='pullrequest_show_all_data', request_method='GET',
174 route_name='pullrequest_show_all_data', request_method='GET',
175 renderer='json_ext', xhr=True)
175 renderer='json_ext', xhr=True)
176 def pull_request_list_data(self):
176 def pull_request_list_data(self):
177 self.load_default_context()
177 self.load_default_context()
178
178
179 # additional filters
179 # additional filters
180 req_get = self.request.GET
180 req_get = self.request.GET
181 source = str2bool(req_get.get('source'))
181 source = str2bool(req_get.get('source'))
182 closed = str2bool(req_get.get('closed'))
182 closed = str2bool(req_get.get('closed'))
183 my = str2bool(req_get.get('my'))
183 my = str2bool(req_get.get('my'))
184 awaiting_review = str2bool(req_get.get('awaiting_review'))
184 awaiting_review = str2bool(req_get.get('awaiting_review'))
185 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
185 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
186
186
187 filter_type = 'awaiting_review' if awaiting_review \
187 filter_type = 'awaiting_review' if awaiting_review \
188 else 'awaiting_my_review' if awaiting_my_review \
188 else 'awaiting_my_review' if awaiting_my_review \
189 else None
189 else None
190
190
191 opened_by = None
191 opened_by = None
192 if my:
192 if my:
193 opened_by = [self._rhodecode_user.user_id]
193 opened_by = [self._rhodecode_user.user_id]
194
194
195 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
195 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
196 if closed:
196 if closed:
197 statuses = [PullRequest.STATUS_CLOSED]
197 statuses = [PullRequest.STATUS_CLOSED]
198
198
199 data = self._get_pull_requests_list(
199 data = self._get_pull_requests_list(
200 repo_name=self.db_repo_name, source=source,
200 repo_name=self.db_repo_name, source=source,
201 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
201 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
202
202
203 return data
203 return data
204
204
205 def _is_diff_cache_enabled(self, target_repo):
205 def _is_diff_cache_enabled(self, target_repo):
206 caching_enabled = self._get_general_setting(
206 caching_enabled = self._get_general_setting(
207 target_repo, 'rhodecode_diff_cache')
207 target_repo, 'rhodecode_diff_cache')
208 log.debug('Diff caching enabled: %s', caching_enabled)
208 log.debug('Diff caching enabled: %s', caching_enabled)
209 return caching_enabled
209 return caching_enabled
210
210
211 def _get_diffset(self, source_repo_name, source_repo,
211 def _get_diffset(self, source_repo_name, source_repo,
212 source_ref_id, target_ref_id,
212 source_ref_id, target_ref_id,
213 target_commit, source_commit, diff_limit, file_limit,
213 target_commit, source_commit, diff_limit, file_limit,
214 fulldiff):
214 fulldiff):
215
215
216 vcs_diff = PullRequestModel().get_diff(
216 vcs_diff = PullRequestModel().get_diff(
217 source_repo, source_ref_id, target_ref_id)
217 source_repo, source_ref_id, target_ref_id)
218
218
219 diff_processor = diffs.DiffProcessor(
219 diff_processor = diffs.DiffProcessor(
220 vcs_diff, format='newdiff', diff_limit=diff_limit,
220 vcs_diff, format='newdiff', diff_limit=diff_limit,
221 file_limit=file_limit, show_full_diff=fulldiff)
221 file_limit=file_limit, show_full_diff=fulldiff)
222
222
223 _parsed = diff_processor.prepare()
223 _parsed = diff_processor.prepare()
224
224
225 diffset = codeblocks.DiffSet(
225 diffset = codeblocks.DiffSet(
226 repo_name=self.db_repo_name,
226 repo_name=self.db_repo_name,
227 source_repo_name=source_repo_name,
227 source_repo_name=source_repo_name,
228 source_node_getter=codeblocks.diffset_node_getter(target_commit),
228 source_node_getter=codeblocks.diffset_node_getter(target_commit),
229 target_node_getter=codeblocks.diffset_node_getter(source_commit),
229 target_node_getter=codeblocks.diffset_node_getter(source_commit),
230 )
230 )
231 diffset = self.path_filter.render_patchset_filtered(
231 diffset = self.path_filter.render_patchset_filtered(
232 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
232 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
233
233
234 return diffset
234 return diffset
235
235
236 @LoginRequired()
236 @LoginRequired()
237 @HasRepoPermissionAnyDecorator(
237 @HasRepoPermissionAnyDecorator(
238 'repository.read', 'repository.write', 'repository.admin')
238 'repository.read', 'repository.write', 'repository.admin')
239 @view_config(
239 @view_config(
240 route_name='pullrequest_show', request_method='GET',
240 route_name='pullrequest_show', request_method='GET',
241 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
241 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
242 def pull_request_show(self):
242 def pull_request_show(self):
243 pull_request_id = self.request.matchdict['pull_request_id']
243 pull_request_id = self.request.matchdict['pull_request_id']
244
244
245 c = self.load_default_context()
245 c = self.load_default_context()
246
246
247 version = self.request.GET.get('version')
247 version = self.request.GET.get('version')
248 from_version = self.request.GET.get('from_version') or version
248 from_version = self.request.GET.get('from_version') or version
249 merge_checks = self.request.GET.get('merge_checks')
249 merge_checks = self.request.GET.get('merge_checks')
250 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
250 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
251
251
252 (pull_request_latest,
252 (pull_request_latest,
253 pull_request_at_ver,
253 pull_request_at_ver,
254 pull_request_display_obj,
254 pull_request_display_obj,
255 at_version) = PullRequestModel().get_pr_version(
255 at_version) = PullRequestModel().get_pr_version(
256 pull_request_id, version=version)
256 pull_request_id, version=version)
257 pr_closed = pull_request_latest.is_closed()
257 pr_closed = pull_request_latest.is_closed()
258
258
259 if pr_closed and (version or from_version):
259 if pr_closed and (version or from_version):
260 # not allow to browse versions
260 # not allow to browse versions
261 raise HTTPFound(h.route_path(
261 raise HTTPFound(h.route_path(
262 'pullrequest_show', repo_name=self.db_repo_name,
262 'pullrequest_show', repo_name=self.db_repo_name,
263 pull_request_id=pull_request_id))
263 pull_request_id=pull_request_id))
264
264
265 versions = pull_request_display_obj.versions()
265 versions = pull_request_display_obj.versions()
266
266
267 c.at_version = at_version
267 c.at_version = at_version
268 c.at_version_num = (at_version
268 c.at_version_num = (at_version
269 if at_version and at_version != 'latest'
269 if at_version and at_version != 'latest'
270 else None)
270 else None)
271 c.at_version_pos = ChangesetComment.get_index_from_version(
271 c.at_version_pos = ChangesetComment.get_index_from_version(
272 c.at_version_num, versions)
272 c.at_version_num, versions)
273
273
274 (prev_pull_request_latest,
274 (prev_pull_request_latest,
275 prev_pull_request_at_ver,
275 prev_pull_request_at_ver,
276 prev_pull_request_display_obj,
276 prev_pull_request_display_obj,
277 prev_at_version) = PullRequestModel().get_pr_version(
277 prev_at_version) = PullRequestModel().get_pr_version(
278 pull_request_id, version=from_version)
278 pull_request_id, version=from_version)
279
279
280 c.from_version = prev_at_version
280 c.from_version = prev_at_version
281 c.from_version_num = (prev_at_version
281 c.from_version_num = (prev_at_version
282 if prev_at_version and prev_at_version != 'latest'
282 if prev_at_version and prev_at_version != 'latest'
283 else None)
283 else None)
284 c.from_version_pos = ChangesetComment.get_index_from_version(
284 c.from_version_pos = ChangesetComment.get_index_from_version(
285 c.from_version_num, versions)
285 c.from_version_num, versions)
286
286
287 # define if we're in COMPARE mode or VIEW at version mode
287 # define if we're in COMPARE mode or VIEW at version mode
288 compare = at_version != prev_at_version
288 compare = at_version != prev_at_version
289
289
290 # pull_requests repo_name we opened it against
290 # pull_requests repo_name we opened it against
291 # ie. target_repo must match
291 # ie. target_repo must match
292 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
292 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
293 raise HTTPNotFound()
293 raise HTTPNotFound()
294
294
295 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
295 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
296 pull_request_at_ver)
296 pull_request_at_ver)
297
297
298 c.pull_request = pull_request_display_obj
298 c.pull_request = pull_request_display_obj
299 c.pull_request_latest = pull_request_latest
299 c.pull_request_latest = pull_request_latest
300
300
301 if compare or (at_version and not at_version == 'latest'):
301 if compare or (at_version and not at_version == 'latest'):
302 c.allowed_to_change_status = False
302 c.allowed_to_change_status = False
303 c.allowed_to_update = False
303 c.allowed_to_update = False
304 c.allowed_to_merge = False
304 c.allowed_to_merge = False
305 c.allowed_to_delete = False
305 c.allowed_to_delete = False
306 c.allowed_to_comment = False
306 c.allowed_to_comment = False
307 c.allowed_to_close = False
307 c.allowed_to_close = False
308 else:
308 else:
309 can_change_status = PullRequestModel().check_user_change_status(
309 can_change_status = PullRequestModel().check_user_change_status(
310 pull_request_at_ver, self._rhodecode_user)
310 pull_request_at_ver, self._rhodecode_user)
311 c.allowed_to_change_status = can_change_status and not pr_closed
311 c.allowed_to_change_status = can_change_status and not pr_closed
312
312
313 c.allowed_to_update = PullRequestModel().check_user_update(
313 c.allowed_to_update = PullRequestModel().check_user_update(
314 pull_request_latest, self._rhodecode_user) and not pr_closed
314 pull_request_latest, self._rhodecode_user) and not pr_closed
315 c.allowed_to_merge = PullRequestModel().check_user_merge(
315 c.allowed_to_merge = PullRequestModel().check_user_merge(
316 pull_request_latest, self._rhodecode_user) and not pr_closed
316 pull_request_latest, self._rhodecode_user) and not pr_closed
317 c.allowed_to_delete = PullRequestModel().check_user_delete(
317 c.allowed_to_delete = PullRequestModel().check_user_delete(
318 pull_request_latest, self._rhodecode_user) and not pr_closed
318 pull_request_latest, self._rhodecode_user) and not pr_closed
319 c.allowed_to_comment = not pr_closed
319 c.allowed_to_comment = not pr_closed
320 c.allowed_to_close = c.allowed_to_merge and not pr_closed
320 c.allowed_to_close = c.allowed_to_merge and not pr_closed
321
321
322 c.forbid_adding_reviewers = False
322 c.forbid_adding_reviewers = False
323 c.forbid_author_to_review = False
323 c.forbid_author_to_review = False
324 c.forbid_commit_author_to_review = False
324 c.forbid_commit_author_to_review = False
325
325
326 if pull_request_latest.reviewer_data and \
326 if pull_request_latest.reviewer_data and \
327 'rules' in pull_request_latest.reviewer_data:
327 'rules' in pull_request_latest.reviewer_data:
328 rules = pull_request_latest.reviewer_data['rules'] or {}
328 rules = pull_request_latest.reviewer_data['rules'] or {}
329 try:
329 try:
330 c.forbid_adding_reviewers = rules.get(
330 c.forbid_adding_reviewers = rules.get(
331 'forbid_adding_reviewers')
331 'forbid_adding_reviewers')
332 c.forbid_author_to_review = rules.get(
332 c.forbid_author_to_review = rules.get(
333 'forbid_author_to_review')
333 'forbid_author_to_review')
334 c.forbid_commit_author_to_review = rules.get(
334 c.forbid_commit_author_to_review = rules.get(
335 'forbid_commit_author_to_review')
335 'forbid_commit_author_to_review')
336 except Exception:
336 except Exception:
337 pass
337 pass
338
338
339 # check merge capabilities
339 # check merge capabilities
340 _merge_check = MergeCheck.validate(
340 _merge_check = MergeCheck.validate(
341 pull_request_latest, user=self._rhodecode_user,
341 pull_request_latest, user=self._rhodecode_user,
342 translator=self.request.translate)
342 translator=self.request.translate)
343 c.pr_merge_errors = _merge_check.error_details
343 c.pr_merge_errors = _merge_check.error_details
344 c.pr_merge_possible = not _merge_check.failed
344 c.pr_merge_possible = not _merge_check.failed
345 c.pr_merge_message = _merge_check.merge_msg
345 c.pr_merge_message = _merge_check.merge_msg
346
346
347 c.pr_merge_info = MergeCheck.get_merge_conditions(
347 c.pr_merge_info = MergeCheck.get_merge_conditions(
348 pull_request_latest, translator=self.request.translate)
348 pull_request_latest, translator=self.request.translate)
349
349
350 c.pull_request_review_status = _merge_check.review_status
350 c.pull_request_review_status = _merge_check.review_status
351 if merge_checks:
351 if merge_checks:
352 self.request.override_renderer = \
352 self.request.override_renderer = \
353 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
353 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
354 return self._get_template_context(c)
354 return self._get_template_context(c)
355
355
356 comments_model = CommentsModel()
356 comments_model = CommentsModel()
357
357
358 # reviewers and statuses
358 # reviewers and statuses
359 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
359 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
360 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
360 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
361
361
362 # GENERAL COMMENTS with versions #
362 # GENERAL COMMENTS with versions #
363 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
363 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
364 q = q.order_by(ChangesetComment.comment_id.asc())
364 q = q.order_by(ChangesetComment.comment_id.asc())
365 general_comments = q
365 general_comments = q
366
366
367 # pick comments we want to render at current version
367 # pick comments we want to render at current version
368 c.comment_versions = comments_model.aggregate_comments(
368 c.comment_versions = comments_model.aggregate_comments(
369 general_comments, versions, c.at_version_num)
369 general_comments, versions, c.at_version_num)
370 c.comments = c.comment_versions[c.at_version_num]['until']
370 c.comments = c.comment_versions[c.at_version_num]['until']
371
371
372 # INLINE COMMENTS with versions #
372 # INLINE COMMENTS with versions #
373 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
373 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
374 q = q.order_by(ChangesetComment.comment_id.asc())
374 q = q.order_by(ChangesetComment.comment_id.asc())
375 inline_comments = q
375 inline_comments = q
376
376
377 c.inline_versions = comments_model.aggregate_comments(
377 c.inline_versions = comments_model.aggregate_comments(
378 inline_comments, versions, c.at_version_num, inline=True)
378 inline_comments, versions, c.at_version_num, inline=True)
379
379
380 # inject latest version
380 # inject latest version
381 latest_ver = PullRequest.get_pr_display_object(
381 latest_ver = PullRequest.get_pr_display_object(
382 pull_request_latest, pull_request_latest)
382 pull_request_latest, pull_request_latest)
383
383
384 c.versions = versions + [latest_ver]
384 c.versions = versions + [latest_ver]
385
385
386 # if we use version, then do not show later comments
386 # if we use version, then do not show later comments
387 # than current version
387 # than current version
388 display_inline_comments = collections.defaultdict(
388 display_inline_comments = collections.defaultdict(
389 lambda: collections.defaultdict(list))
389 lambda: collections.defaultdict(list))
390 for co in inline_comments:
390 for co in inline_comments:
391 if c.at_version_num:
391 if c.at_version_num:
392 # pick comments that are at least UPTO given version, so we
392 # pick comments that are at least UPTO given version, so we
393 # don't render comments for higher version
393 # don't render comments for higher version
394 should_render = co.pull_request_version_id and \
394 should_render = co.pull_request_version_id and \
395 co.pull_request_version_id <= c.at_version_num
395 co.pull_request_version_id <= c.at_version_num
396 else:
396 else:
397 # showing all, for 'latest'
397 # showing all, for 'latest'
398 should_render = True
398 should_render = True
399
399
400 if should_render:
400 if should_render:
401 display_inline_comments[co.f_path][co.line_no].append(co)
401 display_inline_comments[co.f_path][co.line_no].append(co)
402
402
403 # load diff data into template context, if we use compare mode then
403 # load diff data into template context, if we use compare mode then
404 # diff is calculated based on changes between versions of PR
404 # diff is calculated based on changes between versions of PR
405
405
406 source_repo = pull_request_at_ver.source_repo
406 source_repo = pull_request_at_ver.source_repo
407 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
407 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
408
408
409 target_repo = pull_request_at_ver.target_repo
409 target_repo = pull_request_at_ver.target_repo
410 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
410 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
411
411
412 if compare:
412 if compare:
413 # in compare switch the diff base to latest commit from prev version
413 # in compare switch the diff base to latest commit from prev version
414 target_ref_id = prev_pull_request_display_obj.revisions[0]
414 target_ref_id = prev_pull_request_display_obj.revisions[0]
415
415
416 # despite opening commits for bookmarks/branches/tags, we always
416 # despite opening commits for bookmarks/branches/tags, we always
417 # convert this to rev to prevent changes after bookmark or branch change
417 # convert this to rev to prevent changes after bookmark or branch change
418 c.source_ref_type = 'rev'
418 c.source_ref_type = 'rev'
419 c.source_ref = source_ref_id
419 c.source_ref = source_ref_id
420
420
421 c.target_ref_type = 'rev'
421 c.target_ref_type = 'rev'
422 c.target_ref = target_ref_id
422 c.target_ref = target_ref_id
423
423
424 c.source_repo = source_repo
424 c.source_repo = source_repo
425 c.target_repo = target_repo
425 c.target_repo = target_repo
426
426
427 c.commit_ranges = []
427 c.commit_ranges = []
428 source_commit = EmptyCommit()
428 source_commit = EmptyCommit()
429 target_commit = EmptyCommit()
429 target_commit = EmptyCommit()
430 c.missing_requirements = False
430 c.missing_requirements = False
431
431
432 source_scm = source_repo.scm_instance()
432 source_scm = source_repo.scm_instance()
433 target_scm = target_repo.scm_instance()
433 target_scm = target_repo.scm_instance()
434
434
435 # try first shadow repo, fallback to regular repo
435 # try first shadow repo, fallback to regular repo
436 try:
436 try:
437 commits_source_repo = pull_request_latest.get_shadow_repo()
437 commits_source_repo = pull_request_latest.get_shadow_repo()
438 except Exception:
438 except Exception:
439 log.debug('Failed to get shadow repo', exc_info=True)
439 log.debug('Failed to get shadow repo', exc_info=True)
440 commits_source_repo = source_scm
440 commits_source_repo = source_scm
441
441
442 c.commits_source_repo = commits_source_repo
442 c.commits_source_repo = commits_source_repo
443 c.ancestor = None # set it to None, to hide it from PR view
443 c.ancestor = None # set it to None, to hide it from PR view
444
444
445 # empty version means latest, so we keep this to prevent
445 # empty version means latest, so we keep this to prevent
446 # double caching
446 # double caching
447 version_normalized = version or 'latest'
447 version_normalized = version or 'latest'
448 from_version_normalized = from_version or 'latest'
448 from_version_normalized = from_version or 'latest'
449
449
450 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
450 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
451 target_repo)
451 target_repo)
452 cache_file_path = diff_cache_exist(
452 cache_file_path = diff_cache_exist(
453 cache_path, 'pull_request', pull_request_id, version_normalized,
453 cache_path, 'pull_request', pull_request_id, version_normalized,
454 from_version_normalized, source_ref_id, target_ref_id, c.fulldiff)
454 from_version_normalized, source_ref_id, target_ref_id, c.fulldiff)
455
455
456 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
456 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
457 force_recache = str2bool(self.request.GET.get('force_recache'))
457 force_recache = str2bool(self.request.GET.get('force_recache'))
458
458
459 cached_diff = None
459 cached_diff = None
460 if caching_enabled:
460 if caching_enabled:
461 cached_diff = load_cached_diff(cache_file_path)
461 cached_diff = load_cached_diff(cache_file_path)
462
462
463 has_proper_commit_cache = (
463 has_proper_commit_cache = (
464 cached_diff and cached_diff.get('commits')
464 cached_diff and cached_diff.get('commits')
465 and len(cached_diff.get('commits', [])) == 5
465 and len(cached_diff.get('commits', [])) == 5
466 and cached_diff.get('commits')[0]
466 and cached_diff.get('commits')[0]
467 and cached_diff.get('commits')[3])
467 and cached_diff.get('commits')[3])
468 if not force_recache and has_proper_commit_cache:
468 if not force_recache and has_proper_commit_cache:
469 diff_commit_cache = \
469 diff_commit_cache = \
470 (ancestor_commit, commit_cache, missing_requirements,
470 (ancestor_commit, commit_cache, missing_requirements,
471 source_commit, target_commit) = cached_diff['commits']
471 source_commit, target_commit) = cached_diff['commits']
472 else:
472 else:
473 diff_commit_cache = \
473 diff_commit_cache = \
474 (ancestor_commit, commit_cache, missing_requirements,
474 (ancestor_commit, commit_cache, missing_requirements,
475 source_commit, target_commit) = self.get_commits(
475 source_commit, target_commit) = self.get_commits(
476 commits_source_repo,
476 commits_source_repo,
477 pull_request_at_ver,
477 pull_request_at_ver,
478 source_commit,
478 source_commit,
479 source_ref_id,
479 source_ref_id,
480 source_scm,
480 source_scm,
481 target_commit,
481 target_commit,
482 target_ref_id,
482 target_ref_id,
483 target_scm)
483 target_scm)
484
484
485 # register our commit range
485 # register our commit range
486 for comm in commit_cache.values():
486 for comm in commit_cache.values():
487 c.commit_ranges.append(comm)
487 c.commit_ranges.append(comm)
488
488
489 c.missing_requirements = missing_requirements
489 c.missing_requirements = missing_requirements
490 c.ancestor_commit = ancestor_commit
490 c.ancestor_commit = ancestor_commit
491 c.statuses = source_repo.statuses(
491 c.statuses = source_repo.statuses(
492 [x.raw_id for x in c.commit_ranges])
492 [x.raw_id for x in c.commit_ranges])
493
493
494 # auto collapse if we have more than limit
494 # auto collapse if we have more than limit
495 collapse_limit = diffs.DiffProcessor._collapse_commits_over
495 collapse_limit = diffs.DiffProcessor._collapse_commits_over
496 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
496 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
497 c.compare_mode = compare
497 c.compare_mode = compare
498
498
499 # diff_limit is the old behavior, will cut off the whole diff
499 # diff_limit is the old behavior, will cut off the whole diff
500 # if the limit is applied otherwise will just hide the
500 # if the limit is applied otherwise will just hide the
501 # big files from the front-end
501 # big files from the front-end
502 diff_limit = c.visual.cut_off_limit_diff
502 diff_limit = c.visual.cut_off_limit_diff
503 file_limit = c.visual.cut_off_limit_file
503 file_limit = c.visual.cut_off_limit_file
504
504
505 c.missing_commits = False
505 c.missing_commits = False
506 if (c.missing_requirements
506 if (c.missing_requirements
507 or isinstance(source_commit, EmptyCommit)
507 or isinstance(source_commit, EmptyCommit)
508 or source_commit == target_commit):
508 or source_commit == target_commit):
509
509
510 c.missing_commits = True
510 c.missing_commits = True
511 else:
511 else:
512 c.inline_comments = display_inline_comments
512 c.inline_comments = display_inline_comments
513
513
514 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
514 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
515 if not force_recache and has_proper_diff_cache:
515 if not force_recache and has_proper_diff_cache:
516 c.diffset = cached_diff['diff']
516 c.diffset = cached_diff['diff']
517 (ancestor_commit, commit_cache, missing_requirements,
517 (ancestor_commit, commit_cache, missing_requirements,
518 source_commit, target_commit) = cached_diff['commits']
518 source_commit, target_commit) = cached_diff['commits']
519 else:
519 else:
520 c.diffset = self._get_diffset(
520 c.diffset = self._get_diffset(
521 c.source_repo.repo_name, commits_source_repo,
521 c.source_repo.repo_name, commits_source_repo,
522 source_ref_id, target_ref_id,
522 source_ref_id, target_ref_id,
523 target_commit, source_commit,
523 target_commit, source_commit,
524 diff_limit, file_limit, c.fulldiff)
524 diff_limit, file_limit, c.fulldiff)
525
525
526 # save cached diff
526 # save cached diff
527 if caching_enabled:
527 if caching_enabled:
528 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
528 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
529
529
530 c.limited_diff = c.diffset.limited_diff
530 c.limited_diff = c.diffset.limited_diff
531
531
532 # calculate removed files that are bound to comments
532 # calculate removed files that are bound to comments
533 comment_deleted_files = [
533 comment_deleted_files = [
534 fname for fname in display_inline_comments
534 fname for fname in display_inline_comments
535 if fname not in c.diffset.file_stats]
535 if fname not in c.diffset.file_stats]
536
536
537 c.deleted_files_comments = collections.defaultdict(dict)
537 c.deleted_files_comments = collections.defaultdict(dict)
538 for fname, per_line_comments in display_inline_comments.items():
538 for fname, per_line_comments in display_inline_comments.items():
539 if fname in comment_deleted_files:
539 if fname in comment_deleted_files:
540 c.deleted_files_comments[fname]['stats'] = 0
540 c.deleted_files_comments[fname]['stats'] = 0
541 c.deleted_files_comments[fname]['comments'] = list()
541 c.deleted_files_comments[fname]['comments'] = list()
542 for lno, comments in per_line_comments.items():
542 for lno, comments in per_line_comments.items():
543 c.deleted_files_comments[fname]['comments'].extend(
543 c.deleted_files_comments[fname]['comments'].extend(
544 comments)
544 comments)
545
545
546 # this is a hack to properly display links, when creating PR, the
546 # this is a hack to properly display links, when creating PR, the
547 # compare view and others uses different notation, and
547 # compare view and others uses different notation, and
548 # compare_commits.mako renders links based on the target_repo.
548 # compare_commits.mako renders links based on the target_repo.
549 # We need to swap that here to generate it properly on the html side
549 # We need to swap that here to generate it properly on the html side
550 c.target_repo = c.source_repo
550 c.target_repo = c.source_repo
551
551
552 c.commit_statuses = ChangesetStatus.STATUSES
552 c.commit_statuses = ChangesetStatus.STATUSES
553
553
554 c.show_version_changes = not pr_closed
554 c.show_version_changes = not pr_closed
555 if c.show_version_changes:
555 if c.show_version_changes:
556 cur_obj = pull_request_at_ver
556 cur_obj = pull_request_at_ver
557 prev_obj = prev_pull_request_at_ver
557 prev_obj = prev_pull_request_at_ver
558
558
559 old_commit_ids = prev_obj.revisions
559 old_commit_ids = prev_obj.revisions
560 new_commit_ids = cur_obj.revisions
560 new_commit_ids = cur_obj.revisions
561 commit_changes = PullRequestModel()._calculate_commit_id_changes(
561 commit_changes = PullRequestModel()._calculate_commit_id_changes(
562 old_commit_ids, new_commit_ids)
562 old_commit_ids, new_commit_ids)
563 c.commit_changes_summary = commit_changes
563 c.commit_changes_summary = commit_changes
564
564
565 # calculate the diff for commits between versions
565 # calculate the diff for commits between versions
566 c.commit_changes = []
566 c.commit_changes = []
567 mark = lambda cs, fw: list(
567 mark = lambda cs, fw: list(
568 h.itertools.izip_longest([], cs, fillvalue=fw))
568 h.itertools.izip_longest([], cs, fillvalue=fw))
569 for c_type, raw_id in mark(commit_changes.added, 'a') \
569 for c_type, raw_id in mark(commit_changes.added, 'a') \
570 + mark(commit_changes.removed, 'r') \
570 + mark(commit_changes.removed, 'r') \
571 + mark(commit_changes.common, 'c'):
571 + mark(commit_changes.common, 'c'):
572
572
573 if raw_id in commit_cache:
573 if raw_id in commit_cache:
574 commit = commit_cache[raw_id]
574 commit = commit_cache[raw_id]
575 else:
575 else:
576 try:
576 try:
577 commit = commits_source_repo.get_commit(raw_id)
577 commit = commits_source_repo.get_commit(raw_id)
578 except CommitDoesNotExistError:
578 except CommitDoesNotExistError:
579 # in case we fail extracting still use "dummy" commit
579 # in case we fail extracting still use "dummy" commit
580 # for display in commit diff
580 # for display in commit diff
581 commit = h.AttributeDict(
581 commit = h.AttributeDict(
582 {'raw_id': raw_id,
582 {'raw_id': raw_id,
583 'message': 'EMPTY or MISSING COMMIT'})
583 'message': 'EMPTY or MISSING COMMIT'})
584 c.commit_changes.append([c_type, commit])
584 c.commit_changes.append([c_type, commit])
585
585
586 # current user review statuses for each version
586 # current user review statuses for each version
587 c.review_versions = {}
587 c.review_versions = {}
588 if self._rhodecode_user.user_id in allowed_reviewers:
588 if self._rhodecode_user.user_id in allowed_reviewers:
589 for co in general_comments:
589 for co in general_comments:
590 if co.author.user_id == self._rhodecode_user.user_id:
590 if co.author.user_id == self._rhodecode_user.user_id:
591 status = co.status_change
591 status = co.status_change
592 if status:
592 if status:
593 _ver_pr = status[0].comment.pull_request_version_id
593 _ver_pr = status[0].comment.pull_request_version_id
594 c.review_versions[_ver_pr] = status[0]
594 c.review_versions[_ver_pr] = status[0]
595
595
596 return self._get_template_context(c)
596 return self._get_template_context(c)
597
597
598 def get_commits(
598 def get_commits(
599 self, commits_source_repo, pull_request_at_ver, source_commit,
599 self, commits_source_repo, pull_request_at_ver, source_commit,
600 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
600 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
601 commit_cache = collections.OrderedDict()
601 commit_cache = collections.OrderedDict()
602 missing_requirements = False
602 missing_requirements = False
603 try:
603 try:
604 pre_load = ["author", "branch", "date", "message"]
604 pre_load = ["author", "branch", "date", "message"]
605 show_revs = pull_request_at_ver.revisions
605 show_revs = pull_request_at_ver.revisions
606 for rev in show_revs:
606 for rev in show_revs:
607 comm = commits_source_repo.get_commit(
607 comm = commits_source_repo.get_commit(
608 commit_id=rev, pre_load=pre_load)
608 commit_id=rev, pre_load=pre_load)
609 commit_cache[comm.raw_id] = comm
609 commit_cache[comm.raw_id] = comm
610
610
611 # Order here matters, we first need to get target, and then
611 # Order here matters, we first need to get target, and then
612 # the source
612 # the source
613 target_commit = commits_source_repo.get_commit(
613 target_commit = commits_source_repo.get_commit(
614 commit_id=safe_str(target_ref_id))
614 commit_id=safe_str(target_ref_id))
615
615
616 source_commit = commits_source_repo.get_commit(
616 source_commit = commits_source_repo.get_commit(
617 commit_id=safe_str(source_ref_id))
617 commit_id=safe_str(source_ref_id))
618 except CommitDoesNotExistError:
618 except CommitDoesNotExistError:
619 log.warning(
619 log.warning(
620 'Failed to get commit from `{}` repo'.format(
620 'Failed to get commit from `{}` repo'.format(
621 commits_source_repo), exc_info=True)
621 commits_source_repo), exc_info=True)
622 except RepositoryRequirementError:
622 except RepositoryRequirementError:
623 log.warning(
623 log.warning(
624 'Failed to get all required data from repo', exc_info=True)
624 'Failed to get all required data from repo', exc_info=True)
625 missing_requirements = True
625 missing_requirements = True
626 ancestor_commit = None
626 ancestor_commit = None
627 try:
627 try:
628 ancestor_id = source_scm.get_common_ancestor(
628 ancestor_id = source_scm.get_common_ancestor(
629 source_commit.raw_id, target_commit.raw_id, target_scm)
629 source_commit.raw_id, target_commit.raw_id, target_scm)
630 ancestor_commit = source_scm.get_commit(ancestor_id)
630 ancestor_commit = source_scm.get_commit(ancestor_id)
631 except Exception:
631 except Exception:
632 ancestor_commit = None
632 ancestor_commit = None
633 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
633 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
634
634
635 def assure_not_empty_repo(self):
635 def assure_not_empty_repo(self):
636 _ = self.request.translate
636 _ = self.request.translate
637
637
638 try:
638 try:
639 self.db_repo.scm_instance().get_commit()
639 self.db_repo.scm_instance().get_commit()
640 except EmptyRepositoryError:
640 except EmptyRepositoryError:
641 h.flash(h.literal(_('There are no commits yet')),
641 h.flash(h.literal(_('There are no commits yet')),
642 category='warning')
642 category='warning')
643 raise HTTPFound(
643 raise HTTPFound(
644 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
644 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
645
645
646 @LoginRequired()
646 @LoginRequired()
647 @NotAnonymous()
647 @NotAnonymous()
648 @HasRepoPermissionAnyDecorator(
648 @HasRepoPermissionAnyDecorator(
649 'repository.read', 'repository.write', 'repository.admin')
649 'repository.read', 'repository.write', 'repository.admin')
650 @view_config(
650 @view_config(
651 route_name='pullrequest_new', request_method='GET',
651 route_name='pullrequest_new', request_method='GET',
652 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
652 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
653 def pull_request_new(self):
653 def pull_request_new(self):
654 _ = self.request.translate
654 _ = self.request.translate
655 c = self.load_default_context()
655 c = self.load_default_context()
656
656
657 self.assure_not_empty_repo()
657 self.assure_not_empty_repo()
658 source_repo = self.db_repo
658 source_repo = self.db_repo
659
659
660 commit_id = self.request.GET.get('commit')
660 commit_id = self.request.GET.get('commit')
661 branch_ref = self.request.GET.get('branch')
661 branch_ref = self.request.GET.get('branch')
662 bookmark_ref = self.request.GET.get('bookmark')
662 bookmark_ref = self.request.GET.get('bookmark')
663
663
664 try:
664 try:
665 source_repo_data = PullRequestModel().generate_repo_data(
665 source_repo_data = PullRequestModel().generate_repo_data(
666 source_repo, commit_id=commit_id,
666 source_repo, commit_id=commit_id,
667 branch=branch_ref, bookmark=bookmark_ref,
667 branch=branch_ref, bookmark=bookmark_ref,
668 translator=self.request.translate)
668 translator=self.request.translate)
669 except CommitDoesNotExistError as e:
669 except CommitDoesNotExistError as e:
670 log.exception(e)
670 log.exception(e)
671 h.flash(_('Commit does not exist'), 'error')
671 h.flash(_('Commit does not exist'), 'error')
672 raise HTTPFound(
672 raise HTTPFound(
673 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
673 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
674
674
675 default_target_repo = source_repo
675 default_target_repo = source_repo
676
676
677 if source_repo.parent:
677 if source_repo.parent:
678 parent_vcs_obj = source_repo.parent.scm_instance()
678 parent_vcs_obj = source_repo.parent.scm_instance()
679 if parent_vcs_obj and not parent_vcs_obj.is_empty():
679 if parent_vcs_obj and not parent_vcs_obj.is_empty():
680 # change default if we have a parent repo
680 # change default if we have a parent repo
681 default_target_repo = source_repo.parent
681 default_target_repo = source_repo.parent
682
682
683 target_repo_data = PullRequestModel().generate_repo_data(
683 target_repo_data = PullRequestModel().generate_repo_data(
684 default_target_repo, translator=self.request.translate)
684 default_target_repo, translator=self.request.translate)
685
685
686 selected_source_ref = source_repo_data['refs']['selected_ref']
686 selected_source_ref = source_repo_data['refs']['selected_ref']
687 title_source_ref = ''
687 title_source_ref = ''
688 if selected_source_ref:
688 if selected_source_ref:
689 title_source_ref = selected_source_ref.split(':', 2)[1]
689 title_source_ref = selected_source_ref.split(':', 2)[1]
690 c.default_title = PullRequestModel().generate_pullrequest_title(
690 c.default_title = PullRequestModel().generate_pullrequest_title(
691 source=source_repo.repo_name,
691 source=source_repo.repo_name,
692 source_ref=title_source_ref,
692 source_ref=title_source_ref,
693 target=default_target_repo.repo_name
693 target=default_target_repo.repo_name
694 )
694 )
695
695
696 c.default_repo_data = {
696 c.default_repo_data = {
697 'source_repo_name': source_repo.repo_name,
697 'source_repo_name': source_repo.repo_name,
698 'source_refs_json': json.dumps(source_repo_data),
698 'source_refs_json': json.dumps(source_repo_data),
699 'target_repo_name': default_target_repo.repo_name,
699 'target_repo_name': default_target_repo.repo_name,
700 'target_refs_json': json.dumps(target_repo_data),
700 'target_refs_json': json.dumps(target_repo_data),
701 }
701 }
702 c.default_source_ref = selected_source_ref
702 c.default_source_ref = selected_source_ref
703
703
704 return self._get_template_context(c)
704 return self._get_template_context(c)
705
705
706 @LoginRequired()
706 @LoginRequired()
707 @NotAnonymous()
707 @NotAnonymous()
708 @HasRepoPermissionAnyDecorator(
708 @HasRepoPermissionAnyDecorator(
709 'repository.read', 'repository.write', 'repository.admin')
709 'repository.read', 'repository.write', 'repository.admin')
710 @view_config(
710 @view_config(
711 route_name='pullrequest_repo_refs', request_method='GET',
711 route_name='pullrequest_repo_refs', request_method='GET',
712 renderer='json_ext', xhr=True)
712 renderer='json_ext', xhr=True)
713 def pull_request_repo_refs(self):
713 def pull_request_repo_refs(self):
714 self.load_default_context()
714 self.load_default_context()
715 target_repo_name = self.request.matchdict['target_repo_name']
715 target_repo_name = self.request.matchdict['target_repo_name']
716 repo = Repository.get_by_repo_name(target_repo_name)
716 repo = Repository.get_by_repo_name(target_repo_name)
717 if not repo:
717 if not repo:
718 raise HTTPNotFound()
718 raise HTTPNotFound()
719
719
720 target_perm = HasRepoPermissionAny(
720 target_perm = HasRepoPermissionAny(
721 'repository.read', 'repository.write', 'repository.admin')(
721 'repository.read', 'repository.write', 'repository.admin')(
722 target_repo_name)
722 target_repo_name)
723 if not target_perm:
723 if not target_perm:
724 raise HTTPNotFound()
724 raise HTTPNotFound()
725
725
726 return PullRequestModel().generate_repo_data(
726 return PullRequestModel().generate_repo_data(
727 repo, translator=self.request.translate)
727 repo, translator=self.request.translate)
728
728
729 @LoginRequired()
729 @LoginRequired()
730 @NotAnonymous()
730 @NotAnonymous()
731 @HasRepoPermissionAnyDecorator(
731 @HasRepoPermissionAnyDecorator(
732 'repository.read', 'repository.write', 'repository.admin')
732 'repository.read', 'repository.write', 'repository.admin')
733 @view_config(
733 @view_config(
734 route_name='pullrequest_repo_destinations', request_method='GET',
734 route_name='pullrequest_repo_destinations', request_method='GET',
735 renderer='json_ext', xhr=True)
735 renderer='json_ext', xhr=True)
736 def pull_request_repo_destinations(self):
736 def pull_request_repo_destinations(self):
737 _ = self.request.translate
737 _ = self.request.translate
738 filter_query = self.request.GET.get('query')
738 filter_query = self.request.GET.get('query')
739
739
740 query = Repository.query() \
740 query = Repository.query() \
741 .order_by(func.length(Repository.repo_name)) \
741 .order_by(func.length(Repository.repo_name)) \
742 .filter(
742 .filter(
743 or_(Repository.repo_name == self.db_repo.repo_name,
743 or_(Repository.repo_name == self.db_repo.repo_name,
744 Repository.fork_id == self.db_repo.repo_id))
744 Repository.fork_id == self.db_repo.repo_id))
745
745
746 if filter_query:
746 if filter_query:
747 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
747 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
748 query = query.filter(
748 query = query.filter(
749 Repository.repo_name.ilike(ilike_expression))
749 Repository.repo_name.ilike(ilike_expression))
750
750
751 add_parent = False
751 add_parent = False
752 if self.db_repo.parent:
752 if self.db_repo.parent:
753 if filter_query in self.db_repo.parent.repo_name:
753 if filter_query in self.db_repo.parent.repo_name:
754 parent_vcs_obj = self.db_repo.parent.scm_instance()
754 parent_vcs_obj = self.db_repo.parent.scm_instance()
755 if parent_vcs_obj and not parent_vcs_obj.is_empty():
755 if parent_vcs_obj and not parent_vcs_obj.is_empty():
756 add_parent = True
756 add_parent = True
757
757
758 limit = 20 - 1 if add_parent else 20
758 limit = 20 - 1 if add_parent else 20
759 all_repos = query.limit(limit).all()
759 all_repos = query.limit(limit).all()
760 if add_parent:
760 if add_parent:
761 all_repos += [self.db_repo.parent]
761 all_repos += [self.db_repo.parent]
762
762
763 repos = []
763 repos = []
764 for obj in ScmModel().get_repos(all_repos):
764 for obj in ScmModel().get_repos(all_repos):
765 repos.append({
765 repos.append({
766 'id': obj['name'],
766 'id': obj['name'],
767 'text': obj['name'],
767 'text': obj['name'],
768 'type': 'repo',
768 'type': 'repo',
769 'obj': obj['dbrepo']
769 'repo_id': obj['dbrepo']['repo_id'],
770 'repo_type': obj['dbrepo']['repo_type'],
771 'private': obj['dbrepo']['private'],
772
770 })
773 })
771
774
772 data = {
775 data = {
773 'more': False,
776 'more': False,
774 'results': [{
777 'results': [{
775 'text': _('Repositories'),
778 'text': _('Repositories'),
776 'children': repos
779 'children': repos
777 }] if repos else []
780 }] if repos else []
778 }
781 }
779 return data
782 return data
780
783
781 @LoginRequired()
784 @LoginRequired()
782 @NotAnonymous()
785 @NotAnonymous()
783 @HasRepoPermissionAnyDecorator(
786 @HasRepoPermissionAnyDecorator(
784 'repository.read', 'repository.write', 'repository.admin')
787 'repository.read', 'repository.write', 'repository.admin')
785 @CSRFRequired()
788 @CSRFRequired()
786 @view_config(
789 @view_config(
787 route_name='pullrequest_create', request_method='POST',
790 route_name='pullrequest_create', request_method='POST',
788 renderer=None)
791 renderer=None)
789 def pull_request_create(self):
792 def pull_request_create(self):
790 _ = self.request.translate
793 _ = self.request.translate
791 self.assure_not_empty_repo()
794 self.assure_not_empty_repo()
792 self.load_default_context()
795 self.load_default_context()
793
796
794 controls = peppercorn.parse(self.request.POST.items())
797 controls = peppercorn.parse(self.request.POST.items())
795
798
796 try:
799 try:
797 form = PullRequestForm(
800 form = PullRequestForm(
798 self.request.translate, self.db_repo.repo_id)()
801 self.request.translate, self.db_repo.repo_id)()
799 _form = form.to_python(controls)
802 _form = form.to_python(controls)
800 except formencode.Invalid as errors:
803 except formencode.Invalid as errors:
801 if errors.error_dict.get('revisions'):
804 if errors.error_dict.get('revisions'):
802 msg = 'Revisions: %s' % errors.error_dict['revisions']
805 msg = 'Revisions: %s' % errors.error_dict['revisions']
803 elif errors.error_dict.get('pullrequest_title'):
806 elif errors.error_dict.get('pullrequest_title'):
804 msg = errors.error_dict.get('pullrequest_title')
807 msg = errors.error_dict.get('pullrequest_title')
805 else:
808 else:
806 msg = _('Error creating pull request: {}').format(errors)
809 msg = _('Error creating pull request: {}').format(errors)
807 log.exception(msg)
810 log.exception(msg)
808 h.flash(msg, 'error')
811 h.flash(msg, 'error')
809
812
810 # would rather just go back to form ...
813 # would rather just go back to form ...
811 raise HTTPFound(
814 raise HTTPFound(
812 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
815 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
813
816
814 source_repo = _form['source_repo']
817 source_repo = _form['source_repo']
815 source_ref = _form['source_ref']
818 source_ref = _form['source_ref']
816 target_repo = _form['target_repo']
819 target_repo = _form['target_repo']
817 target_ref = _form['target_ref']
820 target_ref = _form['target_ref']
818 commit_ids = _form['revisions'][::-1]
821 commit_ids = _form['revisions'][::-1]
819
822
820 # find the ancestor for this pr
823 # find the ancestor for this pr
821 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
824 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
822 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
825 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
823
826
824 # re-check permissions again here
827 # re-check permissions again here
825 # source_repo we must have read permissions
828 # source_repo we must have read permissions
826
829
827 source_perm = HasRepoPermissionAny(
830 source_perm = HasRepoPermissionAny(
828 'repository.read',
831 'repository.read',
829 'repository.write', 'repository.admin')(source_db_repo.repo_name)
832 'repository.write', 'repository.admin')(source_db_repo.repo_name)
830 if not source_perm:
833 if not source_perm:
831 msg = _('Not Enough permissions to source repo `{}`.'.format(
834 msg = _('Not Enough permissions to source repo `{}`.'.format(
832 source_db_repo.repo_name))
835 source_db_repo.repo_name))
833 h.flash(msg, category='error')
836 h.flash(msg, category='error')
834 # copy the args back to redirect
837 # copy the args back to redirect
835 org_query = self.request.GET.mixed()
838 org_query = self.request.GET.mixed()
836 raise HTTPFound(
839 raise HTTPFound(
837 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
840 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
838 _query=org_query))
841 _query=org_query))
839
842
840 # target repo we must have read permissions, and also later on
843 # target repo we must have read permissions, and also later on
841 # we want to check branch permissions here
844 # we want to check branch permissions here
842 target_perm = HasRepoPermissionAny(
845 target_perm = HasRepoPermissionAny(
843 'repository.read',
846 'repository.read',
844 'repository.write', 'repository.admin')(target_db_repo.repo_name)
847 'repository.write', 'repository.admin')(target_db_repo.repo_name)
845 if not target_perm:
848 if not target_perm:
846 msg = _('Not Enough permissions to target repo `{}`.'.format(
849 msg = _('Not Enough permissions to target repo `{}`.'.format(
847 target_db_repo.repo_name))
850 target_db_repo.repo_name))
848 h.flash(msg, category='error')
851 h.flash(msg, category='error')
849 # copy the args back to redirect
852 # copy the args back to redirect
850 org_query = self.request.GET.mixed()
853 org_query = self.request.GET.mixed()
851 raise HTTPFound(
854 raise HTTPFound(
852 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
855 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
853 _query=org_query))
856 _query=org_query))
854
857
855 source_scm = source_db_repo.scm_instance()
858 source_scm = source_db_repo.scm_instance()
856 target_scm = target_db_repo.scm_instance()
859 target_scm = target_db_repo.scm_instance()
857
860
858 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
861 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
859 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
862 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
860
863
861 ancestor = source_scm.get_common_ancestor(
864 ancestor = source_scm.get_common_ancestor(
862 source_commit.raw_id, target_commit.raw_id, target_scm)
865 source_commit.raw_id, target_commit.raw_id, target_scm)
863
866
864 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
867 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
865 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
868 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
866
869
867 pullrequest_title = _form['pullrequest_title']
870 pullrequest_title = _form['pullrequest_title']
868 title_source_ref = source_ref.split(':', 2)[1]
871 title_source_ref = source_ref.split(':', 2)[1]
869 if not pullrequest_title:
872 if not pullrequest_title:
870 pullrequest_title = PullRequestModel().generate_pullrequest_title(
873 pullrequest_title = PullRequestModel().generate_pullrequest_title(
871 source=source_repo,
874 source=source_repo,
872 source_ref=title_source_ref,
875 source_ref=title_source_ref,
873 target=target_repo
876 target=target_repo
874 )
877 )
875
878
876 description = _form['pullrequest_desc']
879 description = _form['pullrequest_desc']
877
880
878 get_default_reviewers_data, validate_default_reviewers = \
881 get_default_reviewers_data, validate_default_reviewers = \
879 PullRequestModel().get_reviewer_functions()
882 PullRequestModel().get_reviewer_functions()
880
883
881 # recalculate reviewers logic, to make sure we can validate this
884 # recalculate reviewers logic, to make sure we can validate this
882 reviewer_rules = get_default_reviewers_data(
885 reviewer_rules = get_default_reviewers_data(
883 self._rhodecode_db_user, source_db_repo,
886 self._rhodecode_db_user, source_db_repo,
884 source_commit, target_db_repo, target_commit)
887 source_commit, target_db_repo, target_commit)
885
888
886 given_reviewers = _form['review_members']
889 given_reviewers = _form['review_members']
887 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
890 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
888
891
889 try:
892 try:
890 pull_request = PullRequestModel().create(
893 pull_request = PullRequestModel().create(
891 self._rhodecode_user.user_id, source_repo, source_ref,
894 self._rhodecode_user.user_id, source_repo, source_ref,
892 target_repo, target_ref, commit_ids, reviewers,
895 target_repo, target_ref, commit_ids, reviewers,
893 pullrequest_title, description, reviewer_rules
896 pullrequest_title, description, reviewer_rules
894 )
897 )
895 Session().commit()
898 Session().commit()
896
899
897 h.flash(_('Successfully opened new pull request'),
900 h.flash(_('Successfully opened new pull request'),
898 category='success')
901 category='success')
899 except Exception:
902 except Exception:
900 msg = _('Error occurred during creation of this pull request.')
903 msg = _('Error occurred during creation of this pull request.')
901 log.exception(msg)
904 log.exception(msg)
902 h.flash(msg, category='error')
905 h.flash(msg, category='error')
903
906
904 # copy the args back to redirect
907 # copy the args back to redirect
905 org_query = self.request.GET.mixed()
908 org_query = self.request.GET.mixed()
906 raise HTTPFound(
909 raise HTTPFound(
907 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
910 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
908 _query=org_query))
911 _query=org_query))
909
912
910 raise HTTPFound(
913 raise HTTPFound(
911 h.route_path('pullrequest_show', repo_name=target_repo,
914 h.route_path('pullrequest_show', repo_name=target_repo,
912 pull_request_id=pull_request.pull_request_id))
915 pull_request_id=pull_request.pull_request_id))
913
916
914 @LoginRequired()
917 @LoginRequired()
915 @NotAnonymous()
918 @NotAnonymous()
916 @HasRepoPermissionAnyDecorator(
919 @HasRepoPermissionAnyDecorator(
917 'repository.read', 'repository.write', 'repository.admin')
920 'repository.read', 'repository.write', 'repository.admin')
918 @CSRFRequired()
921 @CSRFRequired()
919 @view_config(
922 @view_config(
920 route_name='pullrequest_update', request_method='POST',
923 route_name='pullrequest_update', request_method='POST',
921 renderer='json_ext')
924 renderer='json_ext')
922 def pull_request_update(self):
925 def pull_request_update(self):
923 pull_request = PullRequest.get_or_404(
926 pull_request = PullRequest.get_or_404(
924 self.request.matchdict['pull_request_id'])
927 self.request.matchdict['pull_request_id'])
925 _ = self.request.translate
928 _ = self.request.translate
926
929
927 self.load_default_context()
930 self.load_default_context()
928
931
929 if pull_request.is_closed():
932 if pull_request.is_closed():
930 log.debug('update: forbidden because pull request is closed')
933 log.debug('update: forbidden because pull request is closed')
931 msg = _(u'Cannot update closed pull requests.')
934 msg = _(u'Cannot update closed pull requests.')
932 h.flash(msg, category='error')
935 h.flash(msg, category='error')
933 return True
936 return True
934
937
935 # only owner or admin can update it
938 # only owner or admin can update it
936 allowed_to_update = PullRequestModel().check_user_update(
939 allowed_to_update = PullRequestModel().check_user_update(
937 pull_request, self._rhodecode_user)
940 pull_request, self._rhodecode_user)
938 if allowed_to_update:
941 if allowed_to_update:
939 controls = peppercorn.parse(self.request.POST.items())
942 controls = peppercorn.parse(self.request.POST.items())
940
943
941 if 'review_members' in controls:
944 if 'review_members' in controls:
942 self._update_reviewers(
945 self._update_reviewers(
943 pull_request, controls['review_members'],
946 pull_request, controls['review_members'],
944 pull_request.reviewer_data)
947 pull_request.reviewer_data)
945 elif str2bool(self.request.POST.get('update_commits', 'false')):
948 elif str2bool(self.request.POST.get('update_commits', 'false')):
946 self._update_commits(pull_request)
949 self._update_commits(pull_request)
947 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
950 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
948 self._edit_pull_request(pull_request)
951 self._edit_pull_request(pull_request)
949 else:
952 else:
950 raise HTTPBadRequest()
953 raise HTTPBadRequest()
951 return True
954 return True
952 raise HTTPForbidden()
955 raise HTTPForbidden()
953
956
954 def _edit_pull_request(self, pull_request):
957 def _edit_pull_request(self, pull_request):
955 _ = self.request.translate
958 _ = self.request.translate
956 try:
959 try:
957 PullRequestModel().edit(
960 PullRequestModel().edit(
958 pull_request, self.request.POST.get('title'),
961 pull_request, self.request.POST.get('title'),
959 self.request.POST.get('description'), self._rhodecode_user)
962 self.request.POST.get('description'), self._rhodecode_user)
960 except ValueError:
963 except ValueError:
961 msg = _(u'Cannot update closed pull requests.')
964 msg = _(u'Cannot update closed pull requests.')
962 h.flash(msg, category='error')
965 h.flash(msg, category='error')
963 return
966 return
964 else:
967 else:
965 Session().commit()
968 Session().commit()
966
969
967 msg = _(u'Pull request title & description updated.')
970 msg = _(u'Pull request title & description updated.')
968 h.flash(msg, category='success')
971 h.flash(msg, category='success')
969 return
972 return
970
973
971 def _update_commits(self, pull_request):
974 def _update_commits(self, pull_request):
972 _ = self.request.translate
975 _ = self.request.translate
973 resp = PullRequestModel().update_commits(pull_request)
976 resp = PullRequestModel().update_commits(pull_request)
974
977
975 if resp.executed:
978 if resp.executed:
976
979
977 if resp.target_changed and resp.source_changed:
980 if resp.target_changed and resp.source_changed:
978 changed = 'target and source repositories'
981 changed = 'target and source repositories'
979 elif resp.target_changed and not resp.source_changed:
982 elif resp.target_changed and not resp.source_changed:
980 changed = 'target repository'
983 changed = 'target repository'
981 elif not resp.target_changed and resp.source_changed:
984 elif not resp.target_changed and resp.source_changed:
982 changed = 'source repository'
985 changed = 'source repository'
983 else:
986 else:
984 changed = 'nothing'
987 changed = 'nothing'
985
988
986 msg = _(
989 msg = _(
987 u'Pull request updated to "{source_commit_id}" with '
990 u'Pull request updated to "{source_commit_id}" with '
988 u'{count_added} added, {count_removed} removed commits. '
991 u'{count_added} added, {count_removed} removed commits. '
989 u'Source of changes: {change_source}')
992 u'Source of changes: {change_source}')
990 msg = msg.format(
993 msg = msg.format(
991 source_commit_id=pull_request.source_ref_parts.commit_id,
994 source_commit_id=pull_request.source_ref_parts.commit_id,
992 count_added=len(resp.changes.added),
995 count_added=len(resp.changes.added),
993 count_removed=len(resp.changes.removed),
996 count_removed=len(resp.changes.removed),
994 change_source=changed)
997 change_source=changed)
995 h.flash(msg, category='success')
998 h.flash(msg, category='success')
996
999
997 channel = '/repo${}$/pr/{}'.format(
1000 channel = '/repo${}$/pr/{}'.format(
998 pull_request.target_repo.repo_name,
1001 pull_request.target_repo.repo_name,
999 pull_request.pull_request_id)
1002 pull_request.pull_request_id)
1000 message = msg + (
1003 message = msg + (
1001 ' - <a onclick="window.location.reload()">'
1004 ' - <a onclick="window.location.reload()">'
1002 '<strong>{}</strong></a>'.format(_('Reload page')))
1005 '<strong>{}</strong></a>'.format(_('Reload page')))
1003 channelstream.post_message(
1006 channelstream.post_message(
1004 channel, message, self._rhodecode_user.username,
1007 channel, message, self._rhodecode_user.username,
1005 registry=self.request.registry)
1008 registry=self.request.registry)
1006 else:
1009 else:
1007 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1010 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1008 warning_reasons = [
1011 warning_reasons = [
1009 UpdateFailureReason.NO_CHANGE,
1012 UpdateFailureReason.NO_CHANGE,
1010 UpdateFailureReason.WRONG_REF_TYPE,
1013 UpdateFailureReason.WRONG_REF_TYPE,
1011 ]
1014 ]
1012 category = 'warning' if resp.reason in warning_reasons else 'error'
1015 category = 'warning' if resp.reason in warning_reasons else 'error'
1013 h.flash(msg, category=category)
1016 h.flash(msg, category=category)
1014
1017
1015 @LoginRequired()
1018 @LoginRequired()
1016 @NotAnonymous()
1019 @NotAnonymous()
1017 @HasRepoPermissionAnyDecorator(
1020 @HasRepoPermissionAnyDecorator(
1018 'repository.read', 'repository.write', 'repository.admin')
1021 'repository.read', 'repository.write', 'repository.admin')
1019 @CSRFRequired()
1022 @CSRFRequired()
1020 @view_config(
1023 @view_config(
1021 route_name='pullrequest_merge', request_method='POST',
1024 route_name='pullrequest_merge', request_method='POST',
1022 renderer='json_ext')
1025 renderer='json_ext')
1023 def pull_request_merge(self):
1026 def pull_request_merge(self):
1024 """
1027 """
1025 Merge will perform a server-side merge of the specified
1028 Merge will perform a server-side merge of the specified
1026 pull request, if the pull request is approved and mergeable.
1029 pull request, if the pull request is approved and mergeable.
1027 After successful merging, the pull request is automatically
1030 After successful merging, the pull request is automatically
1028 closed, with a relevant comment.
1031 closed, with a relevant comment.
1029 """
1032 """
1030 pull_request = PullRequest.get_or_404(
1033 pull_request = PullRequest.get_or_404(
1031 self.request.matchdict['pull_request_id'])
1034 self.request.matchdict['pull_request_id'])
1032
1035
1033 self.load_default_context()
1036 self.load_default_context()
1034 check = MergeCheck.validate(pull_request, self._rhodecode_db_user,
1037 check = MergeCheck.validate(pull_request, self._rhodecode_db_user,
1035 translator=self.request.translate)
1038 translator=self.request.translate)
1036 merge_possible = not check.failed
1039 merge_possible = not check.failed
1037
1040
1038 for err_type, error_msg in check.errors:
1041 for err_type, error_msg in check.errors:
1039 h.flash(error_msg, category=err_type)
1042 h.flash(error_msg, category=err_type)
1040
1043
1041 if merge_possible:
1044 if merge_possible:
1042 log.debug("Pre-conditions checked, trying to merge.")
1045 log.debug("Pre-conditions checked, trying to merge.")
1043 extras = vcs_operation_context(
1046 extras = vcs_operation_context(
1044 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1047 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1045 username=self._rhodecode_db_user.username, action='push',
1048 username=self._rhodecode_db_user.username, action='push',
1046 scm=pull_request.target_repo.repo_type)
1049 scm=pull_request.target_repo.repo_type)
1047 self._merge_pull_request(
1050 self._merge_pull_request(
1048 pull_request, self._rhodecode_db_user, extras)
1051 pull_request, self._rhodecode_db_user, extras)
1049 else:
1052 else:
1050 log.debug("Pre-conditions failed, NOT merging.")
1053 log.debug("Pre-conditions failed, NOT merging.")
1051
1054
1052 raise HTTPFound(
1055 raise HTTPFound(
1053 h.route_path('pullrequest_show',
1056 h.route_path('pullrequest_show',
1054 repo_name=pull_request.target_repo.repo_name,
1057 repo_name=pull_request.target_repo.repo_name,
1055 pull_request_id=pull_request.pull_request_id))
1058 pull_request_id=pull_request.pull_request_id))
1056
1059
1057 def _merge_pull_request(self, pull_request, user, extras):
1060 def _merge_pull_request(self, pull_request, user, extras):
1058 _ = self.request.translate
1061 _ = self.request.translate
1059 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
1062 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
1060
1063
1061 if merge_resp.executed:
1064 if merge_resp.executed:
1062 log.debug("The merge was successful, closing the pull request.")
1065 log.debug("The merge was successful, closing the pull request.")
1063 PullRequestModel().close_pull_request(
1066 PullRequestModel().close_pull_request(
1064 pull_request.pull_request_id, user)
1067 pull_request.pull_request_id, user)
1065 Session().commit()
1068 Session().commit()
1066 msg = _('Pull request was successfully merged and closed.')
1069 msg = _('Pull request was successfully merged and closed.')
1067 h.flash(msg, category='success')
1070 h.flash(msg, category='success')
1068 else:
1071 else:
1069 log.debug(
1072 log.debug(
1070 "The merge was not successful. Merge response: %s",
1073 "The merge was not successful. Merge response: %s",
1071 merge_resp)
1074 merge_resp)
1072 msg = PullRequestModel().merge_status_message(
1075 msg = PullRequestModel().merge_status_message(
1073 merge_resp.failure_reason)
1076 merge_resp.failure_reason)
1074 h.flash(msg, category='error')
1077 h.flash(msg, category='error')
1075
1078
1076 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1079 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1077 _ = self.request.translate
1080 _ = self.request.translate
1078 get_default_reviewers_data, validate_default_reviewers = \
1081 get_default_reviewers_data, validate_default_reviewers = \
1079 PullRequestModel().get_reviewer_functions()
1082 PullRequestModel().get_reviewer_functions()
1080
1083
1081 try:
1084 try:
1082 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1085 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1083 except ValueError as e:
1086 except ValueError as e:
1084 log.error('Reviewers Validation: {}'.format(e))
1087 log.error('Reviewers Validation: {}'.format(e))
1085 h.flash(e, category='error')
1088 h.flash(e, category='error')
1086 return
1089 return
1087
1090
1088 PullRequestModel().update_reviewers(
1091 PullRequestModel().update_reviewers(
1089 pull_request, reviewers, self._rhodecode_user)
1092 pull_request, reviewers, self._rhodecode_user)
1090 h.flash(_('Pull request reviewers updated.'), category='success')
1093 h.flash(_('Pull request reviewers updated.'), category='success')
1091 Session().commit()
1094 Session().commit()
1092
1095
1093 @LoginRequired()
1096 @LoginRequired()
1094 @NotAnonymous()
1097 @NotAnonymous()
1095 @HasRepoPermissionAnyDecorator(
1098 @HasRepoPermissionAnyDecorator(
1096 'repository.read', 'repository.write', 'repository.admin')
1099 'repository.read', 'repository.write', 'repository.admin')
1097 @CSRFRequired()
1100 @CSRFRequired()
1098 @view_config(
1101 @view_config(
1099 route_name='pullrequest_delete', request_method='POST',
1102 route_name='pullrequest_delete', request_method='POST',
1100 renderer='json_ext')
1103 renderer='json_ext')
1101 def pull_request_delete(self):
1104 def pull_request_delete(self):
1102 _ = self.request.translate
1105 _ = self.request.translate
1103
1106
1104 pull_request = PullRequest.get_or_404(
1107 pull_request = PullRequest.get_or_404(
1105 self.request.matchdict['pull_request_id'])
1108 self.request.matchdict['pull_request_id'])
1106 self.load_default_context()
1109 self.load_default_context()
1107
1110
1108 pr_closed = pull_request.is_closed()
1111 pr_closed = pull_request.is_closed()
1109 allowed_to_delete = PullRequestModel().check_user_delete(
1112 allowed_to_delete = PullRequestModel().check_user_delete(
1110 pull_request, self._rhodecode_user) and not pr_closed
1113 pull_request, self._rhodecode_user) and not pr_closed
1111
1114
1112 # only owner can delete it !
1115 # only owner can delete it !
1113 if allowed_to_delete:
1116 if allowed_to_delete:
1114 PullRequestModel().delete(pull_request, self._rhodecode_user)
1117 PullRequestModel().delete(pull_request, self._rhodecode_user)
1115 Session().commit()
1118 Session().commit()
1116 h.flash(_('Successfully deleted pull request'),
1119 h.flash(_('Successfully deleted pull request'),
1117 category='success')
1120 category='success')
1118 raise HTTPFound(h.route_path('pullrequest_show_all',
1121 raise HTTPFound(h.route_path('pullrequest_show_all',
1119 repo_name=self.db_repo_name))
1122 repo_name=self.db_repo_name))
1120
1123
1121 log.warning('user %s tried to delete pull request without access',
1124 log.warning('user %s tried to delete pull request without access',
1122 self._rhodecode_user)
1125 self._rhodecode_user)
1123 raise HTTPNotFound()
1126 raise HTTPNotFound()
1124
1127
1125 @LoginRequired()
1128 @LoginRequired()
1126 @NotAnonymous()
1129 @NotAnonymous()
1127 @HasRepoPermissionAnyDecorator(
1130 @HasRepoPermissionAnyDecorator(
1128 'repository.read', 'repository.write', 'repository.admin')
1131 'repository.read', 'repository.write', 'repository.admin')
1129 @CSRFRequired()
1132 @CSRFRequired()
1130 @view_config(
1133 @view_config(
1131 route_name='pullrequest_comment_create', request_method='POST',
1134 route_name='pullrequest_comment_create', request_method='POST',
1132 renderer='json_ext')
1135 renderer='json_ext')
1133 def pull_request_comment_create(self):
1136 def pull_request_comment_create(self):
1134 _ = self.request.translate
1137 _ = self.request.translate
1135
1138
1136 pull_request = PullRequest.get_or_404(
1139 pull_request = PullRequest.get_or_404(
1137 self.request.matchdict['pull_request_id'])
1140 self.request.matchdict['pull_request_id'])
1138 pull_request_id = pull_request.pull_request_id
1141 pull_request_id = pull_request.pull_request_id
1139
1142
1140 if pull_request.is_closed():
1143 if pull_request.is_closed():
1141 log.debug('comment: forbidden because pull request is closed')
1144 log.debug('comment: forbidden because pull request is closed')
1142 raise HTTPForbidden()
1145 raise HTTPForbidden()
1143
1146
1144 allowed_to_comment = PullRequestModel().check_user_comment(
1147 allowed_to_comment = PullRequestModel().check_user_comment(
1145 pull_request, self._rhodecode_user)
1148 pull_request, self._rhodecode_user)
1146 if not allowed_to_comment:
1149 if not allowed_to_comment:
1147 log.debug(
1150 log.debug(
1148 'comment: forbidden because pull request is from forbidden repo')
1151 'comment: forbidden because pull request is from forbidden repo')
1149 raise HTTPForbidden()
1152 raise HTTPForbidden()
1150
1153
1151 c = self.load_default_context()
1154 c = self.load_default_context()
1152
1155
1153 status = self.request.POST.get('changeset_status', None)
1156 status = self.request.POST.get('changeset_status', None)
1154 text = self.request.POST.get('text')
1157 text = self.request.POST.get('text')
1155 comment_type = self.request.POST.get('comment_type')
1158 comment_type = self.request.POST.get('comment_type')
1156 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1159 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1157 close_pull_request = self.request.POST.get('close_pull_request')
1160 close_pull_request = self.request.POST.get('close_pull_request')
1158
1161
1159 # the logic here should work like following, if we submit close
1162 # the logic here should work like following, if we submit close
1160 # pr comment, use `close_pull_request_with_comment` function
1163 # pr comment, use `close_pull_request_with_comment` function
1161 # else handle regular comment logic
1164 # else handle regular comment logic
1162
1165
1163 if close_pull_request:
1166 if close_pull_request:
1164 # only owner or admin or person with write permissions
1167 # only owner or admin or person with write permissions
1165 allowed_to_close = PullRequestModel().check_user_update(
1168 allowed_to_close = PullRequestModel().check_user_update(
1166 pull_request, self._rhodecode_user)
1169 pull_request, self._rhodecode_user)
1167 if not allowed_to_close:
1170 if not allowed_to_close:
1168 log.debug('comment: forbidden because not allowed to close '
1171 log.debug('comment: forbidden because not allowed to close '
1169 'pull request %s', pull_request_id)
1172 'pull request %s', pull_request_id)
1170 raise HTTPForbidden()
1173 raise HTTPForbidden()
1171 comment, status = PullRequestModel().close_pull_request_with_comment(
1174 comment, status = PullRequestModel().close_pull_request_with_comment(
1172 pull_request, self._rhodecode_user, self.db_repo, message=text)
1175 pull_request, self._rhodecode_user, self.db_repo, message=text)
1173 Session().flush()
1176 Session().flush()
1174 events.trigger(
1177 events.trigger(
1175 events.PullRequestCommentEvent(pull_request, comment))
1178 events.PullRequestCommentEvent(pull_request, comment))
1176
1179
1177 else:
1180 else:
1178 # regular comment case, could be inline, or one with status.
1181 # regular comment case, could be inline, or one with status.
1179 # for that one we check also permissions
1182 # for that one we check also permissions
1180
1183
1181 allowed_to_change_status = PullRequestModel().check_user_change_status(
1184 allowed_to_change_status = PullRequestModel().check_user_change_status(
1182 pull_request, self._rhodecode_user)
1185 pull_request, self._rhodecode_user)
1183
1186
1184 if status and allowed_to_change_status:
1187 if status and allowed_to_change_status:
1185 message = (_('Status change %(transition_icon)s %(status)s')
1188 message = (_('Status change %(transition_icon)s %(status)s')
1186 % {'transition_icon': '>',
1189 % {'transition_icon': '>',
1187 'status': ChangesetStatus.get_status_lbl(status)})
1190 'status': ChangesetStatus.get_status_lbl(status)})
1188 text = text or message
1191 text = text or message
1189
1192
1190 comment = CommentsModel().create(
1193 comment = CommentsModel().create(
1191 text=text,
1194 text=text,
1192 repo=self.db_repo.repo_id,
1195 repo=self.db_repo.repo_id,
1193 user=self._rhodecode_user.user_id,
1196 user=self._rhodecode_user.user_id,
1194 pull_request=pull_request,
1197 pull_request=pull_request,
1195 f_path=self.request.POST.get('f_path'),
1198 f_path=self.request.POST.get('f_path'),
1196 line_no=self.request.POST.get('line'),
1199 line_no=self.request.POST.get('line'),
1197 status_change=(ChangesetStatus.get_status_lbl(status)
1200 status_change=(ChangesetStatus.get_status_lbl(status)
1198 if status and allowed_to_change_status else None),
1201 if status and allowed_to_change_status else None),
1199 status_change_type=(status
1202 status_change_type=(status
1200 if status and allowed_to_change_status else None),
1203 if status and allowed_to_change_status else None),
1201 comment_type=comment_type,
1204 comment_type=comment_type,
1202 resolves_comment_id=resolves_comment_id
1205 resolves_comment_id=resolves_comment_id
1203 )
1206 )
1204
1207
1205 if allowed_to_change_status:
1208 if allowed_to_change_status:
1206 # calculate old status before we change it
1209 # calculate old status before we change it
1207 old_calculated_status = pull_request.calculated_review_status()
1210 old_calculated_status = pull_request.calculated_review_status()
1208
1211
1209 # get status if set !
1212 # get status if set !
1210 if status:
1213 if status:
1211 ChangesetStatusModel().set_status(
1214 ChangesetStatusModel().set_status(
1212 self.db_repo.repo_id,
1215 self.db_repo.repo_id,
1213 status,
1216 status,
1214 self._rhodecode_user.user_id,
1217 self._rhodecode_user.user_id,
1215 comment,
1218 comment,
1216 pull_request=pull_request
1219 pull_request=pull_request
1217 )
1220 )
1218
1221
1219 Session().flush()
1222 Session().flush()
1220 # this is somehow required to get access to some relationship
1223 # this is somehow required to get access to some relationship
1221 # loaded on comment
1224 # loaded on comment
1222 Session().refresh(comment)
1225 Session().refresh(comment)
1223
1226
1224 events.trigger(
1227 events.trigger(
1225 events.PullRequestCommentEvent(pull_request, comment))
1228 events.PullRequestCommentEvent(pull_request, comment))
1226
1229
1227 # we now calculate the status of pull request, and based on that
1230 # we now calculate the status of pull request, and based on that
1228 # calculation we set the commits status
1231 # calculation we set the commits status
1229 calculated_status = pull_request.calculated_review_status()
1232 calculated_status = pull_request.calculated_review_status()
1230 if old_calculated_status != calculated_status:
1233 if old_calculated_status != calculated_status:
1231 PullRequestModel()._trigger_pull_request_hook(
1234 PullRequestModel()._trigger_pull_request_hook(
1232 pull_request, self._rhodecode_user, 'review_status_change')
1235 pull_request, self._rhodecode_user, 'review_status_change')
1233
1236
1234 Session().commit()
1237 Session().commit()
1235
1238
1236 data = {
1239 data = {
1237 'target_id': h.safeid(h.safe_unicode(
1240 'target_id': h.safeid(h.safe_unicode(
1238 self.request.POST.get('f_path'))),
1241 self.request.POST.get('f_path'))),
1239 }
1242 }
1240 if comment:
1243 if comment:
1241 c.co = comment
1244 c.co = comment
1242 rendered_comment = render(
1245 rendered_comment = render(
1243 'rhodecode:templates/changeset/changeset_comment_block.mako',
1246 'rhodecode:templates/changeset/changeset_comment_block.mako',
1244 self._get_template_context(c), self.request)
1247 self._get_template_context(c), self.request)
1245
1248
1246 data.update(comment.get_dict())
1249 data.update(comment.get_dict())
1247 data.update({'rendered_text': rendered_comment})
1250 data.update({'rendered_text': rendered_comment})
1248
1251
1249 return data
1252 return data
1250
1253
1251 @LoginRequired()
1254 @LoginRequired()
1252 @NotAnonymous()
1255 @NotAnonymous()
1253 @HasRepoPermissionAnyDecorator(
1256 @HasRepoPermissionAnyDecorator(
1254 'repository.read', 'repository.write', 'repository.admin')
1257 'repository.read', 'repository.write', 'repository.admin')
1255 @CSRFRequired()
1258 @CSRFRequired()
1256 @view_config(
1259 @view_config(
1257 route_name='pullrequest_comment_delete', request_method='POST',
1260 route_name='pullrequest_comment_delete', request_method='POST',
1258 renderer='json_ext')
1261 renderer='json_ext')
1259 def pull_request_comment_delete(self):
1262 def pull_request_comment_delete(self):
1260 pull_request = PullRequest.get_or_404(
1263 pull_request = PullRequest.get_or_404(
1261 self.request.matchdict['pull_request_id'])
1264 self.request.matchdict['pull_request_id'])
1262
1265
1263 comment = ChangesetComment.get_or_404(
1266 comment = ChangesetComment.get_or_404(
1264 self.request.matchdict['comment_id'])
1267 self.request.matchdict['comment_id'])
1265 comment_id = comment.comment_id
1268 comment_id = comment.comment_id
1266
1269
1267 if pull_request.is_closed():
1270 if pull_request.is_closed():
1268 log.debug('comment: forbidden because pull request is closed')
1271 log.debug('comment: forbidden because pull request is closed')
1269 raise HTTPForbidden()
1272 raise HTTPForbidden()
1270
1273
1271 if not comment:
1274 if not comment:
1272 log.debug('Comment with id:%s not found, skipping', comment_id)
1275 log.debug('Comment with id:%s not found, skipping', comment_id)
1273 # comment already deleted in another call probably
1276 # comment already deleted in another call probably
1274 return True
1277 return True
1275
1278
1276 if comment.pull_request.is_closed():
1279 if comment.pull_request.is_closed():
1277 # don't allow deleting comments on closed pull request
1280 # don't allow deleting comments on closed pull request
1278 raise HTTPForbidden()
1281 raise HTTPForbidden()
1279
1282
1280 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1283 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1281 super_admin = h.HasPermissionAny('hg.admin')()
1284 super_admin = h.HasPermissionAny('hg.admin')()
1282 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1285 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1283 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1286 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1284 comment_repo_admin = is_repo_admin and is_repo_comment
1287 comment_repo_admin = is_repo_admin and is_repo_comment
1285
1288
1286 if super_admin or comment_owner or comment_repo_admin:
1289 if super_admin or comment_owner or comment_repo_admin:
1287 old_calculated_status = comment.pull_request.calculated_review_status()
1290 old_calculated_status = comment.pull_request.calculated_review_status()
1288 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1291 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1289 Session().commit()
1292 Session().commit()
1290 calculated_status = comment.pull_request.calculated_review_status()
1293 calculated_status = comment.pull_request.calculated_review_status()
1291 if old_calculated_status != calculated_status:
1294 if old_calculated_status != calculated_status:
1292 PullRequestModel()._trigger_pull_request_hook(
1295 PullRequestModel()._trigger_pull_request_hook(
1293 comment.pull_request, self._rhodecode_user, 'review_status_change')
1296 comment.pull_request, self._rhodecode_user, 'review_status_change')
1294 return True
1297 return True
1295 else:
1298 else:
1296 log.warning('No permissions for user %s to delete comment_id: %s',
1299 log.warning('No permissions for user %s to delete comment_id: %s',
1297 self._rhodecode_db_user, comment_id)
1300 self._rhodecode_db_user, comment_id)
1298 raise HTTPNotFound()
1301 raise HTTPNotFound()
@@ -1,644 +1,684 b''
1 // navigation.less
1 // navigation.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 // HEADER NAVIGATION
5 // HEADER NAVIGATION
6
6
7 .horizontal-list {
7 .horizontal-list {
8 float: right;
8 float: right;
9 display: block;
9 display: block;
10 margin: 0;
10 margin: 0;
11 padding: 0;
11 padding: 0;
12 -webkit-padding-start: 0;
12 -webkit-padding-start: 0;
13 text-align: left;
13 text-align: left;
14 font-size: @navigation-fontsize;
14 font-size: @navigation-fontsize;
15 color: @grey6;
15 color: @grey6;
16 z-index:10;
16 z-index:10;
17
17
18 li {
18 li {
19 line-height: 1em;
19 line-height: 1em;
20 list-style-type: none;
20 list-style-type: none;
21
21
22 a {
22 a {
23 padding: 0 .5em;
23 padding: 0 .5em;
24
24
25 &.menu_link_notifications {
25 &.menu_link_notifications {
26 .pill(7px,@rcblue);
26 .pill(7px,@rcblue);
27 display: inline;
27 display: inline;
28 margin: 0 7px 0 .7em;
28 margin: 0 7px 0 .7em;
29 font-size: @basefontsize;
29 font-size: @basefontsize;
30 color: white;
30 color: white;
31
31
32 &.empty {
32 &.empty {
33 background-color: @grey4;
33 background-color: @grey4;
34 }
34 }
35
35
36 &:hover {
36 &:hover {
37 background-color: @rcdarkblue;
37 background-color: @rcdarkblue;
38 }
38 }
39 }
39 }
40 }
40 }
41 .pill_container {
41 .pill_container {
42 margin: 1.25em 0px 0px 0px;
42 margin: 1.25em 0px 0px 0px;
43 float: right;
43 float: right;
44 }
44 }
45
45
46 &#quick_login_li {
46 &#quick_login_li {
47 &:hover {
47 &:hover {
48 color: @grey5;
48 color: @grey5;
49 }
49 }
50
50
51 a.menu_link_notifications {
51 a.menu_link_notifications {
52 color: white;
52 color: white;
53 }
53 }
54
54
55 .user {
55 .user {
56 padding-bottom: 10px;
56 padding-bottom: 10px;
57 }
57 }
58
58
59 &.open {
59 &.open {
60 .user {
60 .user {
61 border-bottom: 5px solid @rcblue;
61 border-bottom: 5px solid @rcblue;
62 }
62 }
63 }
63 }
64 }
64 }
65
65
66 &:before { content: none; }
66 &:before { content: none; }
67
67
68 &:last-child {
68 &:last-child {
69 .menulabel {
69 .menulabel {
70 padding-right: 0;
70 padding-right: 0;
71 border-right: none;
71 border-right: none;
72
72
73 .show_more {
73 .show_more {
74 padding-right: 0;
74 padding-right: 0;
75 }
75 }
76 }
76 }
77
77
78 &> a {
78 &> a {
79 border-bottom: none;
79 border-bottom: none;
80 }
80 }
81 }
81 }
82
82
83 &.active {
83 &.active {
84 border-bottom: 5px solid @rcblue;
84 border-bottom: 5px solid @rcblue;
85 }
85 }
86
86
87 &.open {
87 &.open {
88
88
89 a {
89 a {
90 color: white;
90 color: white;
91 }
91 }
92 }
92 }
93
93
94 &:focus {
94 &:focus {
95 outline: none;
95 outline: none;
96 }
96 }
97
97
98 ul li {
98 ul li {
99 display: block;
99 display: block;
100
100
101 &:last-child> a {
101 &:last-child> a {
102 border-bottom: none;
102 border-bottom: none;
103 }
103 }
104
104
105 ul li:last-child a {
105 ul li:last-child a {
106 /* we don't expect more then 3 levels of submenu and the third
106 /* we don't expect more then 3 levels of submenu and the third
107 level can have different html structure */
107 level can have different html structure */
108 border-bottom: none;
108 border-bottom: none;
109 }
109 }
110 }
110 }
111 }
111 }
112
112
113 > li {
113 > li {
114 float: left;
114 float: left;
115 display: block;
115 display: block;
116 padding: 0;
116 padding: 0;
117
117
118 > a,
118 > a,
119 &.has_select2 a {
119 &.has_select2 a {
120 display: block;
120 display: block;
121 padding: 10px 0 2px;
121 padding: 10px 0 2px;
122
122
123 .show_more {
123 .show_more {
124 margin-top: -4px;
124 margin-top: -4px;
125 padding-right: .5em;
125 padding-right: .5em;
126 }
126 }
127 }
127 }
128
128
129 .menulabel {
129 .menulabel {
130 padding: 0 .5em;
130 padding: 0 .5em;
131 line-height: 1em;
131 line-height: 1em;
132 // for this specifically we do not use a variable
132 // for this specifically we do not use a variable
133 border-right: 1px solid @grey4;
133 border-right: 1px solid @grey4;
134 }
134 }
135
135
136 .pr_notifications {
136 .pr_notifications {
137 padding-left: .5em;
137 padding-left: .5em;
138 }
138 }
139
139
140 .pr_notifications + .menulabel {
140 .pr_notifications + .menulabel {
141 display:inline;
141 display:inline;
142 padding-left: 0;
142 padding-left: 0;
143 }
143 }
144
144
145 &:hover,
145 &:hover,
146 &.open,
146 &.open,
147 &.active {
147 &.active {
148 a {
148 a {
149 color: @grey1;
149 color: @grey1;
150 }
150 }
151 }
151 }
152 }
152 }
153
153
154 pre {
154 pre {
155 margin: 0;
155 margin: 0;
156 padding: 0;
156 padding: 0;
157 }
157 }
158
158
159 .select2-container,
159 .select2-container,
160 .menulink.childs {
160 .menulink.childs {
161 position: relative;
161 position: relative;
162 }
162 }
163
163
164 #quick_login {
164 #quick_login {
165
165
166 li a {
166 li a {
167 padding: .5em 0;
167 padding: .5em 0;
168 border-bottom: none;
168 border-bottom: none;
169 color: @grey2;
169 color: @grey2;
170
170
171 &:hover { color: @grey1; }
171 &:hover { color: @grey1; }
172 }
172 }
173
173
174 .show_more {
174 .show_more {
175 padding-left: .5em;
175 padding-left: .5em;
176 }
176 }
177 }
177 }
178
178
179 #quick_login_link {
179 #quick_login_link {
180 display: inline-block;
180 display: inline-block;
181
181
182 .gravatar {
182 .gravatar {
183 border: 1px solid @grey2;
183 border: 1px solid @grey2;
184 }
184 }
185
185
186 .gravatar-login {
186 .gravatar-login {
187 height: 20px;
187 height: 20px;
188 width: 20px;
188 width: 20px;
189 margin: -8px 0;
189 margin: -8px 0;
190 padding: 0;
190 padding: 0;
191 }
191 }
192
192
193 &:hover .user {
193 &:hover .user {
194 color: @grey6;
194 color: @grey6;
195 }
195 }
196 }
196 }
197 }
197 }
198 .header .horizontal-list {
198 .header .horizontal-list {
199
199
200 li {
200 li {
201
201
202 &#quick_login_li {
202 &#quick_login_li {
203 padding-left: .5em;
203 padding-left: .5em;
204
204
205 &:hover #quick_login_link {
205 &:hover #quick_login_link {
206 color: inherit;
206 color: inherit;
207 }
207 }
208 }
208 }
209
209
210 &:before { content: none; }
210 &:before { content: none; }
211 }
211 }
212
212
213 > li {
213 > li {
214
214
215 a {
215 a {
216 padding: 18px 0 12px 0;
216 padding: 18px 0 12px 0;
217 color: @nav-grey;
217 color: @nav-grey;
218
218
219 &.menu_link_notifications {
219 &.menu_link_notifications {
220 padding: 1px 8px;
220 padding: 1px 8px;
221 }
221 }
222 }
222 }
223
223
224 &:hover,
224 &:hover,
225 &.open,
225 &.open,
226 &.active {
226 &.active {
227 .pill_container a {
227 .pill_container a {
228 // don't select text for the pill container, it has it' own
228 // don't select text for the pill container, it has it' own
229 // hover behaviour
229 // hover behaviour
230 color: @nav-grey;
230 color: @nav-grey;
231 }
231 }
232 }
232 }
233
233
234 &:hover,
234 &:hover,
235 &.open,
235 &.open,
236 &.active {
236 &.active {
237 a {
237 a {
238 color: @grey6;
238 color: @grey6;
239 }
239 }
240 }
240 }
241
241
242 .select2-dropdown-open a {
242 .select2-dropdown-open a {
243 color: @grey6;
243 color: @grey6;
244 }
244 }
245
245
246 .repo-switcher {
246 .repo-switcher {
247 padding-left: 0;
247 padding-left: 0;
248
248
249 .menulabel {
249 .menulabel {
250 padding-left: 0;
250 padding-left: 0;
251 }
251 }
252 }
252 }
253 }
253 }
254
254
255 li ul li {
255 li ul li {
256 background-color:@grey2;
256 background-color:@grey2;
257
257
258 a {
258 a {
259 padding: .5em 0;
259 padding: .5em 0;
260 border-bottom: @border-thickness solid @border-default-color;
260 border-bottom: @border-thickness solid @border-default-color;
261 color: @grey6;
261 color: @grey6;
262 }
262 }
263
263
264 &:last-child a, &.last a{
264 &:last-child a, &.last a{
265 border-bottom: none;
265 border-bottom: none;
266 }
266 }
267
267
268 &:hover {
268 &:hover {
269 background-color: @grey3;
269 background-color: @grey3;
270 }
270 }
271 }
271 }
272
272
273 .submenu {
273 .submenu {
274 margin-top: 5px;
274 margin-top: 5px;
275 }
275 }
276 }
276 }
277
277
278 // SUBMENUS
278 // SUBMENUS
279 .navigation .submenu {
279 .navigation .submenu {
280 display: none;
280 display: none;
281 }
281 }
282
282
283 .navigation li.open {
283 .navigation li.open {
284
284 .submenu {
285 .submenu,
285 display: block;
286 .repo_switcher {
286 }
287 display: block;
288 }
289 }
287 }
290
288
291 .navigation li:last-child .submenu {
289 .navigation li:last-child .submenu {
292 right: -20px;
290 right: -20px;
293 left: auto;
291 left: auto;
294 }
292 }
295
293
296 .submenu {
294 .submenu {
297 position: absolute;
295 position: absolute;
298 top: 100%;
296 top: 100%;
299 left: 0;
297 left: 0;
300 min-width: 150px;
298 min-width: 150px;
301 margin: 6px 0 0;
299 margin: 6px 0 0;
302 padding: 0;
300 padding: 0;
303 text-align: left;
301 text-align: left;
304 font-family: @text-light;
302 font-family: @text-light;
305 border-radius: @border-radius;
303 border-radius: @border-radius;
306 z-index: 20;
304 z-index: 20;
307
305
308 li {
306 li {
309 display: block;
307 display: block;
310 margin: 0;
308 margin: 0;
311 padding: 0 .5em;
309 padding: 0 .5em;
312 line-height: 1em;
310 line-height: 1em;
313 color: @grey3;
311 color: @grey3;
314 background-color: @grey6;
312 background-color: @grey6;
315
313
316 &:before { content: none; }
314 &:before { content: none; }
317
315
318 a {
316 a {
319 display: block;
317 display: block;
320 width: 100%;
318 width: 100%;
321 padding: .5em 0;
319 padding: .5em 0;
322 border-right: none;
320 border-right: none;
323 border-bottom: @border-thickness solid white;
321 border-bottom: @border-thickness solid white;
324 color: @grey3;
322 color: @grey3;
325 }
323 }
326
324
327 ul {
325 ul {
328 display: none;
326 display: none;
329 position: absolute;
327 position: absolute;
330 top: 0;
328 top: 0;
331 right: 100%;
329 right: 100%;
332 padding: 0;
330 padding: 0;
333 z-index: 30;
331 z-index: 30;
334 }
332 }
335 &:hover {
333 &:hover {
336 background-color: @grey5;
334 background-color: @grey5;
337 -webkit-transition: background .3s;
335 -webkit-transition: background .3s;
338 -moz-transition: background .3s;
336 -moz-transition: background .3s;
339 -o-transition: background .3s;
337 -o-transition: background .3s;
340 transition: background .3s;
338 transition: background .3s;
341
339
342 ul {
340 ul {
343 display: block;
341 display: block;
344 }
342 }
345 }
343 }
346 }
344 }
347 }
345 }
348
346
349
347
350
348
351
349
352 // repo dropdown
350 // repo dropdown
353 .quick_repo_menu {
351 .quick_repo_menu {
354 width: 15px;
352 width: 15px;
355 text-align: center;
353 text-align: center;
356 position: relative;
354 position: relative;
357 cursor: pointer;
355 cursor: pointer;
358
356
359 div {
357 div {
360 overflow: visible !important;
358 overflow: visible !important;
361 }
359 }
362
360
363 &.sorting {
361 &.sorting {
364 cursor: auto;
362 cursor: auto;
365 }
363 }
366
364
367 &:hover {
365 &:hover {
368 .menu_items_container {
366 .menu_items_container {
369 position: absolute;
367 position: absolute;
370 display: block;
368 display: block;
371 }
369 }
372 .menu_items {
370 .menu_items {
373 display: block;
371 display: block;
374 }
372 }
375 }
373 }
376
374
377 i {
375 i {
378 margin: 0;
376 margin: 0;
379 color: @grey4;
377 color: @grey4;
380 }
378 }
381
379
382 .menu_items_container {
380 .menu_items_container {
383 position: absolute;
381 position: absolute;
384 top: 0;
382 top: 0;
385 left: 100%;
383 left: 100%;
386 margin: 0;
384 margin: 0;
387 padding: 0;
385 padding: 0;
388 list-style: none;
386 list-style: none;
389 background-color: @grey6;
387 background-color: @grey6;
390 z-index: 999;
388 z-index: 999;
391 text-align: left;
389 text-align: left;
392
390
393 a {
391 a {
394 color: @grey2;
392 color: @grey2;
395 }
393 }
396
394
397 ul.menu_items {
395 ul.menu_items {
398 margin: 0;
396 margin: 0;
399 padding: 0;
397 padding: 0;
400 }
398 }
401
399
402 li {
400 li {
403 margin: 0;
401 margin: 0;
404 padding: 0;
402 padding: 0;
405 line-height: 1em;
403 line-height: 1em;
406 list-style-type: none;
404 list-style-type: none;
407
405
408 &:before { content: none; }
406 &:before { content: none; }
409
407
410 a {
408 a {
411 display: block;
409 display: block;
412 height: 16px;
410 height: 16px;
413 padding: 8px; //must add up to td height (28px)
411 padding: 8px; //must add up to td height (28px)
414
412
415 &:hover {
413 &:hover {
416 background-color: @grey5;
414 background-color: @grey5;
417 -webkit-transition: background .3s;
415 -webkit-transition: background .3s;
418 -moz-transition: background .3s;
416 -moz-transition: background .3s;
419 -o-transition: background .3s;
417 -o-transition: background .3s;
420 transition: background .3s;
418 transition: background .3s;
421 }
419 }
422 }
420 }
423 }
421 }
424 }
422 }
425 }
423 }
426
424
427 // Header Repository Switcher
425 // Header Repository Switcher
428 // Select2 Dropdown
426 // Select2 Dropdown
429 #select2-drop.select2-drop.repo-switcher-dropdown {
427 #select2-drop.select2-drop.repo-switcher-dropdown {
430 width: auto !important;
428 width: auto !important;
431 margin-top: 5px;
429 margin-top: 5px;
432 padding: 1em 0;
430 padding: 1em 0;
433 text-align: left;
431 text-align: left;
434 .border-radius-bottom(@border-radius);
432 .border-radius-bottom(@border-radius);
435 border-color: transparent;
433 border-color: transparent;
436 color: @grey6;
434 color: @grey6;
437 background-color: @grey2;
435 background-color: @grey2;
438
436
439 input {
437 input {
440 min-width: 90%;
438 min-width: 90%;
441 }
439 }
442
440
443 ul.select2-result-sub {
441 ul.select2-result-sub {
444
442
445 li {
443 li {
446 line-height: 1em;
444 line-height: 1em;
447
445
448 &:hover,
446 &:hover,
449 &.select2-highlighted {
447 &.select2-highlighted {
450 background-color: @grey3;
448 background-color: @grey3;
451 }
449 }
452 }
450 }
453
451
454 &:before { content: none; }
452 &:before { content: none; }
455 }
453 }
456
454
457 ul.select2-results {
455 ul.select2-results {
458 min-width: 200px;
456 min-width: 200px;
459 margin: 0;
457 margin: 0;
460 padding: 0;
458 padding: 0;
461 list-style-type: none;
459 list-style-type: none;
462 overflow-x: visible;
460 overflow-x: visible;
463 overflow-y: scroll;
461 overflow-y: scroll;
464
462
465 li {
463 li {
466 padding: 0 8px;
464 padding: 0 8px;
467 line-height: 1em;
465 line-height: 1em;
468 color: @grey6;
466 color: @grey6;
469
467
470 &:before { content: none; }
468 &:before { content: none; }
471
469
472 &>.select2-result-label {
470 &>.select2-result-label {
473 padding: 8px 0;
471 padding: 8px 0;
474 border-bottom: @border-thickness solid @grey3;
472 border-bottom: @border-thickness solid @grey3;
475 white-space: nowrap;
473 white-space: nowrap;
476 color: @grey5;
474 color: @grey5;
477 cursor: pointer;
475 cursor: pointer;
478 }
476 }
479
477
480 &.select2-result-with-children {
478 &.select2-result-with-children {
481 margin: 0;
479 margin: 0;
482 padding: 0;
480 padding: 0;
483 }
481 }
484
482
485 &.select2-result-unselectable > .select2-result-label {
483 &.select2-result-unselectable > .select2-result-label {
486 margin: 0 8px;
484 margin: 0 8px;
487 }
485 }
488
486
489 }
487 }
490 }
488 }
491
489
492 ul.select2-result-sub {
490 ul.select2-result-sub {
493 margin: 0;
491 margin: 0;
494 padding: 0;
492 padding: 0;
495
493
496 li {
494 li {
497 display: block;
495 display: block;
498 margin: 0;
496 margin: 0;
499 border-right: none;
497 border-right: none;
500 line-height: 1em;
498 line-height: 1em;
501 font-family: @text-light;
499 font-family: @text-light;
502 color: @grey2;
500 color: @grey2;
503
501
504 &:before { content: none; }
502 &:before { content: none; }
505
503
506 &:hover {
504 &:hover {
507 background-color: @grey3;
505 background-color: @grey3;
508 }
506 }
509 }
507 }
510 }
508 }
511 }
509 }
512
510
513
511
514 #context-bar {
512 #context-bar {
515 display: block;
513 display: block;
516 margin: 0 auto;
514 margin: 0 auto;
517 padding: 0 @header-padding;
515 padding: 0 @header-padding;
518 background-color: @grey6;
516 background-color: @grey6;
519 border-bottom: @border-thickness solid @grey5;
517 border-bottom: @border-thickness solid @grey5;
520
518
521 .clear {
519 .clear {
522 clear: both;
520 clear: both;
523 }
521 }
524 }
522 }
525
523
526 ul#context-pages {
524 ul#context-pages {
527 li {
525 li {
528 line-height: 1em;
526 line-height: 1em;
529
527
530 &:before { content: none; }
528 &:before { content: none; }
531
529
532 a {
530 a {
533 color: @grey3;
531 color: @grey3;
534 }
532 }
535
533
536 &.active {
534 &.active {
537 // special case, non-variable color
535 // special case, non-variable color
538 border-bottom: 4px solid @nav-grey;
536 border-bottom: 4px solid @nav-grey;
539
537
540 a {
538 a {
541 color: @grey1;
539 color: @grey1;
542 }
540 }
543 }
541 }
544 }
542 }
545 }
543 }
546
544
547 // PAGINATION
545 // PAGINATION
548
546
549 .pagination {
547 .pagination {
550 border: @border-thickness solid @rcblue;
548 border: @border-thickness solid @rcblue;
551 color: @rcblue;
549 color: @rcblue;
552
550
553 .current {
551 .current {
554 color: @grey4;
552 color: @grey4;
555 }
553 }
556 }
554 }
557
555
558 .dataTables_processing {
556 .dataTables_processing {
559 text-align: center;
557 text-align: center;
560 font-size: 1.1em;
558 font-size: 1.1em;
561 position: relative;
559 position: relative;
562 top: 95px;
560 top: 95px;
563 }
561 }
564
562
565 .dataTables_paginate, .pagination-wh {
563 .dataTables_paginate, .pagination-wh {
566 text-align: left;
564 text-align: left;
567 display: inline-block;
565 display: inline-block;
568 border-left: 1px solid @rcblue;
566 border-left: 1px solid @rcblue;
569 float: none;
567 float: none;
570 overflow: hidden;
568 overflow: hidden;
571
569
572 .paginate_button, .pager_curpage,
570 .paginate_button, .pager_curpage,
573 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
571 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
574 display: inline-block;
572 display: inline-block;
575 padding: @menupadding/4 @menupadding;
573 padding: @menupadding/4 @menupadding;
576 border: 1px solid @rcblue;
574 border: 1px solid @rcblue;
577 border-left: 0;
575 border-left: 0;
578 color: @rcblue;
576 color: @rcblue;
579 cursor: pointer;
577 cursor: pointer;
580 float: left;
578 float: left;
581 }
579 }
582
580
583 .pager_curpage, .pager_dotdot,
581 .pager_curpage, .pager_dotdot,
584 .paginate_button.current, .paginate_button.disabled,
582 .paginate_button.current, .paginate_button.disabled,
585 .disabled {
583 .disabled {
586 color: @grey3;
584 color: @grey3;
587 cursor: default;
585 cursor: default;
588 }
586 }
589
587
590 .ellipsis {
588 .ellipsis {
591 display: inline-block;
589 display: inline-block;
592 text-align: left;
590 text-align: left;
593 padding: @menupadding/4 @menupadding;
591 padding: @menupadding/4 @menupadding;
594 border: 1px solid @rcblue;
592 border: 1px solid @rcblue;
595 border-left: 0;
593 border-left: 0;
596 float: left;
594 float: left;
597 }
595 }
598 }
596 }
599
597
600 // SIDEBAR
598 // SIDEBAR
601
599
602 .sidebar {
600 .sidebar {
603 .block-left;
601 .block-left;
604 clear: left;
602 clear: left;
605 max-width: @sidebar-width;
603 max-width: @sidebar-width;
606 margin-right: @sidebarpadding;
604 margin-right: @sidebarpadding;
607 padding-right: @sidebarpadding;
605 padding-right: @sidebarpadding;
608 font-family: @text-regular;
606 font-family: @text-regular;
609 color: @grey1;
607 color: @grey1;
610
608
611 &#graph_nodes {
609 &#graph_nodes {
612 clear:both;
610 clear:both;
613 width: auto;
611 width: auto;
614 margin-left: -100px;
612 margin-left: -100px;
615 padding: 0;
613 padding: 0;
616 border: none;
614 border: none;
617 }
615 }
618
616
619 .nav-pills {
617 .nav-pills {
620 margin: 0;
618 margin: 0;
621 }
619 }
622
620
623 .nav {
621 .nav {
624 list-style: none;
622 list-style: none;
625 padding: 0;
623 padding: 0;
626
624
627 li {
625 li {
628 padding-bottom: @menupadding;
626 padding-bottom: @menupadding;
629 line-height: 1em;
627 line-height: 1em;
630 color: @grey4;
628 color: @grey4;
631
629
632 &.active a {
630 &.active a {
633 color: @grey2;
631 color: @grey2;
634 }
632 }
635
633
636 a {
634 a {
637 color: @grey4;
635 color: @grey4;
638 }
636 }
639
637
640 &:before { content: none; }
638 &:before { content: none; }
641 }
639 }
642
640
643 }
641 }
644 }
642 }
643
644 .main_filter_help_box {
645 padding: 7px 7px;
646 border-top: 1px solid @grey4;
647 border-right: 1px solid @grey4;
648 border-bottom: 1px solid @grey4;
649 display: inline-block;
650 vertical-align: top;
651 margin-left: -5px;
652 background: @grey3;
653 }
654
655 .main_filter_input_box {
656 display: inline-block;
657 }
658
659 .main_filter_box {
660 margin: 9px 0 0 0;
661 }
662
663 #main_filter_help {
664 background: @grey3;
665 border: 1px solid black;
666 position: absolute;
667 white-space: pre-wrap;
668 z-index: 9999;
669 color: @nav-grey;
670 margin: 1px 7px;
671 padding: 0 2px;
672 }
673
674 .main_filter_input {
675 padding: 6px;
676 min-width: 220px;
677 color: @nav-grey;
678 background: @grey3;
679 }
680
681 .main_filter_input::placeholder {
682 color: @nav-grey;
683 opacity: 1;
684 }
@@ -1,545 +1,545 b''
1 //
1 //
2 // Typography
2 // Typography
3 // modified from Bootstrap
3 // modified from Bootstrap
4 // --------------------------------------------------
4 // --------------------------------------------------
5
5
6 // Base
6 // Base
7 body {
7 body {
8 font-size: @basefontsize;
8 font-size: @basefontsize;
9 font-family: @text-light;
9 font-family: @text-light;
10 letter-spacing: .02em;
10 letter-spacing: .02em;
11 color: @grey2;
11 color: @grey2;
12 }
12 }
13
13
14 #content, label{
14 #content, label{
15 font-size: @basefontsize;
15 font-size: @basefontsize;
16 }
16 }
17
17
18 label {
18 label {
19 color: @grey2;
19 color: @grey2;
20 }
20 }
21
21
22 ::selection { background: @rchighlightblue; }
22 ::selection { background: @rchighlightblue; }
23
23
24 // Headings
24 // Headings
25 // -------------------------
25 // -------------------------
26
26
27 h1, h2, h3, h4, h5, h6,
27 h1, h2, h3, h4, h5, h6,
28 .h1, .h2, .h3, .h4, .h5, .h6 {
28 .h1, .h2, .h3, .h4, .h5, .h6 {
29 margin: 0 0 @textmargin 0;
29 margin: 0 0 @textmargin 0;
30 padding: 0;
30 padding: 0;
31 line-height: 1.8em;
31 line-height: 1.8em;
32 color: @text-color;
32 color: @text-color;
33 a {
33 a {
34 color: @rcblue;
34 color: @rcblue;
35 }
35 }
36 }
36 }
37
37
38 h1, .h1 { font-size: 1.54em; font-family: @text-bold; }
38 h1, .h1 { font-size: 1.54em; font-family: @text-bold; }
39 h2, .h2 { font-size: 1.23em; font-family: @text-semibold; }
39 h2, .h2 { font-size: 1.23em; font-family: @text-semibold; }
40 h3, .h3 { font-size: 1.23em; font-family: @text-regular; }
40 h3, .h3 { font-size: 1.23em; font-family: @text-regular; }
41 h4, .h4 { font-size: 1em; font-family: @text-bold; }
41 h4, .h4 { font-size: 1em; font-family: @text-bold; }
42 h5, .h5 { font-size: 1em; font-family: @text-bold-italic; }
42 h5, .h5 { font-size: 1em; font-family: @text-bold-italic; }
43 h6, .h6 { font-size: 1em; font-family: @text-bold-italic; }
43 h6, .h6 { font-size: 1em; font-family: @text-bold-italic; }
44
44
45 // Breadcrumbs
45 // Breadcrumbs
46 .breadcrumbs {
46 .breadcrumbs {
47 &:extend(h1);
47 &:extend(h1);
48 margin: 0;
48 margin: 0;
49 }
49 }
50
50
51 .breadcrumbs_light {
51 .breadcrumbs_light {
52 float:left;
52 float:left;
53 font-size: 1.3em;
53 font-size: 1.3em;
54 line-height: 38px;
54 line-height: 38px;
55 }
55 }
56
56
57 // Body text
57 // Body text
58 // -------------------------
58 // -------------------------
59
59
60 p {
60 p {
61 margin: 0 0 @textmargin 0;
61 margin: 0 0 @textmargin 0;
62 padding: 0;
62 padding: 0;
63 line-height: 2em;
63 line-height: 2em;
64 }
64 }
65
65
66 .lead {
66 .lead {
67 margin-bottom: @textmargin;
67 margin-bottom: @textmargin;
68 font-weight: 300;
68 font-weight: 300;
69 line-height: 1.4;
69 line-height: 1.4;
70
70
71 @media (min-width: @screen-sm-min) {
71 @media (min-width: @screen-sm-min) {
72 font-size: (@basefontsize * 1.5);
72 font-size: (@basefontsize * 1.5);
73 }
73 }
74 }
74 }
75
75
76 a,
76 a,
77 .link {
77 .link {
78 color: @rcblue;
78 color: @rcblue;
79 text-decoration: none;
79 text-decoration: none;
80 outline: none;
80 outline: none;
81 cursor: pointer;
81 cursor: pointer;
82
82
83 &:focus {
83 &:focus {
84 outline: none;
84 outline: none;
85 }
85 }
86
86
87 &:hover {
87 &:hover {
88 color: @rcdarkblue;
88 color: @rcdarkblue;
89 }
89 }
90 }
90 }
91
91
92 img {
92 img {
93 border: none;
93 border: none;
94 outline: none;
94 outline: none;
95 }
95 }
96
96
97 strong {
97 strong {
98 font-family: @text-bold;
98 font-family: @text-bold;
99 }
99 }
100
100
101 em {
101 em {
102 font-family: @text-italic;
102 font-family: @text-italic;
103 }
103 }
104
104
105 strong em,
105 strong em,
106 em strong {
106 em strong {
107 font-family: @text-bold-italic;
107 font-family: @text-bold-italic;
108 }
108 }
109
109
110 //TODO: lisa: b and i are depreciated, but we are still using them in places.
110 //TODO: lisa: b and i are depreciated, but we are still using them in places.
111 // Should probably make some decision whether to keep or lose these.
111 // Should probably make some decision whether to keep or lose these.
112 b {
112 b {
113
113
114 }
114 }
115
115
116 i {
116 i {
117 font-style: normal;
117 font-style: normal;
118 }
118 }
119
119
120 label {
120 label {
121 color: @text-color;
121 color: @text-color;
122
122
123 input[type="checkbox"] {
123 input[type="checkbox"] {
124 margin-right: 1em;
124 margin-right: 1em;
125 }
125 }
126 input[type="radio"] {
126 input[type="radio"] {
127 margin-right: 1em;
127 margin-right: 1em;
128 }
128 }
129 }
129 }
130
130
131 code,
131 code,
132 .code {
132 .code {
133 font-size: .95em;
133 font-size: .95em;
134 font-family: @text-code;
134 font-family: @text-code;
135 color: @grey3;
135 color: @grey3;
136
136
137 a {
137 a {
138 color: lighten(@rcblue,10%)
138 color: lighten(@rcblue,10%)
139 }
139 }
140 }
140 }
141
141
142 pre {
142 pre {
143 margin: 0;
143 margin: 0;
144 padding: 0;
144 padding: 0;
145 border: 0;
145 border: 0;
146 outline: 0;
146 outline: 0;
147 font-size: @basefontsize*.95;
147 font-size: @basefontsize*.95;
148 line-height: 1.4em;
148 line-height: 1.4em;
149 font-family: @text-code;
149 font-family: @text-code;
150 color: @grey3;
150 color: @grey3;
151 }
151 }
152
152
153 // Emphasis & misc
153 // Emphasis & misc
154 // -------------------------
154 // -------------------------
155
155
156 small,
156 small,
157 .small {
157 .small {
158 font-size: 75%;
158 font-size: 75%;
159 font-weight: normal;
159 font-weight: normal;
160 line-height: 1em;
160 line-height: 1em;
161 }
161 }
162
162
163 mark,
163 mark,
164 .mark {
164 .mark {
165 background-color: @rclightblue;
165 background-color: @rclightblue;
166 padding: .2em;
166 padding: .2em;
167 }
167 }
168
168
169 // Alignment
169 // Alignment
170 .text-left { text-align: left; }
170 .text-left { text-align: left; }
171 .text-right { text-align: right; }
171 .text-right { text-align: right; }
172 .text-center { text-align: center; }
172 .text-center { text-align: center; }
173 .text-justify { text-align: justify; }
173 .text-justify { text-align: justify; }
174 .text-nowrap { white-space: nowrap; }
174 .text-nowrap { white-space: nowrap; }
175
175
176 // Transformation
176 // Transformation
177 .text-lowercase { text-transform: lowercase; }
177 .text-lowercase { text-transform: lowercase; }
178 .text-uppercase { text-transform: uppercase; }
178 .text-uppercase { text-transform: uppercase; }
179 .text-capitalize { text-transform: capitalize; }
179 .text-capitalize { text-transform: capitalize; }
180
180
181 // Contextual colors
181 // Contextual colors
182 .text-muted {
182 .text-muted {
183 color: @grey4;
183 color: @grey4;
184 }
184 }
185 .text-primary {
185 .text-primary {
186 color: @rcblue;
186 color: @rcblue;
187 }
187 }
188 .text-success {
188 .text-success {
189 color: @alert1;
189 color: @alert1;
190 }
190 }
191 .text-info {
191 .text-info {
192 color: @alert4;
192 color: @alert4;
193 }
193 }
194 .text-warning {
194 .text-warning {
195 color: @alert3;
195 color: @alert3;
196 }
196 }
197 .text-danger {
197 .text-danger {
198 color: @alert2;
198 color: @alert2;
199 }
199 }
200
200
201 // Contextual backgrounds
201 // Contextual backgrounds
202 .bg-primary {
202 .bg-primary {
203 background-color: white;
203 background-color: white;
204 }
204 }
205 .bg-success {
205 .bg-success {
206 background-color: @alert1;
206 background-color: @alert1;
207 }
207 }
208 .bg-info {
208 .bg-info {
209 background-color: @alert4;
209 background-color: @alert4;
210 }
210 }
211 .bg-warning {
211 .bg-warning {
212 background-color: @alert3;
212 background-color: @alert3;
213 }
213 }
214 .bg-danger {
214 .bg-danger {
215 background-color: @alert2;
215 background-color: @alert2;
216 }
216 }
217
217
218
218
219 // Page header
219 // Page header
220 // -------------------------
220 // -------------------------
221
221
222 .page-header {
222 .page-header {
223 margin: @pagepadding 0 @textmargin;
223 margin: @pagepadding 0 @textmargin;
224 border-bottom: @border-thickness solid @grey5;
224 border-bottom: @border-thickness solid @grey5;
225 }
225 }
226
226
227 .title {
227 .title {
228 clear: both;
228 clear: both;
229 float: left;
229 float: left;
230 width: 100%;
230 width: 100%;
231 margin: @pagepadding 0 @pagepadding;
231 margin: @pagepadding/2 0 @pagepadding;
232
232
233 .breadcrumbs{
233 .breadcrumbs{
234 float: left;
234 float: left;
235 clear: both;
235 clear: both;
236 width: 700px;
236 width: 700px;
237 margin: 0;
237 margin: 0;
238
238
239 .q_filter_box {
239 .q_filter_box {
240 margin-right: @padding;
240 margin-right: @padding;
241 }
241 }
242 }
242 }
243
243
244 h1 a {
244 h1 a {
245 color: @rcblue;
245 color: @rcblue;
246 }
246 }
247
247
248 input{
248 input{
249 margin-right: @padding;
249 margin-right: @padding;
250 }
250 }
251
251
252 h5, .h5 {
252 h5, .h5 {
253 color: @grey1;
253 color: @grey1;
254 margin-bottom: @space;
254 margin-bottom: @space;
255
255
256 span {
256 span {
257 display: inline-block;
257 display: inline-block;
258 }
258 }
259 }
259 }
260
260
261 p {
261 p {
262 margin-bottom: 0;
262 margin-bottom: 0;
263 }
263 }
264
264
265 .links {
265 .links {
266 float: right;
266 float: right;
267 display: inline;
267 display: inline;
268 margin: 0;
268 margin: 0;
269 padding-left: 0;
269 padding-left: 0;
270 list-style: none;
270 list-style: none;
271 text-align: right;
271 text-align: right;
272
272
273 li:before { content: none; }
273 li:before { content: none; }
274 li { float: right; }
274 li { float: right; }
275 a {
275 a {
276 display: inline-block;
276 display: inline-block;
277 margin-left: @textmargin/2;
277 margin-left: @textmargin/2;
278 }
278 }
279 }
279 }
280
280
281 .title-content {
281 .title-content {
282 float: left;
282 float: left;
283 margin: 0;
283 margin: 0;
284 padding: 0;
284 padding: 0;
285
285
286 & + .breadcrumbs {
286 & + .breadcrumbs {
287 margin-top: @padding;
287 margin-top: @padding;
288 }
288 }
289
289
290 & + .links {
290 & + .links {
291 margin-top: -@button-padding;
291 margin-top: -@button-padding;
292
292
293 & + .breadcrumbs {
293 & + .breadcrumbs {
294 margin-top: @padding;
294 margin-top: @padding;
295 }
295 }
296 }
296 }
297 }
297 }
298
298
299 .title-main {
299 .title-main {
300 font-size: @repo-title-fontsize;
300 font-size: @repo-title-fontsize;
301 }
301 }
302
302
303 .title-description {
303 .title-description {
304 margin-top: .5em;
304 margin-top: .5em;
305 }
305 }
306
306
307 .q_filter_box {
307 .q_filter_box {
308 width: 200px;
308 width: 200px;
309 }
309 }
310
310
311 }
311 }
312
312
313 #readme .title {
313 #readme .title {
314 text-transform: none;
314 text-transform: none;
315 }
315 }
316
316
317 // Lists
317 // Lists
318 // -------------------------
318 // -------------------------
319
319
320 // Unordered and Ordered lists
320 // Unordered and Ordered lists
321 ul,
321 ul,
322 ol {
322 ol {
323 margin-top: 0;
323 margin-top: 0;
324 margin-bottom: @textmargin;
324 margin-bottom: @textmargin;
325 ul,
325 ul,
326 ol {
326 ol {
327 margin-bottom: 0;
327 margin-bottom: 0;
328 }
328 }
329 }
329 }
330
330
331 li {
331 li {
332 line-height: 2em;
332 line-height: 2em;
333 }
333 }
334
334
335 ul li {
335 ul li {
336 position: relative;
336 position: relative;
337 display: block;
337 display: block;
338 list-style-type: none;
338 list-style-type: none;
339
339
340 &:before {
340 &:before {
341 content: "\2014\00A0";
341 content: "\2014\00A0";
342 position: absolute;
342 position: absolute;
343 top: 0;
343 top: 0;
344 left: -1.25em;
344 left: -1.25em;
345 }
345 }
346
346
347 p:first-child {
347 p:first-child {
348 display:inline;
348 display:inline;
349 }
349 }
350 }
350 }
351
351
352 // List options
352 // List options
353
353
354 // Unstyled keeps list items block level, just removes default browser padding and list-style
354 // Unstyled keeps list items block level, just removes default browser padding and list-style
355 .list-unstyled {
355 .list-unstyled {
356 padding-left: 0;
356 padding-left: 0;
357 list-style: none;
357 list-style: none;
358 li:before { content: none; }
358 li:before { content: none; }
359 }
359 }
360
360
361 // Inline turns list items into inline-block
361 // Inline turns list items into inline-block
362 .list-inline {
362 .list-inline {
363 .list-unstyled();
363 .list-unstyled();
364 margin-left: -5px;
364 margin-left: -5px;
365
365
366 > li {
366 > li {
367 display: inline-block;
367 display: inline-block;
368 padding-left: 5px;
368 padding-left: 5px;
369 padding-right: 5px;
369 padding-right: 5px;
370 }
370 }
371 }
371 }
372
372
373 // Description Lists
373 // Description Lists
374
374
375 dl {
375 dl {
376 margin-top: 0; // Remove browser default
376 margin-top: 0; // Remove browser default
377 margin-bottom: @textmargin;
377 margin-bottom: @textmargin;
378 }
378 }
379
379
380 dt,
380 dt,
381 dd {
381 dd {
382 line-height: 1.4em;
382 line-height: 1.4em;
383 }
383 }
384
384
385 dt {
385 dt {
386 margin: @textmargin 0 0 0;
386 margin: @textmargin 0 0 0;
387 font-family: @text-bold;
387 font-family: @text-bold;
388 }
388 }
389
389
390 dd {
390 dd {
391 margin-left: 0; // Undo browser default
391 margin-left: 0; // Undo browser default
392 }
392 }
393
393
394 // Horizontal description lists
394 // Horizontal description lists
395 // Defaults to being stacked without any of the below styles applied, until the
395 // Defaults to being stacked without any of the below styles applied, until the
396 // grid breakpoint is reached (default of ~768px).
396 // grid breakpoint is reached (default of ~768px).
397 // These are used in forms as well; see style guide.
397 // These are used in forms as well; see style guide.
398 // TODO: lisa: These should really not be used in forms.
398 // TODO: lisa: These should really not be used in forms.
399
399
400 .dl-horizontal {
400 .dl-horizontal {
401
401
402 overflow: hidden;
402 overflow: hidden;
403 margin-bottom: @space;
403 margin-bottom: @space;
404
404
405 dt, dd {
405 dt, dd {
406 float: left;
406 float: left;
407 margin: 5px 0 5px 0;
407 margin: 5px 0 5px 0;
408 }
408 }
409
409
410 dt {
410 dt {
411 clear: left;
411 clear: left;
412 width: @label-width - @form-vertical-margin;
412 width: @label-width - @form-vertical-margin;
413 }
413 }
414
414
415 dd {
415 dd {
416 &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
416 &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
417 margin-left: @form-vertical-margin;
417 margin-left: @form-vertical-margin;
418 max-width: @form-max-width - (@label-width - @form-vertical-margin) - @form-vertical-margin;
418 max-width: @form-max-width - (@label-width - @form-vertical-margin) - @form-vertical-margin;
419 }
419 }
420
420
421 pre {
421 pre {
422 margin: 0;
422 margin: 0;
423 }
423 }
424
424
425 &.settings {
425 &.settings {
426 dt {
426 dt {
427 text-align: left;
427 text-align: left;
428 }
428 }
429 }
429 }
430
430
431 @media (min-width: 768px) {
431 @media (min-width: 768px) {
432 dt {
432 dt {
433 float: left;
433 float: left;
434 width: 180px;
434 width: 180px;
435 clear: left;
435 clear: left;
436 text-align: right;
436 text-align: right;
437 }
437 }
438 dd {
438 dd {
439 margin-left: 20px;
439 margin-left: 20px;
440 }
440 }
441 }
441 }
442 }
442 }
443
443
444
444
445 // Misc
445 // Misc
446 // -------------------------
446 // -------------------------
447
447
448 // Abbreviations and acronyms
448 // Abbreviations and acronyms
449 abbr[title],
449 abbr[title],
450 abbr[data-original-title] {
450 abbr[data-original-title] {
451 cursor: help;
451 cursor: help;
452 border-bottom: @border-thickness dotted @grey4;
452 border-bottom: @border-thickness dotted @grey4;
453 }
453 }
454 .initialism {
454 .initialism {
455 font-size: 90%;
455 font-size: 90%;
456 text-transform: uppercase;
456 text-transform: uppercase;
457 }
457 }
458
458
459 // Blockquotes
459 // Blockquotes
460 blockquote {
460 blockquote {
461 padding: 1em 2em;
461 padding: 1em 2em;
462 margin: 0 0 2em;
462 margin: 0 0 2em;
463 font-size: @basefontsize;
463 font-size: @basefontsize;
464 border-left: 2px solid @grey6;
464 border-left: 2px solid @grey6;
465
465
466 p,
466 p,
467 ul,
467 ul,
468 ol {
468 ol {
469 &:last-child {
469 &:last-child {
470 margin-bottom: 0;
470 margin-bottom: 0;
471 }
471 }
472 }
472 }
473
473
474 footer,
474 footer,
475 small,
475 small,
476 .small {
476 .small {
477 display: block;
477 display: block;
478 font-size: 80%;
478 font-size: 80%;
479
479
480 &:before {
480 &:before {
481 content: '\2014 \00A0'; // em dash, nbsp
481 content: '\2014 \00A0'; // em dash, nbsp
482 }
482 }
483 }
483 }
484 }
484 }
485
485
486 // Opposite alignment of blockquote
486 // Opposite alignment of blockquote
487 //
487 //
488 .blockquote-reverse,
488 .blockquote-reverse,
489 blockquote.pull-right {
489 blockquote.pull-right {
490 padding-right: 15px;
490 padding-right: 15px;
491 padding-left: 0;
491 padding-left: 0;
492 border-right: 5px solid @grey6;
492 border-right: 5px solid @grey6;
493 border-left: 0;
493 border-left: 0;
494 text-align: right;
494 text-align: right;
495
495
496 // Account for citation
496 // Account for citation
497 footer,
497 footer,
498 small,
498 small,
499 .small {
499 .small {
500 &:before { content: ''; }
500 &:before { content: ''; }
501 &:after {
501 &:after {
502 content: '\00A0 \2014'; // nbsp, em dash
502 content: '\00A0 \2014'; // nbsp, em dash
503 }
503 }
504 }
504 }
505 }
505 }
506
506
507 // Addresses
507 // Addresses
508 address {
508 address {
509 margin-bottom: 2em;
509 margin-bottom: 2em;
510 font-style: normal;
510 font-style: normal;
511 line-height: 1.8em;
511 line-height: 1.8em;
512 }
512 }
513
513
514 .error-message {
514 .error-message {
515 display: block;
515 display: block;
516 margin: @padding/3 0;
516 margin: @padding/3 0;
517 color: @alert2;
517 color: @alert2;
518 }
518 }
519
519
520 .issue-tracker-link {
520 .issue-tracker-link {
521 color: @rcblue;
521 color: @rcblue;
522 }
522 }
523
523
524 .info_text{
524 .info_text{
525 font-size: @basefontsize;
525 font-size: @basefontsize;
526 color: @grey4;
526 color: @grey4;
527 font-family: @text-regular;
527 font-family: @text-regular;
528 }
528 }
529
529
530 // help block text
530 // help block text
531 .help-block {
531 .help-block {
532 display: block;
532 display: block;
533 margin: 0 0 @padding;
533 margin: 0 0 @padding;
534 color: @grey4;
534 color: @grey4;
535 font-family: @text-light;
535 font-family: @text-light;
536 &.pre-formatting {
536 &.pre-formatting {
537 white-space: pre;
537 white-space: pre;
538 }
538 }
539 }
539 }
540
540
541 .error-message {
541 .error-message {
542 display: block;
542 display: block;
543 margin: @padding/3 0;
543 margin: @padding/3 0;
544 color: @alert2;
544 color: @alert2;
545 }
545 }
@@ -1,101 +1,101 b''
1 // Global keyboard bindings
1 // Global keyboard bindings
2
2
3 function setRCMouseBindings(repoName, repoLandingRev) {
3 function setRCMouseBindings(repoName, repoLandingRev) {
4
4
5 /** custom callback for supressing mousetrap from firing */
5 /** custom callback for supressing mousetrap from firing */
6 Mousetrap.stopCallback = function(e, element) {
6 Mousetrap.stopCallback = function(e, element) {
7 // if the element has the class "mousetrap" then no need to stop
7 // if the element has the class "mousetrap" then no need to stop
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
9 return false;
9 return false;
10 }
10 }
11
11
12 // stop for input, select, and textarea
12 // stop for input, select, and textarea
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
14 };
14 };
15
15
16 // general help "?"
16 // general help "?"
17 Mousetrap.bind(['?'], function(e) {
17 Mousetrap.bind(['?'], function(e) {
18 $('#help_kb').modal({});
18 $('#help_kb').modal({});
19 });
19 });
20
20
21 // / open the quick filter
21 // / open the quick filter
22 Mousetrap.bind(['/'], function(e) {
22 Mousetrap.bind(['/'], function(e) {
23 $('#repo_switcher').select2('open');
23 $('#main_filter').get(0).focus();
24
24
25 // return false to prevent default browser behavior
25 // return false to prevent default browser behavior
26 // and stop event from bubbling
26 // and stop event from bubbling
27 return false;
27 return false;
28 });
28 });
29
29
30 // ctrl/command+b, show the the main bar
30 // ctrl/command+b, show the the main bar
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
32 var $headerInner = $('#header-inner'),
32 var $headerInner = $('#header-inner'),
33 $content = $('#content');
33 $content = $('#content');
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
35 $headerInner.removeClass('hover');
35 $headerInner.removeClass('hover');
36 $content.removeClass('hover');
36 $content.removeClass('hover');
37 } else {
37 } else {
38 $headerInner.addClass('hover');
38 $headerInner.addClass('hover');
39 $content.addClass('hover');
39 $content.addClass('hover');
40 }
40 }
41 return false;
41 return false;
42 });
42 });
43
43
44 // general nav g + action
44 // general nav g + action
45 Mousetrap.bind(['g h'], function(e) {
45 Mousetrap.bind(['g h'], function(e) {
46 window.location = pyroutes.url('home');
46 window.location = pyroutes.url('home');
47 });
47 });
48 Mousetrap.bind(['g g'], function(e) {
48 Mousetrap.bind(['g g'], function(e) {
49 window.location = pyroutes.url('gists_show', {'private': 1});
49 window.location = pyroutes.url('gists_show', {'private': 1});
50 });
50 });
51 Mousetrap.bind(['g G'], function(e) {
51 Mousetrap.bind(['g G'], function(e) {
52 window.location = pyroutes.url('gists_show', {'public': 1});
52 window.location = pyroutes.url('gists_show', {'public': 1});
53 });
53 });
54 Mousetrap.bind(['n g'], function(e) {
54 Mousetrap.bind(['n g'], function(e) {
55 window.location = pyroutes.url('gists_new');
55 window.location = pyroutes.url('gists_new');
56 });
56 });
57 Mousetrap.bind(['n r'], function(e) {
57 Mousetrap.bind(['n r'], function(e) {
58 window.location = pyroutes.url('repo_new');
58 window.location = pyroutes.url('repo_new');
59 });
59 });
60
60
61 if (repoName && repoName != '') {
61 if (repoName && repoName != '') {
62 // nav in repo context
62 // nav in repo context
63 Mousetrap.bind(['g s'], function(e) {
63 Mousetrap.bind(['g s'], function(e) {
64 window.location = pyroutes.url(
64 window.location = pyroutes.url(
65 'repo_summary', {'repo_name': repoName});
65 'repo_summary', {'repo_name': repoName});
66 });
66 });
67 Mousetrap.bind(['g c'], function(e) {
67 Mousetrap.bind(['g c'], function(e) {
68 window.location = pyroutes.url(
68 window.location = pyroutes.url(
69 'repo_changelog', {'repo_name': repoName});
69 'repo_changelog', {'repo_name': repoName});
70 });
70 });
71 Mousetrap.bind(['g F'], function(e) {
71 Mousetrap.bind(['g F'], function(e) {
72 window.location = pyroutes.url(
72 window.location = pyroutes.url(
73 'repo_files',
73 'repo_files',
74 {
74 {
75 'repo_name': repoName,
75 'repo_name': repoName,
76 'commit_id': repoLandingRev,
76 'commit_id': repoLandingRev,
77 'f_path': '',
77 'f_path': '',
78 'search': '1'
78 'search': '1'
79 });
79 });
80 });
80 });
81 Mousetrap.bind(['g f'], function(e) {
81 Mousetrap.bind(['g f'], function(e) {
82 window.location = pyroutes.url(
82 window.location = pyroutes.url(
83 'repo_files',
83 'repo_files',
84 {
84 {
85 'repo_name': repoName,
85 'repo_name': repoName,
86 'commit_id': repoLandingRev,
86 'commit_id': repoLandingRev,
87 'f_path': ''
87 'f_path': ''
88 });
88 });
89 });
89 });
90 Mousetrap.bind(['g o'], function(e) {
90 Mousetrap.bind(['g o'], function(e) {
91 window.location = pyroutes.url(
91 window.location = pyroutes.url(
92 'edit_repo', {'repo_name': repoName});
92 'edit_repo', {'repo_name': repoName});
93 });
93 });
94 Mousetrap.bind(['g O'], function(e) {
94 Mousetrap.bind(['g O'], function(e) {
95 window.location = pyroutes.url(
95 window.location = pyroutes.url(
96 'edit_repo_perms', {'repo_name': repoName});
96 'edit_repo_perms', {'repo_name': repoName});
97 });
97 });
98 }
98 }
99 }
99 }
100
100
101 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
101 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
@@ -1,188 +1,188 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <div class="apikeys_wrap">
7 <p>
7 <p>
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 </p>
10 </p>
11 <table class="rctable auth_tokens">
11 <table class="rctable auth_tokens">
12 <tr>
12 <tr>
13 <th>${_('Token')}</th>
13 <th>${_('Token')}</th>
14 <th>${_('Scope')}</th>
14 <th>${_('Scope')}</th>
15 <th>${_('Description')}</th>
15 <th>${_('Description')}</th>
16 <th>${_('Role')}</th>
16 <th>${_('Role')}</th>
17 <th>${_('Expiration')}</th>
17 <th>${_('Expiration')}</th>
18 <th>${_('Action')}</th>
18 <th>${_('Action')}</th>
19 </tr>
19 </tr>
20 %if c.user_auth_tokens:
20 %if c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
22 <tr class="${'expired' if auth_token.expired else ''}">
22 <tr class="${'expired' if auth_token.expired else ''}">
23 <td class="truncate-wrap td-authtoken">
23 <td class="truncate-wrap td-authtoken">
24 <div class="user_auth_tokens truncate autoexpand">
24 <div class="user_auth_tokens truncate autoexpand">
25 <code>${auth_token.api_key}</code>
25 <code>${auth_token.api_key}</code>
26 </div>
26 </div>
27 </td>
27 </td>
28 <td class="td">${auth_token.scope_humanized}</td>
28 <td class="td">${auth_token.scope_humanized}</td>
29 <td class="td-wrap">${auth_token.description}</td>
29 <td class="td-wrap">${auth_token.description}</td>
30 <td class="td-tags">
30 <td class="td-tags">
31 <span class="tag disabled">${auth_token.role_humanized}</span>
31 <span class="tag disabled">${auth_token.role_humanized}</span>
32 </td>
32 </td>
33 <td class="td-exp">
33 <td class="td-exp">
34 %if auth_token.expires == -1:
34 %if auth_token.expires == -1:
35 ${_('never')}
35 ${_('never')}
36 %else:
36 %else:
37 %if auth_token.expired:
37 %if auth_token.expired:
38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
39 %else:
39 %else:
40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
41 %endif
41 %endif
42 %endif
42 %endif
43 </td>
43 </td>
44 <td class="td-action">
44 <td class="td-action">
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
46 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
46 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
47 <button class="btn btn-link btn-danger" type="submit"
47 <button class="btn btn-link btn-danger" type="submit"
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
49 ${_('Delete')}
49 ${_('Delete')}
50 </button>
50 </button>
51 ${h.end_form()}
51 ${h.end_form()}
52 </td>
52 </td>
53 </tr>
53 </tr>
54 %endfor
54 %endfor
55 %else:
55 %else:
56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
57 %endif
57 %endif
58 </table>
58 </table>
59 </div>
59 </div>
60
60
61 <div class="user_auth_tokens">
61 <div class="user_auth_tokens">
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
63 <div class="form form-vertical">
63 <div class="form form-vertical">
64 <!-- fields -->
64 <!-- fields -->
65 <div class="fields">
65 <div class="fields">
66 <div class="field">
66 <div class="field">
67 <div class="label">
67 <div class="label">
68 <label for="new_email">${_('New authentication token')}:</label>
68 <label for="new_email">${_('New authentication token')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 ${h.text('description', class_='medium', placeholder=_('Description'))}
71 ${h.text('description', class_='medium', placeholder=_('Description'))}
72 ${h.hidden('lifetime')}
72 ${h.hidden('lifetime')}
73 ${h.select('role', '', c.role_options)}
73 ${h.select('role', '', c.role_options)}
74
74
75 % if c.allow_scoped_tokens:
75 % if c.allow_scoped_tokens:
76 ${h.hidden('scope_repo_id')}
76 ${h.hidden('scope_repo_id')}
77 % else:
77 % else:
78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
79 % endif
79 % endif
80 </div>
80 </div>
81 <p class="help-block">
81 <p class="help-block">
82 ${_('Repository scope works only with tokens with VCS type.')}
82 ${_('Repository scope works only with tokens with VCS type.')}
83 </p>
83 </p>
84 </div>
84 </div>
85 <div class="buttons">
85 <div class="buttons">
86 ${h.submit('save',_('Add'),class_="btn")}
86 ${h.submit('save',_('Add'),class_="btn")}
87 ${h.reset('reset',_('Reset'),class_="btn")}
87 ${h.reset('reset',_('Reset'),class_="btn")}
88 </div>
88 </div>
89 </div>
89 </div>
90 </div>
90 </div>
91 ${h.end_form()}
91 ${h.end_form()}
92 </div>
92 </div>
93 </div>
93 </div>
94 </div>
94 </div>
95 <script>
95 <script>
96 $(document).ready(function(){
96 $(document).ready(function(){
97
97
98 var select2Options = {
98 var select2Options = {
99 'containerCssClass': "drop-menu",
99 'containerCssClass': "drop-menu",
100 'dropdownCssClass': "drop-menu-dropdown",
100 'dropdownCssClass': "drop-menu-dropdown",
101 'dropdownAutoWidth': true
101 'dropdownAutoWidth': true
102 };
102 };
103 $("#role").select2(select2Options);
103 $("#role").select2(select2Options);
104
104
105 var preloadData = {
105 var preloadData = {
106 results: [
106 results: [
107 % for entry in c.lifetime_values:
107 % for entry in c.lifetime_values:
108 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
108 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
109 % endfor
109 % endfor
110 ]
110 ]
111 };
111 };
112
112
113 $("#lifetime").select2({
113 $("#lifetime").select2({
114 containerCssClass: "drop-menu",
114 containerCssClass: "drop-menu",
115 dropdownCssClass: "drop-menu-dropdown",
115 dropdownCssClass: "drop-menu-dropdown",
116 dropdownAutoWidth: true,
116 dropdownAutoWidth: true,
117 data: preloadData,
117 data: preloadData,
118 placeholder: "${_('Select or enter expiration date')}",
118 placeholder: "${_('Select or enter expiration date')}",
119 query: function(query) {
119 query: function(query) {
120 feedLifetimeOptions(query, preloadData);
120 feedLifetimeOptions(query, preloadData);
121 }
121 }
122 });
122 });
123
123
124
124
125 var repoFilter = function(data) {
125 var repoFilter = function(data) {
126 var results = [];
126 var results = [];
127
127
128 if (!data.results[0]) {
128 if (!data.results[0]) {
129 return data
129 return data
130 }
130 }
131
131
132 $.each(data.results[0].children, function() {
132 $.each(data.results[0].children, function() {
133 // replace name to ID for submision
133 // replace name to ID for submision
134 this.id = this.obj.repo_id;
134 this.id = this.repo_id;
135 results.push(this);
135 results.push(this);
136 });
136 });
137
137
138 data.results[0].children = results;
138 data.results[0].children = results;
139 return data;
139 return data;
140 };
140 };
141
141
142 $("#scope_repo_id_disabled").select2(select2Options);
142 $("#scope_repo_id_disabled").select2(select2Options);
143
143
144 var selectVcsScope = function() {
144 var selectVcsScope = function() {
145 // select vcs scope and disable input
145 // select vcs scope and disable input
146 $("#role").select2("val", "${c.role_vcs}").trigger('change');
146 $("#role").select2("val", "${c.role_vcs}").trigger('change');
147 $("#role").select2("readonly", true)
147 $("#role").select2("readonly", true)
148 };
148 };
149
149
150 $("#scope_repo_id").select2({
150 $("#scope_repo_id").select2({
151 cachedDataSource: {},
151 cachedDataSource: {},
152 minimumInputLength: 2,
152 minimumInputLength: 2,
153 placeholder: "${_('repository scope')}",
153 placeholder: "${_('repository scope')}",
154 dropdownAutoWidth: true,
154 dropdownAutoWidth: true,
155 containerCssClass: "drop-menu",
155 containerCssClass: "drop-menu",
156 dropdownCssClass: "drop-menu-dropdown",
156 dropdownCssClass: "drop-menu-dropdown",
157 formatResult: formatResult,
157 formatResult: formatRepoResult,
158 query: $.debounce(250, function(query){
158 query: $.debounce(250, function(query){
159 self = this;
159 self = this;
160 var cacheKey = query.term;
160 var cacheKey = query.term;
161 var cachedData = self.cachedDataSource[cacheKey];
161 var cachedData = self.cachedDataSource[cacheKey];
162
162
163 if (cachedData) {
163 if (cachedData) {
164 query.callback({results: cachedData.results});
164 query.callback({results: cachedData.results});
165 } else {
165 } else {
166 $.ajax({
166 $.ajax({
167 url: pyroutes.url('repo_list_data'),
167 url: pyroutes.url('repo_list_data'),
168 data: {'query': query.term},
168 data: {'query': query.term},
169 dataType: 'json',
169 dataType: 'json',
170 type: 'GET',
170 type: 'GET',
171 success: function(data) {
171 success: function(data) {
172 data = repoFilter(data);
172 data = repoFilter(data);
173 self.cachedDataSource[cacheKey] = data;
173 self.cachedDataSource[cacheKey] = data;
174 query.callback({results: data.results});
174 query.callback({results: data.results});
175 },
175 },
176 error: function(data, textStatus, errorThrown) {
176 error: function(data, textStatus, errorThrown) {
177 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
177 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
178 }
178 }
179 })
179 })
180 }
180 }
181 })
181 })
182 });
182 });
183 $("#scope_repo_id").on('select2-selecting', function(e){
183 $("#scope_repo_id").on('select2-selecting', function(e){
184 selectVcsScope()
184 selectVcsScope()
185 });
185 });
186
186
187 });
187 });
188 </script>
188 </script>
@@ -1,211 +1,211 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email), '', ''),
5 (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email), '', ''),
6 (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''),
6 (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''),
7 (_('Updated on'), h.format_date(c.rhodecode_db_repo.updated_on), '', ''),
7 (_('Updated on'), h.format_date(c.rhodecode_db_repo.updated_on), '', ''),
8 (_('Cached Commit id'), lambda: h.link_to(c.rhodecode_db_repo.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.changeset_cache.get('raw_id'))), '', ''),
8 (_('Cached Commit id'), lambda: h.link_to(c.rhodecode_db_repo.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.changeset_cache.get('raw_id'))), '', ''),
9 (_('Attached scoped tokens'), len(c.rhodecode_db_repo.scoped_tokens), '', [x.user for x in c.rhodecode_db_repo.scoped_tokens]),
9 (_('Attached scoped tokens'), len(c.rhodecode_db_repo.scoped_tokens), '', [x.user for x in c.rhodecode_db_repo.scoped_tokens]),
10 ]
10 ]
11 %>
11 %>
12
12
13 <div class="panel panel-default">
13 <div class="panel panel-default">
14 <div class="panel-heading" id="advanced-info" >
14 <div class="panel-heading" id="advanced-info" >
15 <h3 class="panel-title">${_('Repository: %s') % c.rhodecode_db_repo.repo_name} <a class="permalink" href="#advanced-info"> ΒΆ</a></h3>
15 <h3 class="panel-title">${_('Repository: %s') % c.rhodecode_db_repo.repo_name} <a class="permalink" href="#advanced-info"> ΒΆ</a></h3>
16 </div>
16 </div>
17 <div class="panel-body">
17 <div class="panel-body">
18 ${base.dt_info_panel(elems)}
18 ${base.dt_info_panel(elems)}
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22
22
23 <div class="panel panel-default">
23 <div class="panel panel-default">
24 <div class="panel-heading" id="advanced-fork">
24 <div class="panel-heading" id="advanced-fork">
25 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ΒΆ</a></h3>
25 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ΒΆ</a></h3>
26 </div>
26 </div>
27 <div class="panel-body">
27 <div class="panel-body">
28 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
28 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
29
29
30 % if c.rhodecode_db_repo.fork:
30 % if c.rhodecode_db_repo.fork:
31 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.rhodecode_db_repo.fork.repo_name, h.route_path('repo_summary', repo_name=c.rhodecode_db_repo.fork.repo_name))})}
31 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.rhodecode_db_repo.fork.repo_name, h.route_path('repo_summary', repo_name=c.rhodecode_db_repo.fork.repo_name))})}
32 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
32 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
33 % endif
33 % endif
34
34
35 <div class="field">
35 <div class="field">
36 ${h.hidden('id_fork_of')}
36 ${h.hidden('id_fork_of')}
37 ${h.submit('set_as_fork_%s' % c.rhodecode_db_repo.repo_name,_('Set'),class_="btn btn-small",)}
37 ${h.submit('set_as_fork_%s' % c.rhodecode_db_repo.repo_name,_('Set'),class_="btn btn-small",)}
38 </div>
38 </div>
39 <div class="field">
39 <div class="field">
40 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
40 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
41 </div>
41 </div>
42 ${h.end_form()}
42 ${h.end_form()}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46
46
47 <div class="panel panel-default">
47 <div class="panel panel-default">
48 <div class="panel-heading" id="advanced-journal">
48 <div class="panel-heading" id="advanced-journal">
49 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ΒΆ</a></h3>
49 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ΒΆ</a></h3>
50 </div>
50 </div>
51 <div class="panel-body">
51 <div class="panel-body">
52 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
52 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
53 <div class="field">
53 <div class="field">
54 %if c.in_public_journal:
54 %if c.in_public_journal:
55 <button class="btn btn-small" type="submit">
55 <button class="btn btn-small" type="submit">
56 ${_('Remove from Public Journal')}
56 ${_('Remove from Public Journal')}
57 </button>
57 </button>
58 %else:
58 %else:
59 <button class="btn btn-small" type="submit">
59 <button class="btn btn-small" type="submit">
60 ${_('Add to Public Journal')}
60 ${_('Add to Public Journal')}
61 </button>
61 </button>
62 %endif
62 %endif
63 </div>
63 </div>
64 <div class="field" >
64 <div class="field" >
65 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
65 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
66 </div>
66 </div>
67 ${h.end_form()}
67 ${h.end_form()}
68 </div>
68 </div>
69 </div>
69 </div>
70
70
71
71
72 <div class="panel panel-default">
72 <div class="panel panel-default">
73 <div class="panel-heading" id="advanced-locking">
73 <div class="panel-heading" id="advanced-locking">
74 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ΒΆ</a></h3>
74 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ΒΆ</a></h3>
75 </div>
75 </div>
76 <div class="panel-body">
76 <div class="panel-body">
77 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
77 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
78
78
79 %if c.rhodecode_db_repo.locked[0]:
79 %if c.rhodecode_db_repo.locked[0]:
80 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.rhodecode_db_repo.locked[0]),
80 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.rhodecode_db_repo.locked[0]),
81 h.format_date(h. time_to_datetime(c.rhodecode_db_repo.locked[1])), c.rhodecode_db_repo.locked[2])}</div>
81 h.format_date(h. time_to_datetime(c.rhodecode_db_repo.locked[1])), c.rhodecode_db_repo.locked[2])}</div>
82 %else:
82 %else:
83 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
83 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
84 %endif
84 %endif
85
85
86 <div class="field" >
86 <div class="field" >
87 %if c.rhodecode_db_repo.locked[0]:
87 %if c.rhodecode_db_repo.locked[0]:
88 ${h.hidden('set_unlock', '1')}
88 ${h.hidden('set_unlock', '1')}
89 <button class="btn btn-small" type="submit"
89 <button class="btn btn-small" type="submit"
90 onclick="return confirm('${_('Confirm to unlock repository.')}');">
90 onclick="return confirm('${_('Confirm to unlock repository.')}');">
91 <i class="icon-unlock"></i>
91 <i class="icon-unlock"></i>
92 ${_('Unlock repository')}
92 ${_('Unlock repository')}
93 </button>
93 </button>
94 %else:
94 %else:
95 ${h.hidden('set_lock', '1')}
95 ${h.hidden('set_lock', '1')}
96 <button class="btn btn-small" type="submit"
96 <button class="btn btn-small" type="submit"
97 onclick="return confirm('${_('Confirm to lock repository.')}');">
97 onclick="return confirm('${_('Confirm to lock repository.')}');">
98 <i class="icon-lock"></i>
98 <i class="icon-lock"></i>
99 ${_('Lock Repository')}
99 ${_('Lock Repository')}
100 </button>
100 </button>
101 %endif
101 %endif
102 </div>
102 </div>
103 <div class="field" >
103 <div class="field" >
104 <span class="help-block">
104 <span class="help-block">
105 ${_('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.')}
105 ${_('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.')}
106 </span>
106 </span>
107 </div>
107 </div>
108 ${h.end_form()}
108 ${h.end_form()}
109 </div>
109 </div>
110 </div>
110 </div>
111
111
112 <div class="panel panel-danger">
112 <div class="panel panel-danger">
113 <div class="panel-heading" id="advanced-delete">
113 <div class="panel-heading" id="advanced-delete">
114 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ΒΆ</a></h3>
114 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ΒΆ</a></h3>
115 </div>
115 </div>
116 <div class="panel-body">
116 <div class="panel-body">
117 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), request=request)}
117 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), request=request)}
118 <table class="display">
118 <table class="display">
119 <tr>
119 <tr>
120 <td>
120 <td>
121 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.rhodecode_db_repo.forks.count()) % c.rhodecode_db_repo.forks.count()}
121 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.rhodecode_db_repo.forks.count()) % c.rhodecode_db_repo.forks.count()}
122 </td>
122 </td>
123 <td>
123 <td>
124 %if c.rhodecode_db_repo.forks.count():
124 %if c.rhodecode_db_repo.forks.count():
125 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
125 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
126 %endif
126 %endif
127 </td>
127 </td>
128 <td>
128 <td>
129 %if c.rhodecode_db_repo.forks.count():
129 %if c.rhodecode_db_repo.forks.count():
130 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
130 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
131 %endif
131 %endif
132 </td>
132 </td>
133 </tr>
133 </tr>
134 </table>
134 </table>
135 <div style="margin: 0 0 20px 0" class="fake-space"></div>
135 <div style="margin: 0 0 20px 0" class="fake-space"></div>
136
136
137 <div class="field">
137 <div class="field">
138 <button class="btn btn-small btn-danger" type="submit"
138 <button class="btn btn-small btn-danger" type="submit"
139 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
139 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
140 <i class="icon-remove-sign"></i>
140 <i class="icon-remove-sign"></i>
141 ${_('Delete This Repository')}
141 ${_('Delete This Repository')}
142 </button>
142 </button>
143 </div>
143 </div>
144 <div class="field">
144 <div class="field">
145 <span class="help-block">
145 <span class="help-block">
146 ${_('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 available in rhodecode-tools.')}
146 ${_('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 available in rhodecode-tools.')}
147 </span>
147 </span>
148 </div>
148 </div>
149
149
150 ${h.end_form()}
150 ${h.end_form()}
151 </div>
151 </div>
152 </div>
152 </div>
153
153
154
154
155 <script>
155 <script>
156
156
157 var currentRepoId = ${c.rhodecode_db_repo.repo_id};
157 var currentRepoId = ${c.rhodecode_db_repo.repo_id};
158
158
159 var repoTypeFilter = function(data) {
159 var repoTypeFilter = function(data) {
160 var results = [];
160 var results = [];
161
161
162 if (!data.results[0]) {
162 if (!data.results[0]) {
163 return data
163 return data
164 }
164 }
165
165
166 $.each(data.results[0].children, function() {
166 $.each(data.results[0].children, function() {
167 // filter out the SAME repo, it cannot be used as fork of itself
167 // filter out the SAME repo, it cannot be used as fork of itself
168 if (this.obj.repo_id != currentRepoId) {
168 if (this.repo_id != currentRepoId) {
169 this.id = this.obj.repo_id;
169 this.id = this.repo_id;
170 results.push(this)
170 results.push(this)
171 }
171 }
172 });
172 });
173 data.results[0].children = results;
173 data.results[0].children = results;
174 return data;
174 return data;
175 };
175 };
176
176
177 $("#id_fork_of").select2({
177 $("#id_fork_of").select2({
178 cachedDataSource: {},
178 cachedDataSource: {},
179 minimumInputLength: 2,
179 minimumInputLength: 2,
180 placeholder: "${_('Change repository') if c.rhodecode_db_repo.fork else _('Pick repository')}",
180 placeholder: "${_('Change repository') if c.rhodecode_db_repo.fork else _('Pick repository')}",
181 dropdownAutoWidth: true,
181 dropdownAutoWidth: true,
182 containerCssClass: "drop-menu",
182 containerCssClass: "drop-menu",
183 dropdownCssClass: "drop-menu-dropdown",
183 dropdownCssClass: "drop-menu-dropdown",
184 formatResult: formatResult,
184 formatResult: formatRepoResult,
185 query: $.debounce(250, function(query){
185 query: $.debounce(250, function(query){
186 self = this;
186 self = this;
187 var cacheKey = query.term;
187 var cacheKey = query.term;
188 var cachedData = self.cachedDataSource[cacheKey];
188 var cachedData = self.cachedDataSource[cacheKey];
189
189
190 if (cachedData) {
190 if (cachedData) {
191 query.callback({results: cachedData.results});
191 query.callback({results: cachedData.results});
192 } else {
192 } else {
193 $.ajax({
193 $.ajax({
194 url: pyroutes.url('repo_list_data'),
194 url: pyroutes.url('repo_list_data'),
195 data: {'query': query.term, repo_type: '${c.rhodecode_db_repo.repo_type}'},
195 data: {'query': query.term, repo_type: '${c.rhodecode_db_repo.repo_type}'},
196 dataType: 'json',
196 dataType: 'json',
197 type: 'GET',
197 type: 'GET',
198 success: function(data) {
198 success: function(data) {
199 data = repoTypeFilter(data);
199 data = repoTypeFilter(data);
200 self.cachedDataSource[cacheKey] = data;
200 self.cachedDataSource[cacheKey] = data;
201 query.callback({results: data.results});
201 query.callback({results: data.results});
202 },
202 },
203 error: function(data, textStatus, errorThrown) {
203 error: function(data, textStatus, errorThrown) {
204 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
204 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
205 }
205 }
206 })
206 })
207 }
207 }
208 })
208 })
209 });
209 });
210 </script>
210 </script>
211
211
@@ -1,186 +1,186 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <div class="apikeys_wrap">
7 <p>
7 <p>
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 </p>
10 </p>
11 <table class="rctable auth_tokens">
11 <table class="rctable auth_tokens">
12 <tr>
12 <tr>
13 <th>${_('Token')}</th>
13 <th>${_('Token')}</th>
14 <th>${_('Scope')}</th>
14 <th>${_('Scope')}</th>
15 <th>${_('Description')}</th>
15 <th>${_('Description')}</th>
16 <th>${_('Role')}</th>
16 <th>${_('Role')}</th>
17 <th>${_('Expiration')}</th>
17 <th>${_('Expiration')}</th>
18 <th>${_('Action')}</th>
18 <th>${_('Action')}</th>
19 </tr>
19 </tr>
20 %if c.user_auth_tokens:
20 %if c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
22 <tr class="${'expired' if auth_token.expired else ''}">
22 <tr class="${'expired' if auth_token.expired else ''}">
23 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
23 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
24 <td class="td">${auth_token.scope_humanized}</td>
24 <td class="td">${auth_token.scope_humanized}</td>
25 <td class="td-wrap">${auth_token.description}</td>
25 <td class="td-wrap">${auth_token.description}</td>
26 <td class="td-tags">
26 <td class="td-tags">
27 <span class="tag disabled">${auth_token.role_humanized}</span>
27 <span class="tag disabled">${auth_token.role_humanized}</span>
28 </td>
28 </td>
29 <td class="td-exp">
29 <td class="td-exp">
30 %if auth_token.expires == -1:
30 %if auth_token.expires == -1:
31 ${_('never')}
31 ${_('never')}
32 %else:
32 %else:
33 %if auth_token.expired:
33 %if auth_token.expired:
34 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
34 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
35 %else:
35 %else:
36 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
36 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
37 %endif
37 %endif
38 %endif
38 %endif
39 </td>
39 </td>
40 <td class="td-action">
40 <td class="td-action">
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
42 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
42 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
43 <button class="btn btn-link btn-danger" type="submit"
43 <button class="btn btn-link btn-danger" type="submit"
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
45 ${_('Delete')}
45 ${_('Delete')}
46 </button>
46 </button>
47 ${h.end_form()}
47 ${h.end_form()}
48 </td>
48 </td>
49 </tr>
49 </tr>
50 %endfor
50 %endfor
51 %else:
51 %else:
52 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
52 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
53 %endif
53 %endif
54 </table>
54 </table>
55 </div>
55 </div>
56
56
57 <div class="user_auth_tokens">
57 <div class="user_auth_tokens">
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), request=request)}
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), request=request)}
59 <div class="form form-vertical">
59 <div class="form form-vertical">
60 <!-- fields -->
60 <!-- fields -->
61 <div class="fields">
61 <div class="fields">
62 <div class="field">
62 <div class="field">
63 <div class="label">
63 <div class="label">
64 <label for="new_email">${_('New authentication token')}:</label>
64 <label for="new_email">${_('New authentication token')}:</label>
65 </div>
65 </div>
66 <div class="input">
66 <div class="input">
67 ${h.text('description', class_='medium', placeholder=_('Description'))}
67 ${h.text('description', class_='medium', placeholder=_('Description'))}
68 ${h.hidden('lifetime')}
68 ${h.hidden('lifetime')}
69 ${h.select('role', '', c.role_options)}
69 ${h.select('role', '', c.role_options)}
70
70
71 % if c.allow_scoped_tokens:
71 % if c.allow_scoped_tokens:
72 ${h.hidden('scope_repo_id')}
72 ${h.hidden('scope_repo_id')}
73 % else:
73 % else:
74 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
74 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
75 % endif
75 % endif
76 </div>
76 </div>
77 <p class="help-block">
77 <p class="help-block">
78 ${_('Repository scope works only with tokens with VCS type.')}
78 ${_('Repository scope works only with tokens with VCS type.')}
79 </p>
79 </p>
80 </div>
80 </div>
81 <div class="buttons">
81 <div class="buttons">
82 ${h.submit('save',_('Add'),class_="btn")}
82 ${h.submit('save',_('Add'),class_="btn")}
83 ${h.reset('reset',_('Reset'),class_="btn")}
83 ${h.reset('reset',_('Reset'),class_="btn")}
84 </div>
84 </div>
85 </div>
85 </div>
86 </div>
86 </div>
87 ${h.end_form()}
87 ${h.end_form()}
88 </div>
88 </div>
89 </div>
89 </div>
90 </div>
90 </div>
91
91
92 <script>
92 <script>
93
93
94 $(document).ready(function(){
94 $(document).ready(function(){
95
95
96 var select2Options = {
96 var select2Options = {
97 'containerCssClass': "drop-menu",
97 'containerCssClass': "drop-menu",
98 'dropdownCssClass': "drop-menu-dropdown",
98 'dropdownCssClass': "drop-menu-dropdown",
99 'dropdownAutoWidth': true
99 'dropdownAutoWidth': true
100 };
100 };
101 $("#role").select2(select2Options);
101 $("#role").select2(select2Options);
102
102
103 var preloadData = {
103 var preloadData = {
104 results: [
104 results: [
105 % for entry in c.lifetime_values:
105 % for entry in c.lifetime_values:
106 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
106 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
107 % endfor
107 % endfor
108 ]
108 ]
109 };
109 };
110
110
111 $("#lifetime").select2({
111 $("#lifetime").select2({
112 containerCssClass: "drop-menu",
112 containerCssClass: "drop-menu",
113 dropdownCssClass: "drop-menu-dropdown",
113 dropdownCssClass: "drop-menu-dropdown",
114 dropdownAutoWidth: true,
114 dropdownAutoWidth: true,
115 data: preloadData,
115 data: preloadData,
116 placeholder: "${_('Select or enter expiration date')}",
116 placeholder: "${_('Select or enter expiration date')}",
117 query: function(query) {
117 query: function(query) {
118 feedLifetimeOptions(query, preloadData);
118 feedLifetimeOptions(query, preloadData);
119 }
119 }
120 });
120 });
121
121
122
122
123 var repoFilter = function(data) {
123 var repoFilter = function(data) {
124 var results = [];
124 var results = [];
125
125
126 if (!data.results[0]) {
126 if (!data.results[0]) {
127 return data
127 return data
128 }
128 }
129
129
130 $.each(data.results[0].children, function() {
130 $.each(data.results[0].children, function() {
131 // replace name to ID for submision
131 // replace name to ID for submision
132 this.id = this.obj.repo_id;
132 this.id = this.repo_id;
133 results.push(this);
133 results.push(this);
134 });
134 });
135
135
136 data.results[0].children = results;
136 data.results[0].children = results;
137 return data;
137 return data;
138 };
138 };
139
139
140 $("#scope_repo_id_disabled").select2(select2Options);
140 $("#scope_repo_id_disabled").select2(select2Options);
141
141
142 var selectVcsScope = function() {
142 var selectVcsScope = function() {
143 // select vcs scope and disable input
143 // select vcs scope and disable input
144 $("#role").select2("val", "${c.role_vcs}").trigger('change');
144 $("#role").select2("val", "${c.role_vcs}").trigger('change');
145 $("#role").select2("readonly", true)
145 $("#role").select2("readonly", true)
146 };
146 };
147
147
148 $("#scope_repo_id").select2({
148 $("#scope_repo_id").select2({
149 cachedDataSource: {},
149 cachedDataSource: {},
150 minimumInputLength: 2,
150 minimumInputLength: 2,
151 placeholder: "${_('repository scope')}",
151 placeholder: "${_('repository scope')}",
152 dropdownAutoWidth: true,
152 dropdownAutoWidth: true,
153 containerCssClass: "drop-menu",
153 containerCssClass: "drop-menu",
154 dropdownCssClass: "drop-menu-dropdown",
154 dropdownCssClass: "drop-menu-dropdown",
155 formatResult: formatResult,
155 formatResult: formatRepoResult,
156 query: $.debounce(250, function(query){
156 query: $.debounce(250, function(query){
157 self = this;
157 self = this;
158 var cacheKey = query.term;
158 var cacheKey = query.term;
159 var cachedData = self.cachedDataSource[cacheKey];
159 var cachedData = self.cachedDataSource[cacheKey];
160
160
161 if (cachedData) {
161 if (cachedData) {
162 query.callback({results: cachedData.results});
162 query.callback({results: cachedData.results});
163 } else {
163 } else {
164 $.ajax({
164 $.ajax({
165 url: pyroutes.url('repo_list_data'),
165 url: pyroutes.url('repo_list_data'),
166 data: {'query': query.term},
166 data: {'query': query.term},
167 dataType: 'json',
167 dataType: 'json',
168 type: 'GET',
168 type: 'GET',
169 success: function(data) {
169 success: function(data) {
170 data = repoFilter(data);
170 data = repoFilter(data);
171 self.cachedDataSource[cacheKey] = data;
171 self.cachedDataSource[cacheKey] = data;
172 query.callback({results: data.results});
172 query.callback({results: data.results});
173 },
173 },
174 error: function(data, textStatus, errorThrown) {
174 error: function(data, textStatus, errorThrown) {
175 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
175 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
176 }
176 }
177 })
177 })
178 }
178 }
179 })
179 })
180 });
180 });
181 $("#scope_repo_id").on('select2-selecting', function(e){
181 $("#scope_repo_id").on('select2-selecting', function(e){
182 selectVcsScope()
182 selectVcsScope()
183 });
183 });
184
184
185 });
185 });
186 </script>
186 </script>
@@ -1,613 +1,662 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 %if c.rhodecode_name:
14 %if c.rhodecode_name:
15 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
15 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
16 %endif
16 %endif
17 </div>
17 </div>
18 <!-- MENU BAR NAV -->
18 <!-- MENU BAR NAV -->
19 ${self.menu_bar_nav()}
19 ${self.menu_bar_nav()}
20 <!-- END MENU BAR NAV -->
20 <!-- END MENU BAR NAV -->
21 </div>
21 </div>
22 </div>
22 </div>
23 ${self.menu_bar_subnav()}
23 ${self.menu_bar_subnav()}
24 <!-- END HEADER -->
24 <!-- END HEADER -->
25
25
26 <!-- CONTENT -->
26 <!-- CONTENT -->
27 <div id="content" class="wrapper">
27 <div id="content" class="wrapper">
28
28
29 <rhodecode-toast id="notifications"></rhodecode-toast>
29 <rhodecode-toast id="notifications"></rhodecode-toast>
30
30
31 <div class="main">
31 <div class="main">
32 ${next.main()}
32 ${next.main()}
33 </div>
33 </div>
34 </div>
34 </div>
35 <!-- END CONTENT -->
35 <!-- END CONTENT -->
36
36
37 </div>
37 </div>
38 <!-- FOOTER -->
38 <!-- FOOTER -->
39 <div id="footer">
39 <div id="footer">
40 <div id="footer-inner" class="title wrapper">
40 <div id="footer-inner" class="title wrapper">
41 <div>
41 <div>
42 <p class="footer-link-right">
42 <p class="footer-link-right">
43 % if c.visual.show_version:
43 % if c.visual.show_version:
44 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
44 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
45 % endif
45 % endif
46 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
46 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
47 % if c.visual.rhodecode_support_url:
47 % if c.visual.rhodecode_support_url:
48 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
48 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
49 % endif
49 % endif
50 </p>
50 </p>
51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
52 <p class="server-instance" style="display:${sid}">
52 <p class="server-instance" style="display:${sid}">
53 ## display hidden instance ID if specially defined
53 ## display hidden instance ID if specially defined
54 % if c.rhodecode_instanceid:
54 % if c.rhodecode_instanceid:
55 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
55 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
56 % endif
56 % endif
57 </p>
57 </p>
58 </div>
58 </div>
59 </div>
59 </div>
60 </div>
60 </div>
61
61
62 <!-- END FOOTER -->
62 <!-- END FOOTER -->
63
63
64 ### MAKO DEFS ###
64 ### MAKO DEFS ###
65
65
66 <%def name="menu_bar_subnav()">
66 <%def name="menu_bar_subnav()">
67 </%def>
67 </%def>
68
68
69 <%def name="breadcrumbs(class_='breadcrumbs')">
69 <%def name="breadcrumbs(class_='breadcrumbs')">
70 <div class="${class_}">
70 <div class="${class_}">
71 ${self.breadcrumbs_links()}
71 ${self.breadcrumbs_links()}
72 </div>
72 </div>
73 </%def>
73 </%def>
74
74
75 <%def name="admin_menu()">
75 <%def name="admin_menu()">
76 <ul class="admin_menu submenu">
76 <ul class="admin_menu submenu">
77 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
77 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
78 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
78 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
79 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
79 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
80 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
80 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
81 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
81 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
82 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
82 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
83 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
83 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
84 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
84 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
85 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
85 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
86 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
87 </ul>
87 </ul>
88 </%def>
88 </%def>
89
89
90
90
91 <%def name="dt_info_panel(elements)">
91 <%def name="dt_info_panel(elements)">
92 <dl class="dl-horizontal">
92 <dl class="dl-horizontal">
93 %for dt, dd, title, show_items in elements:
93 %for dt, dd, title, show_items in elements:
94 <dt>${dt}:</dt>
94 <dt>${dt}:</dt>
95 <dd title="${h.tooltip(title)}">
95 <dd title="${h.tooltip(title)}">
96 %if callable(dd):
96 %if callable(dd):
97 ## allow lazy evaluation of elements
97 ## allow lazy evaluation of elements
98 ${dd()}
98 ${dd()}
99 %else:
99 %else:
100 ${dd}
100 ${dd}
101 %endif
101 %endif
102 %if show_items:
102 %if show_items:
103 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
103 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
104 %endif
104 %endif
105 </dd>
105 </dd>
106
106
107 %if show_items:
107 %if show_items:
108 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
108 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
109 %for item in show_items:
109 %for item in show_items:
110 <dt></dt>
110 <dt></dt>
111 <dd>${item}</dd>
111 <dd>${item}</dd>
112 %endfor
112 %endfor
113 </div>
113 </div>
114 %endif
114 %endif
115
115
116 %endfor
116 %endfor
117 </dl>
117 </dl>
118 </%def>
118 </%def>
119
119
120
120
121 <%def name="gravatar(email, size=16)">
121 <%def name="gravatar(email, size=16)">
122 <%
122 <%
123 if (size > 16):
123 if (size > 16):
124 gravatar_class = 'gravatar gravatar-large'
124 gravatar_class = 'gravatar gravatar-large'
125 else:
125 else:
126 gravatar_class = 'gravatar'
126 gravatar_class = 'gravatar'
127 %>
127 %>
128 <%doc>
128 <%doc>
129 TODO: johbo: For now we serve double size images to make it smooth
129 TODO: johbo: For now we serve double size images to make it smooth
130 for retina. This is how it worked until now. Should be replaced
130 for retina. This is how it worked until now. Should be replaced
131 with a better solution at some point.
131 with a better solution at some point.
132 </%doc>
132 </%doc>
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 </%def>
134 </%def>
135
135
136
136
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 <% email = h.email_or_none(contact) %>
138 <% email = h.email_or_none(contact) %>
139 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
139 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
140 ${self.gravatar(email, size)}
140 ${self.gravatar(email, size)}
141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
142 </div>
142 </div>
143 </%def>
143 </%def>
144
144
145
145
146 ## admin menu used for people that have some admin resources
146 ## admin menu used for people that have some admin resources
147 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
148 <ul class="submenu">
148 <ul class="submenu">
149 %if repositories:
149 %if repositories:
150 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
150 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
151 %endif
151 %endif
152 %if repository_groups:
152 %if repository_groups:
153 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
153 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
154 %endif
154 %endif
155 %if user_groups:
155 %if user_groups:
156 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
156 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
157 %endif
157 %endif
158 </ul>
158 </ul>
159 </%def>
159 </%def>
160
160
161 <%def name="repo_page_title(repo_instance)">
161 <%def name="repo_page_title(repo_instance)">
162 <div class="title-content">
162 <div class="title-content">
163 <div class="title-main">
163 <div class="title-main">
164 ## SVN/HG/GIT icons
164 ## SVN/HG/GIT icons
165 %if h.is_hg(repo_instance):
165 %if h.is_hg(repo_instance):
166 <i class="icon-hg"></i>
166 <i class="icon-hg"></i>
167 %endif
167 %endif
168 %if h.is_git(repo_instance):
168 %if h.is_git(repo_instance):
169 <i class="icon-git"></i>
169 <i class="icon-git"></i>
170 %endif
170 %endif
171 %if h.is_svn(repo_instance):
171 %if h.is_svn(repo_instance):
172 <i class="icon-svn"></i>
172 <i class="icon-svn"></i>
173 %endif
173 %endif
174
174
175 ## public/private
175 ## public/private
176 %if repo_instance.private:
176 %if repo_instance.private:
177 <i class="icon-repo-private"></i>
177 <i class="icon-repo-private"></i>
178 %else:
178 %else:
179 <i class="icon-repo-public"></i>
179 <i class="icon-repo-public"></i>
180 %endif
180 %endif
181
181
182 ## repo name with group name
182 ## repo name with group name
183 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
184
184
185 </div>
185 </div>
186
186
187 ## FORKED
187 ## FORKED
188 %if repo_instance.fork:
188 %if repo_instance.fork:
189 <p>
189 <p>
190 <i class="icon-code-fork"></i> ${_('Fork of')}
190 <i class="icon-code-fork"></i> ${_('Fork of')}
191 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
191 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
192 </p>
192 </p>
193 %endif
193 %endif
194
194
195 ## IMPORTED FROM REMOTE
195 ## IMPORTED FROM REMOTE
196 %if repo_instance.clone_uri:
196 %if repo_instance.clone_uri:
197 <p>
197 <p>
198 <i class="icon-code-fork"></i> ${_('Clone from')}
198 <i class="icon-code-fork"></i> ${_('Clone from')}
199 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
200 </p>
200 </p>
201 %endif
201 %endif
202
202
203 ## LOCKING STATUS
203 ## LOCKING STATUS
204 %if repo_instance.locked[0]:
204 %if repo_instance.locked[0]:
205 <p class="locking_locked">
205 <p class="locking_locked">
206 <i class="icon-repo-lock"></i>
206 <i class="icon-repo-lock"></i>
207 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
208 </p>
208 </p>
209 %elif repo_instance.enable_locking:
209 %elif repo_instance.enable_locking:
210 <p class="locking_unlocked">
210 <p class="locking_unlocked">
211 <i class="icon-repo-unlock"></i>
211 <i class="icon-repo-unlock"></i>
212 ${_('Repository not locked. Pull repository to lock it.')}
212 ${_('Repository not locked. Pull repository to lock it.')}
213 </p>
213 </p>
214 %endif
214 %endif
215
215
216 </div>
216 </div>
217 </%def>
217 </%def>
218
218
219 <%def name="repo_menu(active=None)">
219 <%def name="repo_menu(active=None)">
220 <%
220 <%
221 def is_active(selected):
221 def is_active(selected):
222 if selected == active:
222 if selected == active:
223 return "active"
223 return "active"
224 %>
224 %>
225
225
226 <!--- CONTEXT BAR -->
226 <!--- CONTEXT BAR -->
227 <div id="context-bar">
227 <div id="context-bar">
228 <div class="wrapper">
228 <div class="wrapper">
229 <ul id="context-pages" class="horizontal-list navigation">
229 <ul id="context-pages" class="navigation horizontal-list">
230 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
231 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
232 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
232 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
233 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
233 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 <li class="${is_active('showpullrequest')}">
236 <li class="${is_active('showpullrequest')}">
237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
238 %if c.repository_pull_requests:
238 %if c.repository_pull_requests:
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 %endif
240 %endif
241 <div class="menulabel">${_('Pull Requests')}</div>
241 <div class="menulabel">${_('Pull Requests')}</div>
242 </a>
242 </a>
243 </li>
243 </li>
244 %endif
244 %endif
245 <li class="${is_active('options')}">
245 <li class="${is_active('options')}">
246 <a class="menulink dropdown">
246 <a class="menulink dropdown">
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 </a>
248 </a>
249 <ul class="submenu">
249 <ul class="submenu">
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
252 %endif
252 %endif
253 %if c.rhodecode_db_repo.fork:
253 %if c.rhodecode_db_repo.fork:
254 <li>
254 <li>
255 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
255 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
256 href="${h.route_path('repo_compare',
256 href="${h.route_path('repo_compare',
257 repo_name=c.rhodecode_db_repo.fork.repo_name,
257 repo_name=c.rhodecode_db_repo.fork.repo_name,
258 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
258 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
259 source_ref=c.rhodecode_db_repo.landing_rev[1],
259 source_ref=c.rhodecode_db_repo.landing_rev[1],
260 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
260 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
261 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
261 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
262 _query=dict(merge=1))}"
262 _query=dict(merge=1))}"
263 >
263 >
264 ${_('Compare fork')}
264 ${_('Compare fork')}
265 </a>
265 </a>
266 </li>
266 </li>
267 %endif
267 %endif
268
268
269 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
269 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
270
270
271 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
271 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
272 %if c.rhodecode_db_repo.locked[0]:
272 %if c.rhodecode_db_repo.locked[0]:
273 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
273 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
274 %else:
274 %else:
275 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
275 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
276 %endif
276 %endif
277 %endif
277 %endif
278 %if c.rhodecode_user.username != h.DEFAULT_USER:
278 %if c.rhodecode_user.username != h.DEFAULT_USER:
279 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
279 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
280 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
280 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
281 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
281 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
282 %endif
282 %endif
283 %endif
283 %endif
284 </ul>
284 </ul>
285 </li>
285 </li>
286 </ul>
286 </ul>
287 </div>
287 </div>
288 <div class="clear"></div>
288 <div class="clear"></div>
289 </div>
289 </div>
290 <!--- END CONTEXT BAR -->
290 <!--- END CONTEXT BAR -->
291
291
292 </%def>
292 </%def>
293
293
294 <%def name="usermenu(active=False)">
294 <%def name="usermenu(active=False)">
295 ## USER MENU
295 ## USER MENU
296 <li id="quick_login_li" class="${'active' if active else ''}">
296 <li id="quick_login_li" class="${'active' if active else ''}">
297 <a id="quick_login_link" class="menulink childs">
297 <a id="quick_login_link" class="menulink childs">
298 ${gravatar(c.rhodecode_user.email, 20)}
298 ${gravatar(c.rhodecode_user.email, 20)}
299 <span class="user">
299 <span class="user">
300 %if c.rhodecode_user.username != h.DEFAULT_USER:
300 %if c.rhodecode_user.username != h.DEFAULT_USER:
301 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
301 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
302 %else:
302 %else:
303 <span>${_('Sign in')}</span>
303 <span>${_('Sign in')}</span>
304 %endif
304 %endif
305 </span>
305 </span>
306 </a>
306 </a>
307
307
308 <div class="user-menu submenu">
308 <div class="user-menu submenu">
309 <div id="quick_login">
309 <div id="quick_login">
310 %if c.rhodecode_user.username == h.DEFAULT_USER:
310 %if c.rhodecode_user.username == h.DEFAULT_USER:
311 <h4>${_('Sign in to your account')}</h4>
311 <h4>${_('Sign in to your account')}</h4>
312 ${h.form(h.route_path('login', _query={'came_from': h.current_route_path(request)}), needs_csrf_token=False)}
312 ${h.form(h.route_path('login', _query={'came_from': h.current_route_path(request)}), needs_csrf_token=False)}
313 <div class="form form-vertical">
313 <div class="form form-vertical">
314 <div class="fields">
314 <div class="fields">
315 <div class="field">
315 <div class="field">
316 <div class="label">
316 <div class="label">
317 <label for="username">${_('Username')}:</label>
317 <label for="username">${_('Username')}:</label>
318 </div>
318 </div>
319 <div class="input">
319 <div class="input">
320 ${h.text('username',class_='focus',tabindex=1)}
320 ${h.text('username',class_='focus',tabindex=1)}
321 </div>
321 </div>
322
322
323 </div>
323 </div>
324 <div class="field">
324 <div class="field">
325 <div class="label">
325 <div class="label">
326 <label for="password">${_('Password')}:</label>
326 <label for="password">${_('Password')}:</label>
327 %if h.HasPermissionAny('hg.password_reset.enabled')():
327 %if h.HasPermissionAny('hg.password_reset.enabled')():
328 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
328 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
329 %endif
329 %endif
330 </div>
330 </div>
331 <div class="input">
331 <div class="input">
332 ${h.password('password',class_='focus',tabindex=2)}
332 ${h.password('password',class_='focus',tabindex=2)}
333 </div>
333 </div>
334 </div>
334 </div>
335 <div class="buttons">
335 <div class="buttons">
336 <div class="register">
336 <div class="register">
337 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
337 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
338 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
338 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
339 %endif
339 %endif
340 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
340 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
341 </div>
341 </div>
342 <div class="submit">
342 <div class="submit">
343 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
343 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
344 </div>
344 </div>
345 </div>
345 </div>
346 </div>
346 </div>
347 </div>
347 </div>
348 ${h.end_form()}
348 ${h.end_form()}
349 %else:
349 %else:
350 <div class="">
350 <div class="">
351 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
351 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
352 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
352 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
353 <div class="email">${c.rhodecode_user.email}</div>
353 <div class="email">${c.rhodecode_user.email}</div>
354 </div>
354 </div>
355 <div class="">
355 <div class="">
356 <ol class="links">
356 <ol class="links">
357 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
357 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
358 % if c.rhodecode_user.personal_repo_group:
358 % if c.rhodecode_user.personal_repo_group:
359 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
359 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
360 % endif
360 % endif
361 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
361 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
362
362
363 <li class="logout">
363 <li class="logout">
364 ${h.secure_form(h.route_path('logout'), request=request)}
364 ${h.secure_form(h.route_path('logout'), request=request)}
365 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
365 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
366 ${h.end_form()}
366 ${h.end_form()}
367 </li>
367 </li>
368 </ol>
368 </ol>
369 </div>
369 </div>
370 %endif
370 %endif
371 </div>
371 </div>
372 </div>
372 </div>
373 %if c.rhodecode_user.username != h.DEFAULT_USER:
373 %if c.rhodecode_user.username != h.DEFAULT_USER:
374 <div class="pill_container">
374 <div class="pill_container">
375 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
375 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
376 </div>
376 </div>
377 % endif
377 % endif
378 </li>
378 </li>
379 </%def>
379 </%def>
380
380
381 <%def name="menu_items(active=None)">
381 <%def name="menu_items(active=None)">
382 <%
382 <%
383 def is_active(selected):
383 def is_active(selected):
384 if selected == active:
384 if selected == active:
385 return "active"
385 return "active"
386 return ""
386 return ""
387 %>
387 %>
388 <ul id="quick" class="main_nav navigation horizontal-list">
388 <ul id="quick" class="main_nav navigation horizontal-list">
389 <!-- repo switcher -->
389
390 <li class="${is_active('repositories')} repo_switcher_li has_select2">
390 ## Main filter
391 <input id="repo_switcher" name="repo_switcher" type="hidden">
391 <li>
392 </li>
392 <div class="menulabel main_filter_box">
393 <div class="main_filter_input_box">
394 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
395 </div>
396 <div class="main_filter_help_box">
397 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
398 </div>
399 </div>
400
401 <div id="main_filter_help" style="display: none">
402 Use '/' key to quickly access this field.
403 Enter name of repository, or repository group for quick search.
404
405 Prefix query to allow special search:
406
407 For usernames, e.g user:admin
408
409 For commit hash/id, e.g commit:efced4
410
411 </div>
412 </li>
393
413
394 ## ROOT MENU
414 ## ROOT MENU
395 %if c.rhodecode_user.username != h.DEFAULT_USER:
415 %if c.rhodecode_user.username != h.DEFAULT_USER:
396 <li class="${is_active('journal')}">
416 <li class="${is_active('journal')}">
397 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
417 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
398 <div class="menulabel">${_('Journal')}</div>
418 <div class="menulabel">${_('Journal')}</div>
399 </a>
419 </a>
400 </li>
420 </li>
401 %else:
421 %else:
402 <li class="${is_active('journal')}">
422 <li class="${is_active('journal')}">
403 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
423 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
404 <div class="menulabel">${_('Public journal')}</div>
424 <div class="menulabel">${_('Public journal')}</div>
405 </a>
425 </a>
406 </li>
426 </li>
407 %endif
427 %endif
408 <li class="${is_active('gists')}">
428 <li class="${is_active('gists')}">
409 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
429 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
410 <div class="menulabel">${_('Gists')}</div>
430 <div class="menulabel">${_('Gists')}</div>
411 </a>
431 </a>
412 </li>
432 </li>
413 <li class="${is_active('search')}">
433 <li class="${is_active('search')}">
414 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
434 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
415 <div class="menulabel">${_('Search')}</div>
435 <div class="menulabel">${_('Search')}</div>
416 </a>
436 </a>
417 </li>
437 </li>
418 % if h.HasPermissionAll('hg.admin')('access admin main page'):
438 % if h.HasPermissionAll('hg.admin')('access admin main page'):
419 <li class="${is_active('admin')}">
439 <li class="${is_active('admin')}">
420 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
440 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
421 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
441 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
422 </a>
442 </a>
423 ${admin_menu()}
443 ${admin_menu()}
424 </li>
444 </li>
425 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
445 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
426 <li class="${is_active('admin')}">
446 <li class="${is_active('admin')}">
427 <a class="menulink childs" title="${_('Delegated Admin settings')}">
447 <a class="menulink childs" title="${_('Delegated Admin settings')}">
428 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
448 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
429 </a>
449 </a>
430 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
450 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
431 c.rhodecode_user.repository_groups_admin,
451 c.rhodecode_user.repository_groups_admin,
432 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
452 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
433 </li>
453 </li>
434 % endif
454 % endif
455 ## render extra user menu
456 ${usermenu(active=(active=='my_account'))}
457
435 % if c.debug_style:
458 % if c.debug_style:
436 <li class="${is_active('debug_style')}">
459 <li class="${is_active('debug_style')}">
437 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
460 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
438 <div class="menulabel">${_('Style')}</div>
461 <div class="menulabel">${_('Style')}</div>
439 </a>
462 </a>
440 </li>
463 </li>
441 % endif
464 % endif
442 ## render extra user menu
443 ${usermenu(active=(active=='my_account'))}
444 </ul>
465 </ul>
445
466
446 <script type="text/javascript">
467 <script type="text/javascript">
447 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
468 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
448
449 /*format the look of items in the list*/
450 var format = function(state, escapeMarkup){
451 if (!state.id){
452 return state.text; // optgroup
453 }
454 var obj_dict = state.obj;
455 var tmpl = '';
456
469
457 if(obj_dict && state.type == 'repo'){
470 var formatRepoResult = function(result, container, query, escapeMarkup) {
458 if(obj_dict['repo_type'] === 'hg'){
471 return function(data, escapeMarkup) {
459 tmpl += '<i class="icon-hg"></i> ';
472 if (!data.repo_id){
460 }
473 return data.text; // optgroup text Repositories
461 else if(obj_dict['repo_type'] === 'git'){
462 tmpl += '<i class="icon-git"></i> ';
463 }
464 else if(obj_dict['repo_type'] === 'svn'){
465 tmpl += '<i class="icon-svn"></i> ';
466 }
474 }
467 if(obj_dict['private']){
475
468 tmpl += '<i class="icon-lock" ></i> ';
476 var tmpl = '';
469 }
477 var repoType = data['repo_type'];
470 else if(visual_show_public_icon){
478 var repoName = data['text'];
471 tmpl += '<i class="icon-unlock-alt"></i> ';
479
480 if(data && data.type == 'repo'){
481 if(repoType === 'hg'){
482 tmpl += '<i class="icon-hg"></i> ';
483 }
484 else if(repoType === 'git'){
485 tmpl += '<i class="icon-git"></i> ';
486 }
487 else if(repoType === 'svn'){
488 tmpl += '<i class="icon-svn"></i> ';
489 }
490 if(data['private']){
491 tmpl += '<i class="icon-lock" ></i> ';
492 }
493 else if(visualShowPublicIcon){
494 tmpl += '<i class="icon-unlock-alt"></i> ';
495 }
472 }
496 }
473 }
497 tmpl += escapeMarkup(repoName);
474 if(obj_dict && state.type == 'commit') {
498 return tmpl;
475 tmpl += '<i class="icon-tag"></i>';
476 }
477 if(obj_dict && state.type == 'group'){
478 tmpl += '<i class="icon-folder-close"></i> ';
479 }
480 tmpl += escapeMarkup(state.text);
481 return tmpl;
482 };
483
499
484 var formatResult = function(result, container, query, escapeMarkup) {
500 }(result, escapeMarkup);
485 return format(result, escapeMarkup);
486 };
487
488 var formatSelection = function(data, container, escapeMarkup) {
489 return format(data, escapeMarkup);
490 };
501 };
491
502
492 $("#repo_switcher").select2({
503
493 cachedDataSource: {},
504 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
494 minimumInputLength: 2,
505
495 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
506 if (value.split(':').length === 2) {
496 dropdownAutoWidth: true,
507 value = value.split(':')[1]
497 formatResult: formatResult,
508 }
498 formatSelection: formatSelection,
509
499 containerCssClass: "repo-switcher",
510 var searchType = data['type'];
500 dropdownCssClass: "repo-switcher-dropdown",
511 var valueDisplay = data['value_display'];
501 escapeMarkup: function(m){
512
502 // don't escape our custom placeholder
513 var escapeRegExChars = function (value) {
503 if(m.substr(0,23) == '<div class="menulabel">'){
514 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
504 return m;
515 };
505 }
516 var pattern = '(' + escapeRegExChars(value) + ')';
517
518 // highlight match
519 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
520 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
521
522 var icon = '';
506
523
507 return Select2.util.escapeMarkup(m);
524 if (searchType === 'search') {
508 },
525 icon += '<i class="icon-more"></i> ';
509 query: $.debounce(250, function(query){
526 }
510 self = this;
527 else if (searchType === 'repo') {
511 var cacheKey = query.term;
528 if (data['repo_type'] === 'hg') {
512 var cachedData = self.cachedDataSource[cacheKey];
529 icon += '<i class="icon-hg"></i> ';
530 }
531 else if (data['repo_type'] === 'git') {
532 icon += '<i class="icon-git"></i> ';
533 }
534 else if (data['repo_type'] === 'svn') {
535 icon += '<i class="icon-svn"></i> ';
536 }
537 if (data['private']) {
538 icon += '<i class="icon-lock" ></i> ';
539 }
540 else if (visualShowPublicIcon) {
541 icon += '<i class="icon-unlock-alt"></i> ';
542 }
543 }
544 else if (searchType === 'repo_group') {
545 icon += '<i class="icon-folder-close"></i> ';
546 }
547 else if (searchType === 'user') {
548 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
549 }
550 else if (searchType === 'commit') {
551 icon += '<i class="icon-tag"></i>';
552 }
513
553
514 if (cachedData) {
554 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
515 query.callback({results: cachedData.results});
555 return tmpl.format(icon, valueDisplay);
516 } else {
556 };
517 $.ajax({
557
518 url: pyroutes.url('goto_switcher_data'),
558 var handleSelect = function(element, suggestion) {
519 data: {'query': query.term},
559 window.location = suggestion['url'];
520 dataType: 'json',
560 };
521 type: 'GET',
561 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
522 success: function(data) {
562 if (queryLowerCase.split(':').length === 2) {
523 self.cachedDataSource[cacheKey] = data;
563 queryLowerCase = queryLowerCase.split(':')[1]
524 query.callback({results: data.results});
564 }
525 },
565 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
526 error: function(data, textStatus, errorThrown) {
566 };
527 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
567
528 }
568 $('#main_filter').autocomplete({
529 })
569 serviceUrl: pyroutes.url('goto_switcher_data'),
530 }
570 minChars:2,
531 })
571 maxHeight:400,
572 deferRequestBy: 300, //miliseconds
573 tabDisabled: true,
574 autoSelectFirst: true,
575 formatResult: autocompleteMainFilterFormatResult,
576 lookupFilter: autocompleteMainFilterResult,
577 onSelect: function(element, suggestion){
578 handleSelect(element, suggestion);
579 return false;
580 }
532 });
581 });
533
582
534 $("#repo_switcher").on('select2-selecting', function(e){
583 showMainFilterBox = function () {
535 e.preventDefault();
584 $('#main_filter_help').toggle();
536 window.location = e.choice.url;
585 }
537 });
538
586
539 </script>
587 </script>
540 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
588 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
541 </%def>
589 </%def>
542
590
543 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
591 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
544 <div class="modal-dialog">
592 <div class="modal-dialog">
545 <div class="modal-content">
593 <div class="modal-content">
546 <div class="modal-header">
594 <div class="modal-header">
547 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
595 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
548 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
596 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
549 </div>
597 </div>
550 <div class="modal-body">
598 <div class="modal-body">
551 <div class="block-left">
599 <div class="block-left">
552 <table class="keyboard-mappings">
600 <table class="keyboard-mappings">
553 <tbody>
601 <tbody>
554 <tr>
602 <tr>
555 <th></th>
603 <th></th>
556 <th>${_('Site-wide shortcuts')}</th>
604 <th>${_('Site-wide shortcuts')}</th>
557 </tr>
605 </tr>
558 <%
606 <%
559 elems = [
607 elems = [
560 ('/', 'Open quick search box'),
608 ('/', 'Use quick search box'),
561 ('g h', 'Goto home page'),
609 ('g h', 'Goto home page'),
562 ('g g', 'Goto my private gists page'),
610 ('g g', 'Goto my private gists page'),
563 ('g G', 'Goto my public gists page'),
611 ('g G', 'Goto my public gists page'),
564 ('n r', 'New repository page'),
612 ('n r', 'New repository page'),
565 ('n g', 'New gist page'),
613 ('n g', 'New gist page'),
566 ]
614 ]
567 %>
615 %>
568 %for key, desc in elems:
616 %for key, desc in elems:
569 <tr>
617 <tr>
570 <td class="keys">
618 <td class="keys">
571 <span class="key tag">${key}</span>
619 <span class="key tag">${key}</span>
572 </td>
620 </td>
573 <td>${desc}</td>
621 <td>${desc}</td>
574 </tr>
622 </tr>
575 %endfor
623 %endfor
576 </tbody>
624 </tbody>
577 </table>
625 </table>
578 </div>
626 </div>
579 <div class="block-left">
627 <div class="block-left">
580 <table class="keyboard-mappings">
628 <table class="keyboard-mappings">
581 <tbody>
629 <tbody>
582 <tr>
630 <tr>
583 <th></th>
631 <th></th>
584 <th>${_('Repositories')}</th>
632 <th>${_('Repositories')}</th>
585 </tr>
633 </tr>
586 <%
634 <%
587 elems = [
635 elems = [
588 ('g s', 'Goto summary page'),
636 ('g s', 'Goto summary page'),
589 ('g c', 'Goto changelog page'),
637 ('g c', 'Goto changelog page'),
590 ('g f', 'Goto files page'),
638 ('g f', 'Goto files page'),
591 ('g F', 'Goto files page with file search activated'),
639 ('g F', 'Goto files page with file search activated'),
592 ('g p', 'Goto pull requests page'),
640 ('g p', 'Goto pull requests page'),
593 ('g o', 'Goto repository settings'),
641 ('g o', 'Goto repository settings'),
594 ('g O', 'Goto repository permissions settings'),
642 ('g O', 'Goto repository permissions settings'),
595 ]
643 ]
596 %>
644 %>
597 %for key, desc in elems:
645 %for key, desc in elems:
598 <tr>
646 <tr>
599 <td class="keys">
647 <td class="keys">
600 <span class="key tag">${key}</span>
648 <span class="key tag">${key}</span>
601 </td>
649 </td>
602 <td>${desc}</td>
650 <td>${desc}</td>
603 </tr>
651 </tr>
604 %endfor
652 %endfor
605 </tbody>
653 </tbody>
606 </table>
654 </table>
607 </div>
655 </div>
608 </div>
656 </div>
609 <div class="modal-footer">
657 <div class="modal-footer">
610 </div>
658 </div>
611 </div><!-- /.modal-content -->
659 </div><!-- /.modal-content -->
612 </div><!-- /.modal-dialog -->
660 </div><!-- /.modal-dialog -->
613 </div><!-- /.modal -->
661 </div><!-- /.modal -->
662
@@ -1,201 +1,141 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="main()">
3 <%def name="main()">
4 <div class="box">
4 <div class="box">
5 <!-- box / title -->
5 <!-- box / title -->
6 <div class="title">
6 <div class="title">
7 <div class="block-left breadcrumbs">
7 <div class="block-left breadcrumbs">
8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
9 ${self.breadcrumbs()}
8 ${self.breadcrumbs()}
10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
9 <span id="match_container" style="display:none"><span id="match_count">0</span> ${_('matches')}</span>
11 </div>
10 </div>
12 %if c.rhodecode_user.username != h.DEFAULT_USER:
11 %if c.rhodecode_user.username != h.DEFAULT_USER:
13 <div class="block-right">
12 <div class="block-right">
14 <%
13 <%
15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
14 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
15 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
16 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
17 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
19
18
20 gr_name = c.repo_group.group_name if c.repo_group else None
19 gr_name = c.repo_group.group_name if c.repo_group else None
21 # create repositories with write permission on group is set to true
20 # create repositories with write permission on group is set to true
22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
21 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
22 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
23 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
25 %>
24 %>
26
25
27 %if not c.repo_group:
26 %if not c.repo_group:
28 ## no repository group context here
27 ## no repository group context here
29 %if is_admin or create_repo:
28 %if is_admin or create_repo:
30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
29 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
31 %endif
30 %endif
32
31
33 %if is_admin or create_repo_group:
32 %if is_admin or create_repo_group:
34 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
33 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
35 %endif
34 %endif
36 %else:
35 %else:
37 ##we're inside other repository group other terms apply
36 ##we're inside other repository group other terms apply
38 %if is_admin or group_admin or (group_write and create_on_write):
37 %if is_admin or group_admin or (group_write and create_on_write):
39 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
38 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
40 %endif
39 %endif
41 %if is_admin or group_admin:
40 %if is_admin or group_admin:
42 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
41 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
43 %endif
42 %endif
44 %if is_admin or group_admin:
43 %if is_admin or group_admin:
45 <a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
44 <a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
46 %endif
45 %endif
47 %endif
46 %endif
48 </div>
47 </div>
49 %endif
48 %endif
50 </div>
49 </div>
51 <!-- end box / title -->
50 <!-- end box / title -->
52 <div class="table">
51 <div class="table">
53 <div id="groups_list_wrap">
52 <div id="groups_list_wrap">
54 <table id="group_list_table" class="display"></table>
53 <table id="group_list_table" class="display"></table>
55 </div>
54 </div>
56 </div>
55 </div>
57
56
58 <div class="table">
57 <div class="table">
59 <div id="repos_list_wrap">
58 <div id="repos_list_wrap">
60 <table id="repo_list_table" class="display"></table>
59 <table id="repo_list_table" class="display"></table>
61 </div>
60 </div>
62 </div>
61 </div>
63
62
64 ## no repository groups and repos present, show something to the users
63 ## no repository groups and repos present, show something to the users
65 % if c.repo_groups_data == '[]' and c.repos_data == '[]':
64 % if c.repo_groups_data == '[]' and c.repos_data == '[]':
66 <div class="table">
65 <div class="table">
67 <h2 class="no-object-border">
66 <h2 class="no-object-border">
68 ${_('No repositories or repositories groups exists here.')}
67 ${_('No repositories or repositories groups exists here.')}
69 </h2>
68 </h2>
70 </div>
69 </div>
71 % endif
70 % endif
72
71
73 </div>
72 </div>
74 <script>
73 <script>
75 $(document).ready(function() {
74 $(document).ready(function() {
76
75
77 // repo group list
76 // repo group list
78 % if c.repo_groups_data != '[]':
77 % if c.repo_groups_data != '[]':
79 $('#group_list_table').DataTable({
78 $('#group_list_table').DataTable({
80 data: ${c.repo_groups_data|n},
79 data: ${c.repo_groups_data|n},
81 dom: 'rtp',
80 dom: 'rtp',
82 pageLength: ${c.visual.dashboard_items},
81 pageLength: ${c.visual.dashboard_items},
83 order: [[ 0, "asc" ]],
82 order: [[ 0, "asc" ]],
84 columns: [
83 columns: [
85 { data: {"_": "name",
84 { data: {"_": "name",
86 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
85 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
87 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
86 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
88 { data: {"_": "desc",
87 { data: {"_": "desc",
89 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
88 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
90 { data: {"_": "last_change",
89 { data: {"_": "last_change",
91 "sort": "last_change_raw",
90 "sort": "last_change_raw",
92 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
91 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
93 { data: {"_": "owner",
92 { data: {"_": "owner",
94 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
93 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
95 ],
94 ],
96 language: {
95 language: {
97 paginate: DEFAULT_GRID_PAGINATION,
96 paginate: DEFAULT_GRID_PAGINATION,
98 emptyTable: _gettext("No repository groups available yet.")
97 emptyTable: _gettext("No repository groups available yet.")
99 },
98 },
100 "drawCallback": function( settings, json ) {
99 "drawCallback": function( settings, json ) {
101 timeagoActivate();
100 timeagoActivate();
102 quick_repo_menu();
101 quick_repo_menu();
103 }
102 }
104 });
103 });
105 % endif
104 % endif
106
105
107 // repo list
106 // repo list
108 % if c.repos_data != '[]':
107 % if c.repos_data != '[]':
109 $('#repo_list_table').DataTable({
108 $('#repo_list_table').DataTable({
110 data: ${c.repos_data|n},
109 data: ${c.repos_data|n},
111 dom: 'rtp',
110 dom: 'rtp',
112 order: [[ 0, "asc" ]],
111 order: [[ 0, "asc" ]],
113 pageLength: ${c.visual.dashboard_items},
112 pageLength: ${c.visual.dashboard_items},
114 columns: [
113 columns: [
115 { data: {"_": "name",
114 { data: {"_": "name",
116 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
115 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
117 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
116 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
118 { data: {"_": "desc",
117 { data: {"_": "desc",
119 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
118 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
120 { data: {"_": "last_change",
119 { data: {"_": "last_change",
121 "sort": "last_change_raw",
120 "sort": "last_change_raw",
122 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
121 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
123 { data: {"_": "last_changeset",
122 { data: {"_": "last_changeset",
124 "sort": "last_changeset_raw",
123 "sort": "last_changeset_raw",
125 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
124 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
126 { data: {"_": "owner",
125 { data: {"_": "owner",
127 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
126 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
128 ],
127 ],
129 language: {
128 language: {
130 paginate: DEFAULT_GRID_PAGINATION,
129 paginate: DEFAULT_GRID_PAGINATION,
131 emptyTable: _gettext("No repositories available yet.")
130 emptyTable: _gettext("No repositories available yet.")
132 },
131 },
133 "drawCallback": function( settings, json ) {
132 "drawCallback": function( settings, json ) {
134 timeagoActivate();
133 timeagoActivate();
135 quick_repo_menu();
134 quick_repo_menu();
136 }
135 }
137 });
136 });
138 % endif
137 % endif
139
138
140 var getDatatableCount = function() {
141 var reposCount = 0;
142 var reposCountTotal = 0;
143
144 % if c.repos_data != '[]':
145 var pageInfo = $('#repo_list_table').dataTable().api().page.info();
146 var reposCount = pageInfo.recordsDisplay;
147 var reposCountTotal = pageInfo.recordsTotal;
148 % endif
149
150 var repoGroupsCount = 0;
151 var repoGroupsCountTotal = 0;
152
153 % if c.repo_groups_data != '[]':
154 var pageInfo = $('#group_list_table').dataTable().api().page.info();
155 var repoGroupsCount = pageInfo.recordsDisplay;
156 var repoGroupsCountTotal = pageInfo.recordsTotal;
157 % endif
158
159 if (repoGroupsCount !== repoGroupsCountTotal) {
160 $('#match_count').text(reposCount + repoGroupsCount);
161 }
162 if (reposCount !== reposCountTotal) {
163 $('#match_container').show();
164 }
165 if ($('#q_filter').val() === '') {
166 $('#match_container').hide();
167 }
168 };
169
170 // update the counter when doing search
171 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
172 getDatatableCount();
173 });
174
175 // filter, filter both grids
176 $('#q_filter').on( 'keyup', function () {
177
178 % if c.repo_groups_data != '[]':
179 var repo_group_api = $('#group_list_table').dataTable().api();
180 repo_group_api
181 .columns( 0 )
182 .search( this.value )
183 .draw();
184 % endif
185
186 % if c.repos_data != '[]':
187 var repo_api = $('#repo_list_table').dataTable().api();
188 repo_api
189 .columns( 0 )
190 .search( this.value )
191 .draw();
192 % endif
193
194 });
195
196 // refilter table if page load via back button
197 $("#q_filter").trigger('keyup');
198
199 });
139 });
200 </script>
140 </script>
201 </%def>
141 </%def>
@@ -1,537 +1,537 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('New pull request')}
8 ${_('New pull request')}
9 </%def>
9 </%def>
10
10
11 <%def name="menu_bar_nav()">
11 <%def name="menu_bar_nav()">
12 ${self.menu_items(active='repositories')}
12 ${self.menu_items(active='repositories')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_subnav()">
15 <%def name="menu_bar_subnav()">
16 ${self.repo_menu(active='showpullrequest')}
16 ${self.repo_menu(active='showpullrequest')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.repo_page_title(c.rhodecode_db_repo)}
22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 </div>
23 </div>
24
24
25 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
25 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
26
26
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28
28
29 <div class="box pr-summary">
29 <div class="box pr-summary">
30
30
31 <div class="summary-details block-left">
31 <div class="summary-details block-left">
32
32
33
33
34 <div class="pr-details-title">
34 <div class="pr-details-title">
35 ${_('Pull request summary')}
35 ${_('Pull request summary')}
36 </div>
36 </div>
37
37
38 <div class="form" style="padding-top: 10px">
38 <div class="form" style="padding-top: 10px">
39 <!-- fields -->
39 <!-- fields -->
40
40
41 <div class="fields" >
41 <div class="fields" >
42
42
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="pullrequest_title">${_('Title')}:</label>
45 <label for="pullrequest_title">${_('Title')}:</label>
46 </div>
46 </div>
47 <div class="input">
47 <div class="input">
48 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
48 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52 <div class="field">
52 <div class="field">
53 <div class="label label-textarea">
53 <div class="label label-textarea">
54 <label for="pullrequest_desc">${_('Description')}:</label>
54 <label for="pullrequest_desc">${_('Description')}:</label>
55 </div>
55 </div>
56 <div class="textarea text-area editor">
56 <div class="textarea text-area editor">
57 ${h.textarea('pullrequest_desc',size=30, )}
57 ${h.textarea('pullrequest_desc',size=30, )}
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
59 </div>
59 </div>
60 </div>
60 </div>
61
61
62 <div class="field">
62 <div class="field">
63 <div class="label label-textarea">
63 <div class="label label-textarea">
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
65 </div>
65 </div>
66
66
67 ## TODO: johbo: Abusing the "content" class here to get the
67 ## TODO: johbo: Abusing the "content" class here to get the
68 ## desired effect. Should be replaced by a proper solution.
68 ## desired effect. Should be replaced by a proper solution.
69
69
70 ##ORG
70 ##ORG
71 <div class="content">
71 <div class="content">
72 <strong>${_('Source repository')}:</strong>
72 <strong>${_('Source repository')}:</strong>
73 ${c.rhodecode_db_repo.description}
73 ${c.rhodecode_db_repo.description}
74 </div>
74 </div>
75 <div class="content">
75 <div class="content">
76 ${h.hidden('source_repo')}
76 ${h.hidden('source_repo')}
77 ${h.hidden('source_ref')}
77 ${h.hidden('source_ref')}
78 </div>
78 </div>
79
79
80 ##OTHER, most Probably the PARENT OF THIS FORK
80 ##OTHER, most Probably the PARENT OF THIS FORK
81 <div class="content">
81 <div class="content">
82 ## filled with JS
82 ## filled with JS
83 <div id="target_repo_desc"></div>
83 <div id="target_repo_desc"></div>
84 </div>
84 </div>
85
85
86 <div class="content">
86 <div class="content">
87 ${h.hidden('target_repo')}
87 ${h.hidden('target_repo')}
88 ${h.hidden('target_ref')}
88 ${h.hidden('target_ref')}
89 <span id="target_ref_loading" style="display: none">
89 <span id="target_ref_loading" style="display: none">
90 ${_('Loading refs...')}
90 ${_('Loading refs...')}
91 </span>
91 </span>
92 </div>
92 </div>
93 </div>
93 </div>
94
94
95 <div class="field">
95 <div class="field">
96 <div class="label label-textarea">
96 <div class="label label-textarea">
97 <label for="pullrequest_submit"></label>
97 <label for="pullrequest_submit"></label>
98 </div>
98 </div>
99 <div class="input">
99 <div class="input">
100 <div class="pr-submit-button">
100 <div class="pr-submit-button">
101 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
101 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
102 </div>
102 </div>
103 <div id="pr_open_message"></div>
103 <div id="pr_open_message"></div>
104 </div>
104 </div>
105 </div>
105 </div>
106
106
107 <div class="pr-spacing-container"></div>
107 <div class="pr-spacing-container"></div>
108 </div>
108 </div>
109 </div>
109 </div>
110 </div>
110 </div>
111 <div>
111 <div>
112 ## AUTHOR
112 ## AUTHOR
113 <div class="reviewers-title block-right">
113 <div class="reviewers-title block-right">
114 <div class="pr-details-title">
114 <div class="pr-details-title">
115 ${_('Author of this pull request')}
115 ${_('Author of this pull request')}
116 </div>
116 </div>
117 </div>
117 </div>
118 <div class="block-right pr-details-content reviewers">
118 <div class="block-right pr-details-content reviewers">
119 <ul class="group_members">
119 <ul class="group_members">
120 <li>
120 <li>
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
122 </li>
122 </li>
123 </ul>
123 </ul>
124 </div>
124 </div>
125
125
126 ## REVIEW RULES
126 ## REVIEW RULES
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
128 <div class="pr-details-title">
128 <div class="pr-details-title">
129 ${_('Reviewer rules')}
129 ${_('Reviewer rules')}
130 </div>
130 </div>
131 <div class="pr-reviewer-rules">
131 <div class="pr-reviewer-rules">
132 ## review rules will be appended here, by default reviewers logic
132 ## review rules will be appended here, by default reviewers logic
133 </div>
133 </div>
134 </div>
134 </div>
135
135
136 ## REVIEWERS
136 ## REVIEWERS
137 <div class="reviewers-title block-right">
137 <div class="reviewers-title block-right">
138 <div class="pr-details-title">
138 <div class="pr-details-title">
139 ${_('Pull request reviewers')}
139 ${_('Pull request reviewers')}
140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
141 </div>
141 </div>
142 </div>
142 </div>
143 <div id="reviewers" class="block-right pr-details-content reviewers">
143 <div id="reviewers" class="block-right pr-details-content reviewers">
144 ## members goes here, filled via JS based on initial selection !
144 ## members goes here, filled via JS based on initial selection !
145 <input type="hidden" name="__start__" value="review_members:sequence">
145 <input type="hidden" name="__start__" value="review_members:sequence">
146 <ul id="review_members" class="group_members"></ul>
146 <ul id="review_members" class="group_members"></ul>
147 <input type="hidden" name="__end__" value="review_members:sequence">
147 <input type="hidden" name="__end__" value="review_members:sequence">
148 <div id="add_reviewer_input" class='ac'>
148 <div id="add_reviewer_input" class='ac'>
149 <div class="reviewer_ac">
149 <div class="reviewer_ac">
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
151 <div id="reviewers_container"></div>
151 <div id="reviewers_container"></div>
152 </div>
152 </div>
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156 </div>
156 </div>
157 <div class="box">
157 <div class="box">
158 <div>
158 <div>
159 ## overview pulled by ajax
159 ## overview pulled by ajax
160 <div id="pull_request_overview"></div>
160 <div id="pull_request_overview"></div>
161 </div>
161 </div>
162 </div>
162 </div>
163 ${h.end_form()}
163 ${h.end_form()}
164 </div>
164 </div>
165
165
166 <script type="text/javascript">
166 <script type="text/javascript">
167 $(function(){
167 $(function(){
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
172
172
173 var $pullRequestForm = $('#pull_request_form');
173 var $pullRequestForm = $('#pull_request_form');
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
178
178
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
181
181
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
184
184
185 var calculateContainerWidth = function() {
185 var calculateContainerWidth = function() {
186 var maxWidth = 0;
186 var maxWidth = 0;
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
188 $.each(repoSelect2Containers, function(idx, value) {
188 $.each(repoSelect2Containers, function(idx, value) {
189 $(value).select2('container').width('auto');
189 $(value).select2('container').width('auto');
190 var curWidth = $(value).select2('container').width();
190 var curWidth = $(value).select2('container').width();
191 if (maxWidth <= curWidth) {
191 if (maxWidth <= curWidth) {
192 maxWidth = curWidth;
192 maxWidth = curWidth;
193 }
193 }
194 $.each(repoSelect2Containers, function(idx, value) {
194 $.each(repoSelect2Containers, function(idx, value) {
195 $(value).select2('container').width(maxWidth + 10);
195 $(value).select2('container').width(maxWidth + 10);
196 });
196 });
197 });
197 });
198 };
198 };
199
199
200 var initRefSelection = function(selectedRef) {
200 var initRefSelection = function(selectedRef) {
201 return function(element, callback) {
201 return function(element, callback) {
202 // translate our select2 id into a text, it's a mapping to show
202 // translate our select2 id into a text, it's a mapping to show
203 // simple label when selecting by internal ID.
203 // simple label when selecting by internal ID.
204 var id, refData;
204 var id, refData;
205 if (selectedRef === undefined || selectedRef === null) {
205 if (selectedRef === undefined || selectedRef === null) {
206 id = element.val();
206 id = element.val();
207 refData = element.val().split(':');
207 refData = element.val().split(':');
208
208
209 if (refData.length !== 3){
209 if (refData.length !== 3){
210 refData = ["", "", ""]
210 refData = ["", "", ""]
211 }
211 }
212 } else {
212 } else {
213 id = selectedRef;
213 id = selectedRef;
214 refData = selectedRef.split(':');
214 refData = selectedRef.split(':');
215 }
215 }
216
216
217 var text = refData[1];
217 var text = refData[1];
218 if (refData[0] === 'rev') {
218 if (refData[0] === 'rev') {
219 text = text.substring(0, 12);
219 text = text.substring(0, 12);
220 }
220 }
221
221
222 var data = {id: id, text: text};
222 var data = {id: id, text: text};
223 callback(data);
223 callback(data);
224 };
224 };
225 };
225 };
226
226
227 var formatRefSelection = function(item) {
227 var formatRefSelection = function(item) {
228 var prefix = '';
228 var prefix = '';
229 var refData = item.id.split(':');
229 var refData = item.id.split(':');
230 if (refData[0] === 'branch') {
230 if (refData[0] === 'branch') {
231 prefix = '<i class="icon-branch"></i>';
231 prefix = '<i class="icon-branch"></i>';
232 }
232 }
233 else if (refData[0] === 'book') {
233 else if (refData[0] === 'book') {
234 prefix = '<i class="icon-bookmark"></i>';
234 prefix = '<i class="icon-bookmark"></i>';
235 }
235 }
236 else if (refData[0] === 'tag') {
236 else if (refData[0] === 'tag') {
237 prefix = '<i class="icon-tag"></i>';
237 prefix = '<i class="icon-tag"></i>';
238 }
238 }
239
239
240 var originalOption = item.element;
240 var originalOption = item.element;
241 return prefix + item.text;
241 return prefix + item.text;
242 };
242 };
243
243
244 // custom code mirror
244 // custom code mirror
245 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
245 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
246
246
247 reviewersController = new ReviewersController();
247 reviewersController = new ReviewersController();
248
248
249 var queryTargetRepo = function(self, query) {
249 var queryTargetRepo = function(self, query) {
250 // cache ALL results if query is empty
250 // cache ALL results if query is empty
251 var cacheKey = query.term || '__';
251 var cacheKey = query.term || '__';
252 var cachedData = self.cachedDataSource[cacheKey];
252 var cachedData = self.cachedDataSource[cacheKey];
253
253
254 if (cachedData) {
254 if (cachedData) {
255 query.callback({results: cachedData.results});
255 query.callback({results: cachedData.results});
256 } else {
256 } else {
257 $.ajax({
257 $.ajax({
258 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
258 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
259 data: {query: query.term},
259 data: {query: query.term},
260 dataType: 'json',
260 dataType: 'json',
261 type: 'GET',
261 type: 'GET',
262 success: function(data) {
262 success: function(data) {
263 self.cachedDataSource[cacheKey] = data;
263 self.cachedDataSource[cacheKey] = data;
264 query.callback({results: data.results});
264 query.callback({results: data.results});
265 },
265 },
266 error: function(data, textStatus, errorThrown) {
266 error: function(data, textStatus, errorThrown) {
267 alert(
267 alert(
268 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
268 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
269 }
269 }
270 });
270 });
271 }
271 }
272 };
272 };
273
273
274 var queryTargetRefs = function(initialData, query) {
274 var queryTargetRefs = function(initialData, query) {
275 var data = {results: []};
275 var data = {results: []};
276 // filter initialData
276 // filter initialData
277 $.each(initialData, function() {
277 $.each(initialData, function() {
278 var section = this.text;
278 var section = this.text;
279 var children = [];
279 var children = [];
280 $.each(this.children, function() {
280 $.each(this.children, function() {
281 if (query.term.length === 0 ||
281 if (query.term.length === 0 ||
282 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
282 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
283 children.push({'id': this.id, 'text': this.text})
283 children.push({'id': this.id, 'text': this.text})
284 }
284 }
285 });
285 });
286 data.results.push({'text': section, 'children': children})
286 data.results.push({'text': section, 'children': children})
287 });
287 });
288 query.callback({results: data.results});
288 query.callback({results: data.results});
289 };
289 };
290
290
291 var loadRepoRefDiffPreview = function() {
291 var loadRepoRefDiffPreview = function() {
292
292
293 var url_data = {
293 var url_data = {
294 'repo_name': targetRepo(),
294 'repo_name': targetRepo(),
295 'target_repo': sourceRepo(),
295 'target_repo': sourceRepo(),
296 'source_ref': targetRef()[2],
296 'source_ref': targetRef()[2],
297 'source_ref_type': 'rev',
297 'source_ref_type': 'rev',
298 'target_ref': sourceRef()[2],
298 'target_ref': sourceRef()[2],
299 'target_ref_type': 'rev',
299 'target_ref_type': 'rev',
300 'merge': true,
300 'merge': true,
301 '_': Date.now() // bypass browser caching
301 '_': Date.now() // bypass browser caching
302 }; // gather the source/target ref and repo here
302 }; // gather the source/target ref and repo here
303
303
304 if (sourceRef().length !== 3 || targetRef().length !== 3) {
304 if (sourceRef().length !== 3 || targetRef().length !== 3) {
305 prButtonLock(true, "${_('Please select source and target')}");
305 prButtonLock(true, "${_('Please select source and target')}");
306 return;
306 return;
307 }
307 }
308 var url = pyroutes.url('repo_compare', url_data);
308 var url = pyroutes.url('repo_compare', url_data);
309
309
310 // lock PR button, so we cannot send PR before it's calculated
310 // lock PR button, so we cannot send PR before it's calculated
311 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
311 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
312
312
313 if (loadRepoRefDiffPreview._currentRequest) {
313 if (loadRepoRefDiffPreview._currentRequest) {
314 loadRepoRefDiffPreview._currentRequest.abort();
314 loadRepoRefDiffPreview._currentRequest.abort();
315 }
315 }
316
316
317 loadRepoRefDiffPreview._currentRequest = $.get(url)
317 loadRepoRefDiffPreview._currentRequest = $.get(url)
318 .error(function(data, textStatus, errorThrown) {
318 .error(function(data, textStatus, errorThrown) {
319 if (textStatus !== 'abort') {
319 if (textStatus !== 'abort') {
320 alert(
320 alert(
321 "Error while processing request.\nError code {0} ({1}).".format(
321 "Error while processing request.\nError code {0} ({1}).".format(
322 data.status, data.statusText));
322 data.status, data.statusText));
323 }
323 }
324
324
325 })
325 })
326 .done(function(data) {
326 .done(function(data) {
327 loadRepoRefDiffPreview._currentRequest = null;
327 loadRepoRefDiffPreview._currentRequest = null;
328 $('#pull_request_overview').html(data);
328 $('#pull_request_overview').html(data);
329
329
330 var commitElements = $(data).find('tr[commit_id]');
330 var commitElements = $(data).find('tr[commit_id]');
331
331
332 var prTitleAndDesc = getTitleAndDescription(
332 var prTitleAndDesc = getTitleAndDescription(
333 sourceRef()[1], commitElements, 5);
333 sourceRef()[1], commitElements, 5);
334
334
335 var title = prTitleAndDesc[0];
335 var title = prTitleAndDesc[0];
336 var proposedDescription = prTitleAndDesc[1];
336 var proposedDescription = prTitleAndDesc[1];
337
337
338 var useGeneratedTitle = (
338 var useGeneratedTitle = (
339 $('#pullrequest_title').hasClass('autogenerated-title') ||
339 $('#pullrequest_title').hasClass('autogenerated-title') ||
340 $('#pullrequest_title').val() === "");
340 $('#pullrequest_title').val() === "");
341
341
342 if (title && useGeneratedTitle) {
342 if (title && useGeneratedTitle) {
343 // use generated title if we haven't specified our own
343 // use generated title if we haven't specified our own
344 $('#pullrequest_title').val(title);
344 $('#pullrequest_title').val(title);
345 $('#pullrequest_title').addClass('autogenerated-title');
345 $('#pullrequest_title').addClass('autogenerated-title');
346
346
347 }
347 }
348
348
349 var useGeneratedDescription = (
349 var useGeneratedDescription = (
350 !codeMirrorInstance._userDefinedDesc ||
350 !codeMirrorInstance._userDefinedDesc ||
351 codeMirrorInstance.getValue() === "");
351 codeMirrorInstance.getValue() === "");
352
352
353 if (proposedDescription && useGeneratedDescription) {
353 if (proposedDescription && useGeneratedDescription) {
354 // set proposed content, if we haven't defined our own,
354 // set proposed content, if we haven't defined our own,
355 // or we don't have description written
355 // or we don't have description written
356 codeMirrorInstance._userDefinedDesc = false; // reset state
356 codeMirrorInstance._userDefinedDesc = false; // reset state
357 codeMirrorInstance.setValue(proposedDescription);
357 codeMirrorInstance.setValue(proposedDescription);
358 }
358 }
359
359
360 var msg = '';
360 var msg = '';
361 if (commitElements.length === 1) {
361 if (commitElements.length === 1) {
362 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
362 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
363 } else {
363 } else {
364 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
364 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
365 }
365 }
366
366
367 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
367 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
368
368
369 if (commitElements.length) {
369 if (commitElements.length) {
370 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
370 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
371 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
371 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
372 }
372 }
373 else {
373 else {
374 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
374 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
375 }
375 }
376
376
377
377
378 });
378 });
379 };
379 };
380
380
381 var Select2Box = function(element, overrides) {
381 var Select2Box = function(element, overrides) {
382 var globalDefaults = {
382 var globalDefaults = {
383 dropdownAutoWidth: true,
383 dropdownAutoWidth: true,
384 containerCssClass: "drop-menu",
384 containerCssClass: "drop-menu",
385 dropdownCssClass: "drop-menu-dropdown"
385 dropdownCssClass: "drop-menu-dropdown"
386 };
386 };
387
387
388 var initSelect2 = function(defaultOptions) {
388 var initSelect2 = function(defaultOptions) {
389 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
389 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
390 element.select2(options);
390 element.select2(options);
391 };
391 };
392
392
393 return {
393 return {
394 initRef: function() {
394 initRef: function() {
395 var defaultOptions = {
395 var defaultOptions = {
396 minimumResultsForSearch: 5,
396 minimumResultsForSearch: 5,
397 formatSelection: formatRefSelection
397 formatSelection: formatRefSelection
398 };
398 };
399
399
400 initSelect2(defaultOptions);
400 initSelect2(defaultOptions);
401 },
401 },
402
402
403 initRepo: function(defaultValue, readOnly) {
403 initRepo: function(defaultValue, readOnly) {
404 var defaultOptions = {
404 var defaultOptions = {
405 initSelection : function (element, callback) {
405 initSelection : function (element, callback) {
406 var data = {id: defaultValue, text: defaultValue};
406 var data = {id: defaultValue, text: defaultValue};
407 callback(data);
407 callback(data);
408 }
408 }
409 };
409 };
410
410
411 initSelect2(defaultOptions);
411 initSelect2(defaultOptions);
412
412
413 element.select2('val', defaultSourceRepo);
413 element.select2('val', defaultSourceRepo);
414 if (readOnly === true) {
414 if (readOnly === true) {
415 element.select2('readonly', true);
415 element.select2('readonly', true);
416 }
416 }
417 }
417 }
418 };
418 };
419 };
419 };
420
420
421 var initTargetRefs = function(refsData, selectedRef) {
421 var initTargetRefs = function(refsData, selectedRef) {
422
422
423 Select2Box($targetRef, {
423 Select2Box($targetRef, {
424 placeholder: "${_('Select commit reference')}",
424 placeholder: "${_('Select commit reference')}",
425 query: function(query) {
425 query: function(query) {
426 queryTargetRefs(refsData, query);
426 queryTargetRefs(refsData, query);
427 },
427 },
428 initSelection : initRefSelection(selectedRef)
428 initSelection : initRefSelection(selectedRef)
429 }).initRef();
429 }).initRef();
430
430
431 if (!(selectedRef === undefined)) {
431 if (!(selectedRef === undefined)) {
432 $targetRef.select2('val', selectedRef);
432 $targetRef.select2('val', selectedRef);
433 }
433 }
434 };
434 };
435
435
436 var targetRepoChanged = function(repoData) {
436 var targetRepoChanged = function(repoData) {
437 // generate new DESC of target repo displayed next to select
437 // generate new DESC of target repo displayed next to select
438 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
438 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
439 $('#target_repo_desc').html(
439 $('#target_repo_desc').html(
440 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
440 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
441 );
441 );
442
442
443 // generate dynamic select2 for refs.
443 // generate dynamic select2 for refs.
444 initTargetRefs(repoData['refs']['select2_refs'],
444 initTargetRefs(repoData['refs']['select2_refs'],
445 repoData['refs']['selected_ref']);
445 repoData['refs']['selected_ref']);
446
446
447 };
447 };
448
448
449 var sourceRefSelect2 = Select2Box($sourceRef, {
449 var sourceRefSelect2 = Select2Box($sourceRef, {
450 placeholder: "${_('Select commit reference')}",
450 placeholder: "${_('Select commit reference')}",
451 query: function(query) {
451 query: function(query) {
452 var initialData = defaultSourceRepoData['refs']['select2_refs'];
452 var initialData = defaultSourceRepoData['refs']['select2_refs'];
453 queryTargetRefs(initialData, query)
453 queryTargetRefs(initialData, query)
454 },
454 },
455 initSelection: initRefSelection()
455 initSelection: initRefSelection()
456 }
456 }
457 );
457 );
458
458
459 var sourceRepoSelect2 = Select2Box($sourceRepo, {
459 var sourceRepoSelect2 = Select2Box($sourceRepo, {
460 query: function(query) {}
460 query: function(query) {}
461 });
461 });
462
462
463 var targetRepoSelect2 = Select2Box($targetRepo, {
463 var targetRepoSelect2 = Select2Box($targetRepo, {
464 cachedDataSource: {},
464 cachedDataSource: {},
465 query: $.debounce(250, function(query) {
465 query: $.debounce(250, function(query) {
466 queryTargetRepo(this, query);
466 queryTargetRepo(this, query);
467 }),
467 }),
468 formatResult: formatResult
468 formatResult: formatRepoResult
469 });
469 });
470
470
471 sourceRefSelect2.initRef();
471 sourceRefSelect2.initRef();
472
472
473 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
473 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
474
474
475 targetRepoSelect2.initRepo(defaultTargetRepo, false);
475 targetRepoSelect2.initRepo(defaultTargetRepo, false);
476
476
477 $sourceRef.on('change', function(e){
477 $sourceRef.on('change', function(e){
478 loadRepoRefDiffPreview();
478 loadRepoRefDiffPreview();
479 reviewersController.loadDefaultReviewers(
479 reviewersController.loadDefaultReviewers(
480 sourceRepo(), sourceRef(), targetRepo(), targetRef());
480 sourceRepo(), sourceRef(), targetRepo(), targetRef());
481 });
481 });
482
482
483 $targetRef.on('change', function(e){
483 $targetRef.on('change', function(e){
484 loadRepoRefDiffPreview();
484 loadRepoRefDiffPreview();
485 reviewersController.loadDefaultReviewers(
485 reviewersController.loadDefaultReviewers(
486 sourceRepo(), sourceRef(), targetRepo(), targetRef());
486 sourceRepo(), sourceRef(), targetRepo(), targetRef());
487 });
487 });
488
488
489 $targetRepo.on('change', function(e){
489 $targetRepo.on('change', function(e){
490 var repoName = $(this).val();
490 var repoName = $(this).val();
491 calculateContainerWidth();
491 calculateContainerWidth();
492 $targetRef.select2('destroy');
492 $targetRef.select2('destroy');
493 $('#target_ref_loading').show();
493 $('#target_ref_loading').show();
494
494
495 $.ajax({
495 $.ajax({
496 url: pyroutes.url('pullrequest_repo_refs',
496 url: pyroutes.url('pullrequest_repo_refs',
497 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
497 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
498 data: {},
498 data: {},
499 dataType: 'json',
499 dataType: 'json',
500 type: 'GET',
500 type: 'GET',
501 success: function(data) {
501 success: function(data) {
502 $('#target_ref_loading').hide();
502 $('#target_ref_loading').hide();
503 targetRepoChanged(data);
503 targetRepoChanged(data);
504 loadRepoRefDiffPreview();
504 loadRepoRefDiffPreview();
505 },
505 },
506 error: function(data, textStatus, errorThrown) {
506 error: function(data, textStatus, errorThrown) {
507 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
507 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
508 }
508 }
509 })
509 })
510
510
511 });
511 });
512
512
513 prButtonLock(true, "${_('Please select source and target')}", 'all');
513 prButtonLock(true, "${_('Please select source and target')}", 'all');
514
514
515 // auto-load on init, the target refs select2
515 // auto-load on init, the target refs select2
516 calculateContainerWidth();
516 calculateContainerWidth();
517 targetRepoChanged(defaultTargetRepoData);
517 targetRepoChanged(defaultTargetRepoData);
518
518
519 $('#pullrequest_title').on('keyup', function(e){
519 $('#pullrequest_title').on('keyup', function(e){
520 $(this).removeClass('autogenerated-title');
520 $(this).removeClass('autogenerated-title');
521 });
521 });
522
522
523 % if c.default_source_ref:
523 % if c.default_source_ref:
524 // in case we have a pre-selected value, use it now
524 // in case we have a pre-selected value, use it now
525 $sourceRef.select2('val', '${c.default_source_ref}');
525 $sourceRef.select2('val', '${c.default_source_ref}');
526 // diff preview load
526 // diff preview load
527 loadRepoRefDiffPreview();
527 loadRepoRefDiffPreview();
528 // default reviewers
528 // default reviewers
529 reviewersController.loadDefaultReviewers(
529 reviewersController.loadDefaultReviewers(
530 sourceRepo(), sourceRef(), targetRepo(), targetRef());
530 sourceRepo(), sourceRef(), targetRepo(), targetRef());
531 % endif
531 % endif
532
532
533 ReviewerAutoComplete('#user');
533 ReviewerAutoComplete('#user');
534 });
534 });
535 </script>
535 </script>
536
536
537 </%def>
537 </%def>
General Comments 0
You need to be logged in to leave comments. Login now