|
@@
-1,792
+1,791
|
|
1
|
# -*- coding: utf-8 -*-
|
|
1
|
# -*- coding: utf-8 -*-
|
|
2
|
|
|
2
|
|
|
3
|
# Copyright (C) 2010-2020 RhodeCode GmbH
|
|
3
|
# Copyright (C) 2010-2020 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
|
from pyramid.httpexceptions import (
|
|
24
|
from pyramid.httpexceptions import (
|
|
25
|
HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
|
|
25
|
HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
|
|
26
|
from pyramid.view import view_config
|
|
26
|
from pyramid.view import view_config
|
|
27
|
from pyramid.renderers import render
|
|
27
|
from pyramid.renderers import render
|
|
28
|
from pyramid.response import Response
|
|
28
|
from pyramid.response import Response
|
|
29
|
|
|
29
|
|
|
30
|
from rhodecode.apps._base import RepoAppView
|
|
30
|
from rhodecode.apps._base import RepoAppView
|
|
31
|
from rhodecode.apps.file_store import utils as store_utils
|
|
31
|
from rhodecode.apps.file_store import utils as store_utils
|
|
32
|
from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
|
|
32
|
from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
|
|
33
|
|
|
33
|
|
|
34
|
from rhodecode.lib import diffs, codeblocks, channelstream
|
|
34
|
from rhodecode.lib import diffs, codeblocks, channelstream
|
|
35
|
from rhodecode.lib.auth import (
|
|
35
|
from rhodecode.lib.auth import (
|
|
36
|
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
|
|
36
|
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
|
|
37
|
from rhodecode.lib.ext_json import json
|
|
37
|
from rhodecode.lib.ext_json import json
|
|
38
|
from rhodecode.lib.compat import OrderedDict
|
|
38
|
from rhodecode.lib.compat import OrderedDict
|
|
39
|
from rhodecode.lib.diffs import (
|
|
39
|
from rhodecode.lib.diffs import (
|
|
40
|
cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
|
|
40
|
cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
|
|
41
|
get_diff_whitespace_flag)
|
|
41
|
get_diff_whitespace_flag)
|
|
42
|
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
|
|
42
|
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
|
|
43
|
import rhodecode.lib.helpers as h
|
|
43
|
import rhodecode.lib.helpers as h
|
|
44
|
from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
|
|
44
|
from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
|
|
45
|
from rhodecode.lib.vcs.backends.base import EmptyCommit
|
|
45
|
from rhodecode.lib.vcs.backends.base import EmptyCommit
|
|
46
|
from rhodecode.lib.vcs.exceptions import (
|
|
46
|
from rhodecode.lib.vcs.exceptions import (
|
|
47
|
RepositoryError, CommitDoesNotExistError)
|
|
47
|
RepositoryError, CommitDoesNotExistError)
|
|
48
|
from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
|
|
48
|
from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
|
|
49
|
ChangesetCommentHistory
|
|
49
|
ChangesetCommentHistory
|
|
50
|
from rhodecode.model.changeset_status import ChangesetStatusModel
|
|
50
|
from rhodecode.model.changeset_status import ChangesetStatusModel
|
|
51
|
from rhodecode.model.comment import CommentsModel
|
|
51
|
from rhodecode.model.comment import CommentsModel
|
|
52
|
from rhodecode.model.meta import Session
|
|
52
|
from rhodecode.model.meta import Session
|
|
53
|
from rhodecode.model.settings import VcsSettingsModel
|
|
53
|
from rhodecode.model.settings import VcsSettingsModel
|
|
54
|
|
|
54
|
|
|
55
|
log = logging.getLogger(__name__)
|
|
55
|
log = logging.getLogger(__name__)
|
|
56
|
|
|
56
|
|
|
57
|
|
|
57
|
|
|
58
|
def _update_with_GET(params, request):
|
|
58
|
def _update_with_GET(params, request):
|
|
59
|
for k in ['diff1', 'diff2', 'diff']:
|
|
59
|
for k in ['diff1', 'diff2', 'diff']:
|
|
60
|
params[k] += request.GET.getall(k)
|
|
60
|
params[k] += request.GET.getall(k)
|
|
61
|
|
|
61
|
|
|
62
|
|
|
62
|
|
|
63
|
class RepoCommitsView(RepoAppView):
|
|
63
|
class RepoCommitsView(RepoAppView):
|
|
64
|
def load_default_context(self):
|
|
64
|
def load_default_context(self):
|
|
65
|
c = self._get_local_tmpl_context(include_app_defaults=True)
|
|
65
|
c = self._get_local_tmpl_context(include_app_defaults=True)
|
|
66
|
c.rhodecode_repo = self.rhodecode_vcs_repo
|
|
66
|
c.rhodecode_repo = self.rhodecode_vcs_repo
|
|
67
|
|
|
67
|
|
|
68
|
return c
|
|
68
|
return c
|
|
69
|
|
|
69
|
|
|
70
|
def _is_diff_cache_enabled(self, target_repo):
|
|
70
|
def _is_diff_cache_enabled(self, target_repo):
|
|
71
|
caching_enabled = self._get_general_setting(
|
|
71
|
caching_enabled = self._get_general_setting(
|
|
72
|
target_repo, 'rhodecode_diff_cache')
|
|
72
|
target_repo, 'rhodecode_diff_cache')
|
|
73
|
log.debug('Diff caching enabled: %s', caching_enabled)
|
|
73
|
log.debug('Diff caching enabled: %s', caching_enabled)
|
|
74
|
return caching_enabled
|
|
74
|
return caching_enabled
|
|
75
|
|
|
75
|
|
|
76
|
def _commit(self, commit_id_range, method):
|
|
76
|
def _commit(self, commit_id_range, method):
|
|
77
|
_ = self.request.translate
|
|
77
|
_ = self.request.translate
|
|
78
|
c = self.load_default_context()
|
|
78
|
c = self.load_default_context()
|
|
79
|
c.fulldiff = self.request.GET.get('fulldiff')
|
|
79
|
c.fulldiff = self.request.GET.get('fulldiff')
|
|
80
|
|
|
80
|
|
|
81
|
# fetch global flags of ignore ws or context lines
|
|
81
|
# fetch global flags of ignore ws or context lines
|
|
82
|
diff_context = get_diff_context(self.request)
|
|
82
|
diff_context = get_diff_context(self.request)
|
|
83
|
hide_whitespace_changes = get_diff_whitespace_flag(self.request)
|
|
83
|
hide_whitespace_changes = get_diff_whitespace_flag(self.request)
|
|
84
|
|
|
84
|
|
|
85
|
# diff_limit will cut off the whole diff if the limit is applied
|
|
85
|
# diff_limit will cut off the whole diff if the limit is applied
|
|
86
|
# otherwise it will just hide the big files from the front-end
|
|
86
|
# otherwise it will just hide the big files from the front-end
|
|
87
|
diff_limit = c.visual.cut_off_limit_diff
|
|
87
|
diff_limit = c.visual.cut_off_limit_diff
|
|
88
|
file_limit = c.visual.cut_off_limit_file
|
|
88
|
file_limit = c.visual.cut_off_limit_file
|
|
89
|
|
|
89
|
|
|
90
|
# get ranges of commit ids if preset
|
|
90
|
# get ranges of commit ids if preset
|
|
91
|
commit_range = commit_id_range.split('...')[:2]
|
|
91
|
commit_range = commit_id_range.split('...')[:2]
|
|
92
|
|
|
92
|
|
|
93
|
try:
|
|
93
|
try:
|
|
94
|
pre_load = ['affected_files', 'author', 'branch', 'date',
|
|
94
|
pre_load = ['affected_files', 'author', 'branch', 'date',
|
|
95
|
'message', 'parents']
|
|
95
|
'message', 'parents']
|
|
96
|
if self.rhodecode_vcs_repo.alias == 'hg':
|
|
96
|
if self.rhodecode_vcs_repo.alias == 'hg':
|
|
97
|
pre_load += ['hidden', 'obsolete', 'phase']
|
|
97
|
pre_load += ['hidden', 'obsolete', 'phase']
|
|
98
|
|
|
98
|
|
|
99
|
if len(commit_range) == 2:
|
|
99
|
if len(commit_range) == 2:
|
|
100
|
commits = self.rhodecode_vcs_repo.get_commits(
|
|
100
|
commits = self.rhodecode_vcs_repo.get_commits(
|
|
101
|
start_id=commit_range[0], end_id=commit_range[1],
|
|
101
|
start_id=commit_range[0], end_id=commit_range[1],
|
|
102
|
pre_load=pre_load, translate_tags=False)
|
|
102
|
pre_load=pre_load, translate_tags=False)
|
|
103
|
commits = list(commits)
|
|
103
|
commits = list(commits)
|
|
104
|
else:
|
|
104
|
else:
|
|
105
|
commits = [self.rhodecode_vcs_repo.get_commit(
|
|
105
|
commits = [self.rhodecode_vcs_repo.get_commit(
|
|
106
|
commit_id=commit_id_range, pre_load=pre_load)]
|
|
106
|
commit_id=commit_id_range, pre_load=pre_load)]
|
|
107
|
|
|
107
|
|
|
108
|
c.commit_ranges = commits
|
|
108
|
c.commit_ranges = commits
|
|
109
|
if not c.commit_ranges:
|
|
109
|
if not c.commit_ranges:
|
|
110
|
raise RepositoryError('The commit range returned an empty result')
|
|
110
|
raise RepositoryError('The commit range returned an empty result')
|
|
111
|
except CommitDoesNotExistError as e:
|
|
111
|
except CommitDoesNotExistError as e:
|
|
112
|
msg = _('No such commit exists. Org exception: `{}`').format(e)
|
|
112
|
msg = _('No such commit exists. Org exception: `{}`').format(e)
|
|
113
|
h.flash(msg, category='error')
|
|
113
|
h.flash(msg, category='error')
|
|
114
|
raise HTTPNotFound()
|
|
114
|
raise HTTPNotFound()
|
|
115
|
except Exception:
|
|
115
|
except Exception:
|
|
116
|
log.exception("General failure")
|
|
116
|
log.exception("General failure")
|
|
117
|
raise HTTPNotFound()
|
|
117
|
raise HTTPNotFound()
|
|
118
|
single_commit = len(c.commit_ranges) == 1
|
|
118
|
single_commit = len(c.commit_ranges) == 1
|
|
119
|
|
|
119
|
|
|
120
|
c.changes = OrderedDict()
|
|
120
|
c.changes = OrderedDict()
|
|
121
|
c.lines_added = 0
|
|
121
|
c.lines_added = 0
|
|
122
|
c.lines_deleted = 0
|
|
122
|
c.lines_deleted = 0
|
|
123
|
|
|
123
|
|
|
124
|
# auto collapse if we have more than limit
|
|
124
|
# auto collapse if we have more than limit
|
|
125
|
collapse_limit = diffs.DiffProcessor._collapse_commits_over
|
|
125
|
collapse_limit = diffs.DiffProcessor._collapse_commits_over
|
|
126
|
c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
|
|
126
|
c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
|
|
127
|
|
|
127
|
|
|
128
|
c.commit_statuses = ChangesetStatus.STATUSES
|
|
128
|
c.commit_statuses = ChangesetStatus.STATUSES
|
|
129
|
c.inline_comments = []
|
|
129
|
c.inline_comments = []
|
|
130
|
c.files = []
|
|
130
|
c.files = []
|
|
131
|
|
|
131
|
|
|
132
|
c.comments = []
|
|
132
|
c.comments = []
|
|
133
|
c.unresolved_comments = []
|
|
133
|
c.unresolved_comments = []
|
|
134
|
c.resolved_comments = []
|
|
134
|
c.resolved_comments = []
|
|
135
|
|
|
135
|
|
|
136
|
# Single commit
|
|
136
|
# Single commit
|
|
137
|
if single_commit:
|
|
137
|
if single_commit:
|
|
138
|
commit = c.commit_ranges[0]
|
|
138
|
commit = c.commit_ranges[0]
|
|
139
|
c.comments = CommentsModel().get_comments(
|
|
139
|
c.comments = CommentsModel().get_comments(
|
|
140
|
self.db_repo.repo_id,
|
|
140
|
self.db_repo.repo_id,
|
|
141
|
revision=commit.raw_id)
|
|
141
|
revision=commit.raw_id)
|
|
142
|
|
|
142
|
|
|
143
|
# comments from PR
|
|
143
|
# comments from PR
|
|
144
|
statuses = ChangesetStatusModel().get_statuses(
|
|
144
|
statuses = ChangesetStatusModel().get_statuses(
|
|
145
|
self.db_repo.repo_id, commit.raw_id,
|
|
145
|
self.db_repo.repo_id, commit.raw_id,
|
|
146
|
with_revisions=True)
|
|
146
|
with_revisions=True)
|
|
147
|
|
|
147
|
|
|
148
|
prs = set()
|
|
148
|
prs = set()
|
|
149
|
reviewers = list()
|
|
149
|
reviewers = list()
|
|
150
|
reviewers_duplicates = set() # to not have duplicates from multiple votes
|
|
150
|
reviewers_duplicates = set() # to not have duplicates from multiple votes
|
|
151
|
for c_status in statuses:
|
|
151
|
for c_status in statuses:
|
|
152
|
|
|
152
|
|
|
153
|
# extract associated pull-requests from votes
|
|
153
|
# extract associated pull-requests from votes
|
|
154
|
if c_status.pull_request:
|
|
154
|
if c_status.pull_request:
|
|
155
|
prs.add(c_status.pull_request)
|
|
155
|
prs.add(c_status.pull_request)
|
|
156
|
|
|
156
|
|
|
157
|
# extract reviewers
|
|
157
|
# extract reviewers
|
|
158
|
_user_id = c_status.author.user_id
|
|
158
|
_user_id = c_status.author.user_id
|
|
159
|
if _user_id not in reviewers_duplicates:
|
|
159
|
if _user_id not in reviewers_duplicates:
|
|
160
|
reviewers.append(
|
|
160
|
reviewers.append(
|
|
161
|
StrictAttributeDict({
|
|
161
|
StrictAttributeDict({
|
|
162
|
'user': c_status.author,
|
|
162
|
'user': c_status.author,
|
|
163
|
|
|
163
|
|
|
164
|
# fake attributed for commit, page that we don't have
|
|
164
|
# fake attributed for commit, page that we don't have
|
|
165
|
# but we share the display with PR page
|
|
165
|
# but we share the display with PR page
|
|
166
|
'mandatory': False,
|
|
166
|
'mandatory': False,
|
|
167
|
'reasons': [],
|
|
167
|
'reasons': [],
|
|
168
|
'rule_user_group_data': lambda: None
|
|
168
|
'rule_user_group_data': lambda: None
|
|
169
|
})
|
|
169
|
})
|
|
170
|
)
|
|
170
|
)
|
|
171
|
reviewers_duplicates.add(_user_id)
|
|
171
|
reviewers_duplicates.add(_user_id)
|
|
172
|
|
|
172
|
|
|
173
|
c.allowed_reviewers = reviewers
|
|
|
|
|
174
|
c.reviewers_count = len(reviewers)
|
|
173
|
c.reviewers_count = len(reviewers)
|
|
175
|
c.observers_count = 0
|
|
174
|
c.observers_count = 0
|
|
176
|
|
|
175
|
|
|
177
|
# from associated statuses, check the pull requests, and
|
|
176
|
# from associated statuses, check the pull requests, and
|
|
178
|
# show comments from them
|
|
177
|
# show comments from them
|
|
179
|
for pr in prs:
|
|
178
|
for pr in prs:
|
|
180
|
c.comments.extend(pr.comments)
|
|
179
|
c.comments.extend(pr.comments)
|
|
181
|
|
|
180
|
|
|
182
|
c.unresolved_comments = CommentsModel()\
|
|
181
|
c.unresolved_comments = CommentsModel()\
|
|
183
|
.get_commit_unresolved_todos(commit.raw_id)
|
|
182
|
.get_commit_unresolved_todos(commit.raw_id)
|
|
184
|
c.resolved_comments = CommentsModel()\
|
|
183
|
c.resolved_comments = CommentsModel()\
|
|
185
|
.get_commit_resolved_todos(commit.raw_id)
|
|
184
|
.get_commit_resolved_todos(commit.raw_id)
|
|
186
|
|
|
185
|
|
|
187
|
c.inline_comments_flat = CommentsModel()\
|
|
186
|
c.inline_comments_flat = CommentsModel()\
|
|
188
|
.get_commit_inline_comments(commit.raw_id)
|
|
187
|
.get_commit_inline_comments(commit.raw_id)
|
|
189
|
|
|
188
|
|
|
190
|
review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
|
|
189
|
review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
|
|
191
|
statuses, reviewers)
|
|
190
|
statuses, reviewers)
|
|
192
|
|
|
191
|
|
|
193
|
c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
|
|
192
|
c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
|
|
194
|
|
|
193
|
|
|
195
|
c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
|
|
194
|
c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
|
|
196
|
|
|
195
|
|
|
197
|
for review_obj, member, reasons, mandatory, status in review_statuses:
|
|
196
|
for review_obj, member, reasons, mandatory, status in review_statuses:
|
|
198
|
member_reviewer = h.reviewer_as_json(
|
|
197
|
member_reviewer = h.reviewer_as_json(
|
|
199
|
member, reasons=reasons, mandatory=mandatory, role=None,
|
|
198
|
member, reasons=reasons, mandatory=mandatory, role=None,
|
|
200
|
user_group=None
|
|
199
|
user_group=None
|
|
201
|
)
|
|
200
|
)
|
|
202
|
|
|
201
|
|
|
203
|
current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
|
|
202
|
current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
|
|
204
|
member_reviewer['review_status'] = current_review_status
|
|
203
|
member_reviewer['review_status'] = current_review_status
|
|
205
|
member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
|
|
204
|
member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
|
|
206
|
member_reviewer['allowed_to_update'] = False
|
|
205
|
member_reviewer['allowed_to_update'] = False
|
|
207
|
c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
|
|
206
|
c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
|
|
208
|
|
|
207
|
|
|
209
|
c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
|
|
208
|
c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
|
|
210
|
|
|
209
|
|
|
211
|
# NOTE(marcink): this uses the same voting logic as in pull-requests
|
|
210
|
# NOTE(marcink): this uses the same voting logic as in pull-requests
|
|
212
|
c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
|
|
211
|
c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
|
|
213
|
c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
|
|
212
|
c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
|
|
214
|
|
|
213
|
|
|
215
|
diff = None
|
|
214
|
diff = None
|
|
216
|
# Iterate over ranges (default commit view is always one commit)
|
|
215
|
# Iterate over ranges (default commit view is always one commit)
|
|
217
|
for commit in c.commit_ranges:
|
|
216
|
for commit in c.commit_ranges:
|
|
218
|
c.changes[commit.raw_id] = []
|
|
217
|
c.changes[commit.raw_id] = []
|
|
219
|
|
|
218
|
|
|
220
|
commit2 = commit
|
|
219
|
commit2 = commit
|
|
221
|
commit1 = commit.first_parent
|
|
220
|
commit1 = commit.first_parent
|
|
222
|
|
|
221
|
|
|
223
|
if method == 'show':
|
|
222
|
if method == 'show':
|
|
224
|
inline_comments = CommentsModel().get_inline_comments(
|
|
223
|
inline_comments = CommentsModel().get_inline_comments(
|
|
225
|
self.db_repo.repo_id, revision=commit.raw_id)
|
|
224
|
self.db_repo.repo_id, revision=commit.raw_id)
|
|
226
|
c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
|
|
225
|
c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
|
|
227
|
inline_comments))
|
|
226
|
inline_comments))
|
|
228
|
c.inline_comments = inline_comments
|
|
227
|
c.inline_comments = inline_comments
|
|
229
|
|
|
228
|
|
|
230
|
cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
|
|
229
|
cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
|
|
231
|
self.db_repo)
|
|
230
|
self.db_repo)
|
|
232
|
cache_file_path = diff_cache_exist(
|
|
231
|
cache_file_path = diff_cache_exist(
|
|
233
|
cache_path, 'diff', commit.raw_id,
|
|
232
|
cache_path, 'diff', commit.raw_id,
|
|
234
|
hide_whitespace_changes, diff_context, c.fulldiff)
|
|
233
|
hide_whitespace_changes, diff_context, c.fulldiff)
|
|
235
|
|
|
234
|
|
|
236
|
caching_enabled = self._is_diff_cache_enabled(self.db_repo)
|
|
235
|
caching_enabled = self._is_diff_cache_enabled(self.db_repo)
|
|
237
|
force_recache = str2bool(self.request.GET.get('force_recache'))
|
|
236
|
force_recache = str2bool(self.request.GET.get('force_recache'))
|
|
238
|
|
|
237
|
|
|
239
|
cached_diff = None
|
|
238
|
cached_diff = None
|
|
240
|
if caching_enabled:
|
|
239
|
if caching_enabled:
|
|
241
|
cached_diff = load_cached_diff(cache_file_path)
|
|
240
|
cached_diff = load_cached_diff(cache_file_path)
|
|
242
|
|
|
241
|
|
|
243
|
has_proper_diff_cache = cached_diff and cached_diff.get('diff')
|
|
242
|
has_proper_diff_cache = cached_diff and cached_diff.get('diff')
|
|
244
|
if not force_recache and has_proper_diff_cache:
|
|
243
|
if not force_recache and has_proper_diff_cache:
|
|
245
|
diffset = cached_diff['diff']
|
|
244
|
diffset = cached_diff['diff']
|
|
246
|
else:
|
|
245
|
else:
|
|
247
|
vcs_diff = self.rhodecode_vcs_repo.get_diff(
|
|
246
|
vcs_diff = self.rhodecode_vcs_repo.get_diff(
|
|
248
|
commit1, commit2,
|
|
247
|
commit1, commit2,
|
|
249
|
ignore_whitespace=hide_whitespace_changes,
|
|
248
|
ignore_whitespace=hide_whitespace_changes,
|
|
250
|
context=diff_context)
|
|
249
|
context=diff_context)
|
|
251
|
|
|
250
|
|
|
252
|
diff_processor = diffs.DiffProcessor(
|
|
251
|
diff_processor = diffs.DiffProcessor(
|
|
253
|
vcs_diff, format='newdiff', diff_limit=diff_limit,
|
|
252
|
vcs_diff, format='newdiff', diff_limit=diff_limit,
|
|
254
|
file_limit=file_limit, show_full_diff=c.fulldiff)
|
|
253
|
file_limit=file_limit, show_full_diff=c.fulldiff)
|
|
255
|
|
|
254
|
|
|
256
|
_parsed = diff_processor.prepare()
|
|
255
|
_parsed = diff_processor.prepare()
|
|
257
|
|
|
256
|
|
|
258
|
diffset = codeblocks.DiffSet(
|
|
257
|
diffset = codeblocks.DiffSet(
|
|
259
|
repo_name=self.db_repo_name,
|
|
258
|
repo_name=self.db_repo_name,
|
|
260
|
source_node_getter=codeblocks.diffset_node_getter(commit1),
|
|
259
|
source_node_getter=codeblocks.diffset_node_getter(commit1),
|
|
261
|
target_node_getter=codeblocks.diffset_node_getter(commit2))
|
|
260
|
target_node_getter=codeblocks.diffset_node_getter(commit2))
|
|
262
|
|
|
261
|
|
|
263
|
diffset = self.path_filter.render_patchset_filtered(
|
|
262
|
diffset = self.path_filter.render_patchset_filtered(
|
|
264
|
diffset, _parsed, commit1.raw_id, commit2.raw_id)
|
|
263
|
diffset, _parsed, commit1.raw_id, commit2.raw_id)
|
|
265
|
|
|
264
|
|
|
266
|
# save cached diff
|
|
265
|
# save cached diff
|
|
267
|
if caching_enabled:
|
|
266
|
if caching_enabled:
|
|
268
|
cache_diff(cache_file_path, diffset, None)
|
|
267
|
cache_diff(cache_file_path, diffset, None)
|
|
269
|
|
|
268
|
|
|
270
|
c.limited_diff = diffset.limited_diff
|
|
269
|
c.limited_diff = diffset.limited_diff
|
|
271
|
c.changes[commit.raw_id] = diffset
|
|
270
|
c.changes[commit.raw_id] = diffset
|
|
272
|
else:
|
|
271
|
else:
|
|
273
|
# TODO(marcink): no cache usage here...
|
|
272
|
# TODO(marcink): no cache usage here...
|
|
274
|
_diff = self.rhodecode_vcs_repo.get_diff(
|
|
273
|
_diff = self.rhodecode_vcs_repo.get_diff(
|
|
275
|
commit1, commit2,
|
|
274
|
commit1, commit2,
|
|
276
|
ignore_whitespace=hide_whitespace_changes, context=diff_context)
|
|
275
|
ignore_whitespace=hide_whitespace_changes, context=diff_context)
|
|
277
|
diff_processor = diffs.DiffProcessor(
|
|
276
|
diff_processor = diffs.DiffProcessor(
|
|
278
|
_diff, format='newdiff', diff_limit=diff_limit,
|
|
277
|
_diff, format='newdiff', diff_limit=diff_limit,
|
|
279
|
file_limit=file_limit, show_full_diff=c.fulldiff)
|
|
278
|
file_limit=file_limit, show_full_diff=c.fulldiff)
|
|
280
|
# downloads/raw we only need RAW diff nothing else
|
|
279
|
# downloads/raw we only need RAW diff nothing else
|
|
281
|
diff = self.path_filter.get_raw_patch(diff_processor)
|
|
280
|
diff = self.path_filter.get_raw_patch(diff_processor)
|
|
282
|
c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
|
|
281
|
c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
|
|
283
|
|
|
282
|
|
|
284
|
# sort comments by how they were generated
|
|
283
|
# sort comments by how they were generated
|
|
285
|
c.comments = sorted(c.comments, key=lambda x: x.comment_id)
|
|
284
|
c.comments = sorted(c.comments, key=lambda x: x.comment_id)
|
|
286
|
c.at_version_num = None
|
|
285
|
c.at_version_num = None
|
|
287
|
|
|
286
|
|
|
288
|
if len(c.commit_ranges) == 1:
|
|
287
|
if len(c.commit_ranges) == 1:
|
|
289
|
c.commit = c.commit_ranges[0]
|
|
288
|
c.commit = c.commit_ranges[0]
|
|
290
|
c.parent_tmpl = ''.join(
|
|
289
|
c.parent_tmpl = ''.join(
|
|
291
|
'# Parent %s\n' % x.raw_id for x in c.commit.parents)
|
|
290
|
'# Parent %s\n' % x.raw_id for x in c.commit.parents)
|
|
292
|
|
|
291
|
|
|
293
|
if method == 'download':
|
|
292
|
if method == 'download':
|
|
294
|
response = Response(diff)
|
|
293
|
response = Response(diff)
|
|
295
|
response.content_type = 'text/plain'
|
|
294
|
response.content_type = 'text/plain'
|
|
296
|
response.content_disposition = (
|
|
295
|
response.content_disposition = (
|
|
297
|
'attachment; filename=%s.diff' % commit_id_range[:12])
|
|
296
|
'attachment; filename=%s.diff' % commit_id_range[:12])
|
|
298
|
return response
|
|
297
|
return response
|
|
299
|
elif method == 'patch':
|
|
298
|
elif method == 'patch':
|
|
300
|
c.diff = safe_unicode(diff)
|
|
299
|
c.diff = safe_unicode(diff)
|
|
301
|
patch = render(
|
|
300
|
patch = render(
|
|
302
|
'rhodecode:templates/changeset/patch_changeset.mako',
|
|
301
|
'rhodecode:templates/changeset/patch_changeset.mako',
|
|
303
|
self._get_template_context(c), self.request)
|
|
302
|
self._get_template_context(c), self.request)
|
|
304
|
response = Response(patch)
|
|
303
|
response = Response(patch)
|
|
305
|
response.content_type = 'text/plain'
|
|
304
|
response.content_type = 'text/plain'
|
|
306
|
return response
|
|
305
|
return response
|
|
307
|
elif method == 'raw':
|
|
306
|
elif method == 'raw':
|
|
308
|
response = Response(diff)
|
|
307
|
response = Response(diff)
|
|
309
|
response.content_type = 'text/plain'
|
|
308
|
response.content_type = 'text/plain'
|
|
310
|
return response
|
|
309
|
return response
|
|
311
|
elif method == 'show':
|
|
310
|
elif method == 'show':
|
|
312
|
if len(c.commit_ranges) == 1:
|
|
311
|
if len(c.commit_ranges) == 1:
|
|
313
|
html = render(
|
|
312
|
html = render(
|
|
314
|
'rhodecode:templates/changeset/changeset.mako',
|
|
313
|
'rhodecode:templates/changeset/changeset.mako',
|
|
315
|
self._get_template_context(c), self.request)
|
|
314
|
self._get_template_context(c), self.request)
|
|
316
|
return Response(html)
|
|
315
|
return Response(html)
|
|
317
|
else:
|
|
316
|
else:
|
|
318
|
c.ancestor = None
|
|
317
|
c.ancestor = None
|
|
319
|
c.target_repo = self.db_repo
|
|
318
|
c.target_repo = self.db_repo
|
|
320
|
html = render(
|
|
319
|
html = render(
|
|
321
|
'rhodecode:templates/changeset/changeset_range.mako',
|
|
320
|
'rhodecode:templates/changeset/changeset_range.mako',
|
|
322
|
self._get_template_context(c), self.request)
|
|
321
|
self._get_template_context(c), self.request)
|
|
323
|
return Response(html)
|
|
322
|
return Response(html)
|
|
324
|
|
|
323
|
|
|
325
|
raise HTTPBadRequest()
|
|
324
|
raise HTTPBadRequest()
|
|
326
|
|
|
325
|
|
|
327
|
@LoginRequired()
|
|
326
|
@LoginRequired()
|
|
328
|
@HasRepoPermissionAnyDecorator(
|
|
327
|
@HasRepoPermissionAnyDecorator(
|
|
329
|
'repository.read', 'repository.write', 'repository.admin')
|
|
328
|
'repository.read', 'repository.write', 'repository.admin')
|
|
330
|
@view_config(
|
|
329
|
@view_config(
|
|
331
|
route_name='repo_commit', request_method='GET',
|
|
330
|
route_name='repo_commit', request_method='GET',
|
|
332
|
renderer=None)
|
|
331
|
renderer=None)
|
|
333
|
def repo_commit_show(self):
|
|
332
|
def repo_commit_show(self):
|
|
334
|
commit_id = self.request.matchdict['commit_id']
|
|
333
|
commit_id = self.request.matchdict['commit_id']
|
|
335
|
return self._commit(commit_id, method='show')
|
|
334
|
return self._commit(commit_id, method='show')
|
|
336
|
|
|
335
|
|
|
337
|
@LoginRequired()
|
|
336
|
@LoginRequired()
|
|
338
|
@HasRepoPermissionAnyDecorator(
|
|
337
|
@HasRepoPermissionAnyDecorator(
|
|
339
|
'repository.read', 'repository.write', 'repository.admin')
|
|
338
|
'repository.read', 'repository.write', 'repository.admin')
|
|
340
|
@view_config(
|
|
339
|
@view_config(
|
|
341
|
route_name='repo_commit_raw', request_method='GET',
|
|
340
|
route_name='repo_commit_raw', request_method='GET',
|
|
342
|
renderer=None)
|
|
341
|
renderer=None)
|
|
343
|
@view_config(
|
|
342
|
@view_config(
|
|
344
|
route_name='repo_commit_raw_deprecated', request_method='GET',
|
|
343
|
route_name='repo_commit_raw_deprecated', request_method='GET',
|
|
345
|
renderer=None)
|
|
344
|
renderer=None)
|
|
346
|
def repo_commit_raw(self):
|
|
345
|
def repo_commit_raw(self):
|
|
347
|
commit_id = self.request.matchdict['commit_id']
|
|
346
|
commit_id = self.request.matchdict['commit_id']
|
|
348
|
return self._commit(commit_id, method='raw')
|
|
347
|
return self._commit(commit_id, method='raw')
|
|
349
|
|
|
348
|
|
|
350
|
@LoginRequired()
|
|
349
|
@LoginRequired()
|
|
351
|
@HasRepoPermissionAnyDecorator(
|
|
350
|
@HasRepoPermissionAnyDecorator(
|
|
352
|
'repository.read', 'repository.write', 'repository.admin')
|
|
351
|
'repository.read', 'repository.write', 'repository.admin')
|
|
353
|
@view_config(
|
|
352
|
@view_config(
|
|
354
|
route_name='repo_commit_patch', request_method='GET',
|
|
353
|
route_name='repo_commit_patch', request_method='GET',
|
|
355
|
renderer=None)
|
|
354
|
renderer=None)
|
|
356
|
def repo_commit_patch(self):
|
|
355
|
def repo_commit_patch(self):
|
|
357
|
commit_id = self.request.matchdict['commit_id']
|
|
356
|
commit_id = self.request.matchdict['commit_id']
|
|
358
|
return self._commit(commit_id, method='patch')
|
|
357
|
return self._commit(commit_id, method='patch')
|
|
359
|
|
|
358
|
|
|
360
|
@LoginRequired()
|
|
359
|
@LoginRequired()
|
|
361
|
@HasRepoPermissionAnyDecorator(
|
|
360
|
@HasRepoPermissionAnyDecorator(
|
|
362
|
'repository.read', 'repository.write', 'repository.admin')
|
|
361
|
'repository.read', 'repository.write', 'repository.admin')
|
|
363
|
@view_config(
|
|
362
|
@view_config(
|
|
364
|
route_name='repo_commit_download', request_method='GET',
|
|
363
|
route_name='repo_commit_download', request_method='GET',
|
|
365
|
renderer=None)
|
|
364
|
renderer=None)
|
|
366
|
def repo_commit_download(self):
|
|
365
|
def repo_commit_download(self):
|
|
367
|
commit_id = self.request.matchdict['commit_id']
|
|
366
|
commit_id = self.request.matchdict['commit_id']
|
|
368
|
return self._commit(commit_id, method='download')
|
|
367
|
return self._commit(commit_id, method='download')
|
|
369
|
|
|
368
|
|
|
370
|
@LoginRequired()
|
|
369
|
@LoginRequired()
|
|
371
|
@NotAnonymous()
|
|
370
|
@NotAnonymous()
|
|
372
|
@HasRepoPermissionAnyDecorator(
|
|
371
|
@HasRepoPermissionAnyDecorator(
|
|
373
|
'repository.read', 'repository.write', 'repository.admin')
|
|
372
|
'repository.read', 'repository.write', 'repository.admin')
|
|
374
|
@CSRFRequired()
|
|
373
|
@CSRFRequired()
|
|
375
|
@view_config(
|
|
374
|
@view_config(
|
|
376
|
route_name='repo_commit_comment_create', request_method='POST',
|
|
375
|
route_name='repo_commit_comment_create', request_method='POST',
|
|
377
|
renderer='json_ext')
|
|
376
|
renderer='json_ext')
|
|
378
|
def repo_commit_comment_create(self):
|
|
377
|
def repo_commit_comment_create(self):
|
|
379
|
_ = self.request.translate
|
|
378
|
_ = self.request.translate
|
|
380
|
commit_id = self.request.matchdict['commit_id']
|
|
379
|
commit_id = self.request.matchdict['commit_id']
|
|
381
|
|
|
380
|
|
|
382
|
c = self.load_default_context()
|
|
381
|
c = self.load_default_context()
|
|
383
|
status = self.request.POST.get('changeset_status', None)
|
|
382
|
status = self.request.POST.get('changeset_status', None)
|
|
384
|
text = self.request.POST.get('text')
|
|
383
|
text = self.request.POST.get('text')
|
|
385
|
comment_type = self.request.POST.get('comment_type')
|
|
384
|
comment_type = self.request.POST.get('comment_type')
|
|
386
|
resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
|
|
385
|
resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
|
|
387
|
|
|
386
|
|
|
388
|
if status:
|
|
387
|
if status:
|
|
389
|
text = text or (_('Status change %(transition_icon)s %(status)s')
|
|
388
|
text = text or (_('Status change %(transition_icon)s %(status)s')
|
|
390
|
% {'transition_icon': '>',
|
|
389
|
% {'transition_icon': '>',
|
|
391
|
'status': ChangesetStatus.get_status_lbl(status)})
|
|
390
|
'status': ChangesetStatus.get_status_lbl(status)})
|
|
392
|
|
|
391
|
|
|
393
|
multi_commit_ids = []
|
|
392
|
multi_commit_ids = []
|
|
394
|
for _commit_id in self.request.POST.get('commit_ids', '').split(','):
|
|
393
|
for _commit_id in self.request.POST.get('commit_ids', '').split(','):
|
|
395
|
if _commit_id not in ['', None, EmptyCommit.raw_id]:
|
|
394
|
if _commit_id not in ['', None, EmptyCommit.raw_id]:
|
|
396
|
if _commit_id not in multi_commit_ids:
|
|
395
|
if _commit_id not in multi_commit_ids:
|
|
397
|
multi_commit_ids.append(_commit_id)
|
|
396
|
multi_commit_ids.append(_commit_id)
|
|
398
|
|
|
397
|
|
|
399
|
commit_ids = multi_commit_ids or [commit_id]
|
|
398
|
commit_ids = multi_commit_ids or [commit_id]
|
|
400
|
|
|
399
|
|
|
401
|
comment = None
|
|
400
|
comment = None
|
|
402
|
for current_id in filter(None, commit_ids):
|
|
401
|
for current_id in filter(None, commit_ids):
|
|
403
|
comment = CommentsModel().create(
|
|
402
|
comment = CommentsModel().create(
|
|
404
|
text=text,
|
|
403
|
text=text,
|
|
405
|
repo=self.db_repo.repo_id,
|
|
404
|
repo=self.db_repo.repo_id,
|
|
406
|
user=self._rhodecode_db_user.user_id,
|
|
405
|
user=self._rhodecode_db_user.user_id,
|
|
407
|
commit_id=current_id,
|
|
406
|
commit_id=current_id,
|
|
408
|
f_path=self.request.POST.get('f_path'),
|
|
407
|
f_path=self.request.POST.get('f_path'),
|
|
409
|
line_no=self.request.POST.get('line'),
|
|
408
|
line_no=self.request.POST.get('line'),
|
|
410
|
status_change=(ChangesetStatus.get_status_lbl(status)
|
|
409
|
status_change=(ChangesetStatus.get_status_lbl(status)
|
|
411
|
if status else None),
|
|
410
|
if status else None),
|
|
412
|
status_change_type=status,
|
|
411
|
status_change_type=status,
|
|
413
|
comment_type=comment_type,
|
|
412
|
comment_type=comment_type,
|
|
414
|
resolves_comment_id=resolves_comment_id,
|
|
413
|
resolves_comment_id=resolves_comment_id,
|
|
415
|
auth_user=self._rhodecode_user
|
|
414
|
auth_user=self._rhodecode_user
|
|
416
|
)
|
|
415
|
)
|
|
417
|
is_inline = bool(comment.f_path and comment.line_no)
|
|
416
|
is_inline = bool(comment.f_path and comment.line_no)
|
|
418
|
|
|
417
|
|
|
419
|
# get status if set !
|
|
418
|
# get status if set !
|
|
420
|
if status:
|
|
419
|
if status:
|
|
421
|
# if latest status was from pull request and it's closed
|
|
420
|
# if latest status was from pull request and it's closed
|
|
422
|
# disallow changing status !
|
|
421
|
# disallow changing status !
|
|
423
|
# dont_allow_on_closed_pull_request = True !
|
|
422
|
# dont_allow_on_closed_pull_request = True !
|
|
424
|
|
|
423
|
|
|
425
|
try:
|
|
424
|
try:
|
|
426
|
ChangesetStatusModel().set_status(
|
|
425
|
ChangesetStatusModel().set_status(
|
|
427
|
self.db_repo.repo_id,
|
|
426
|
self.db_repo.repo_id,
|
|
428
|
status,
|
|
427
|
status,
|
|
429
|
self._rhodecode_db_user.user_id,
|
|
428
|
self._rhodecode_db_user.user_id,
|
|
430
|
comment,
|
|
429
|
comment,
|
|
431
|
revision=current_id,
|
|
430
|
revision=current_id,
|
|
432
|
dont_allow_on_closed_pull_request=True
|
|
431
|
dont_allow_on_closed_pull_request=True
|
|
433
|
)
|
|
432
|
)
|
|
434
|
except StatusChangeOnClosedPullRequestError:
|
|
433
|
except StatusChangeOnClosedPullRequestError:
|
|
435
|
msg = _('Changing the status of a commit associated with '
|
|
434
|
msg = _('Changing the status of a commit associated with '
|
|
436
|
'a closed pull request is not allowed')
|
|
435
|
'a closed pull request is not allowed')
|
|
437
|
log.exception(msg)
|
|
436
|
log.exception(msg)
|
|
438
|
h.flash(msg, category='warning')
|
|
437
|
h.flash(msg, category='warning')
|
|
439
|
raise HTTPFound(h.route_path(
|
|
438
|
raise HTTPFound(h.route_path(
|
|
440
|
'repo_commit', repo_name=self.db_repo_name,
|
|
439
|
'repo_commit', repo_name=self.db_repo_name,
|
|
441
|
commit_id=current_id))
|
|
440
|
commit_id=current_id))
|
|
442
|
|
|
441
|
|
|
443
|
commit = self.db_repo.get_commit(current_id)
|
|
442
|
commit = self.db_repo.get_commit(current_id)
|
|
444
|
CommentsModel().trigger_commit_comment_hook(
|
|
443
|
CommentsModel().trigger_commit_comment_hook(
|
|
445
|
self.db_repo, self._rhodecode_user, 'create',
|
|
444
|
self.db_repo, self._rhodecode_user, 'create',
|
|
446
|
data={'comment': comment, 'commit': commit})
|
|
445
|
data={'comment': comment, 'commit': commit})
|
|
447
|
|
|
446
|
|
|
448
|
# finalize, commit and redirect
|
|
447
|
# finalize, commit and redirect
|
|
449
|
Session().commit()
|
|
448
|
Session().commit()
|
|
450
|
|
|
449
|
|
|
451
|
data = {
|
|
450
|
data = {
|
|
452
|
'target_id': h.safeid(h.safe_unicode(
|
|
451
|
'target_id': h.safeid(h.safe_unicode(
|
|
453
|
self.request.POST.get('f_path'))),
|
|
452
|
self.request.POST.get('f_path'))),
|
|
454
|
}
|
|
453
|
}
|
|
455
|
if comment:
|
|
454
|
if comment:
|
|
456
|
c.co = comment
|
|
455
|
c.co = comment
|
|
457
|
c.at_version_num = 0
|
|
456
|
c.at_version_num = 0
|
|
458
|
rendered_comment = render(
|
|
457
|
rendered_comment = render(
|
|
459
|
'rhodecode:templates/changeset/changeset_comment_block.mako',
|
|
458
|
'rhodecode:templates/changeset/changeset_comment_block.mako',
|
|
460
|
self._get_template_context(c), self.request)
|
|
459
|
self._get_template_context(c), self.request)
|
|
461
|
|
|
460
|
|
|
462
|
data.update(comment.get_dict())
|
|
461
|
data.update(comment.get_dict())
|
|
463
|
data.update({'rendered_text': rendered_comment})
|
|
462
|
data.update({'rendered_text': rendered_comment})
|
|
464
|
|
|
463
|
|
|
465
|
comment_broadcast_channel = channelstream.comment_channel(
|
|
464
|
comment_broadcast_channel = channelstream.comment_channel(
|
|
466
|
self.db_repo_name, commit_obj=commit)
|
|
465
|
self.db_repo_name, commit_obj=commit)
|
|
467
|
|
|
466
|
|
|
468
|
comment_data = data
|
|
467
|
comment_data = data
|
|
469
|
comment_type = 'inline' if is_inline else 'general'
|
|
468
|
comment_type = 'inline' if is_inline else 'general'
|
|
470
|
channelstream.comment_channelstream_push(
|
|
469
|
channelstream.comment_channelstream_push(
|
|
471
|
self.request, comment_broadcast_channel, self._rhodecode_user,
|
|
470
|
self.request, comment_broadcast_channel, self._rhodecode_user,
|
|
472
|
_('posted a new {} comment').format(comment_type),
|
|
471
|
_('posted a new {} comment').format(comment_type),
|
|
473
|
comment_data=comment_data)
|
|
472
|
comment_data=comment_data)
|
|
474
|
|
|
473
|
|
|
475
|
return data
|
|
474
|
return data
|
|
476
|
|
|
475
|
|
|
477
|
@LoginRequired()
|
|
476
|
@LoginRequired()
|
|
478
|
@NotAnonymous()
|
|
477
|
@NotAnonymous()
|
|
479
|
@HasRepoPermissionAnyDecorator(
|
|
478
|
@HasRepoPermissionAnyDecorator(
|
|
480
|
'repository.read', 'repository.write', 'repository.admin')
|
|
479
|
'repository.read', 'repository.write', 'repository.admin')
|
|
481
|
@CSRFRequired()
|
|
480
|
@CSRFRequired()
|
|
482
|
@view_config(
|
|
481
|
@view_config(
|
|
483
|
route_name='repo_commit_comment_preview', request_method='POST',
|
|
482
|
route_name='repo_commit_comment_preview', request_method='POST',
|
|
484
|
renderer='string', xhr=True)
|
|
483
|
renderer='string', xhr=True)
|
|
485
|
def repo_commit_comment_preview(self):
|
|
484
|
def repo_commit_comment_preview(self):
|
|
486
|
# Technically a CSRF token is not needed as no state changes with this
|
|
485
|
# Technically a CSRF token is not needed as no state changes with this
|
|
487
|
# call. However, as this is a POST is better to have it, so automated
|
|
486
|
# call. However, as this is a POST is better to have it, so automated
|
|
488
|
# tools don't flag it as potential CSRF.
|
|
487
|
# tools don't flag it as potential CSRF.
|
|
489
|
# Post is required because the payload could be bigger than the maximum
|
|
488
|
# Post is required because the payload could be bigger than the maximum
|
|
490
|
# allowed by GET.
|
|
489
|
# allowed by GET.
|
|
491
|
|
|
490
|
|
|
492
|
text = self.request.POST.get('text')
|
|
491
|
text = self.request.POST.get('text')
|
|
493
|
renderer = self.request.POST.get('renderer') or 'rst'
|
|
492
|
renderer = self.request.POST.get('renderer') or 'rst'
|
|
494
|
if text:
|
|
493
|
if text:
|
|
495
|
return h.render(text, renderer=renderer, mentions=True,
|
|
494
|
return h.render(text, renderer=renderer, mentions=True,
|
|
496
|
repo_name=self.db_repo_name)
|
|
495
|
repo_name=self.db_repo_name)
|
|
497
|
return ''
|
|
496
|
return ''
|
|
498
|
|
|
497
|
|
|
499
|
@LoginRequired()
|
|
498
|
@LoginRequired()
|
|
500
|
@HasRepoPermissionAnyDecorator(
|
|
499
|
@HasRepoPermissionAnyDecorator(
|
|
501
|
'repository.read', 'repository.write', 'repository.admin')
|
|
500
|
'repository.read', 'repository.write', 'repository.admin')
|
|
502
|
@CSRFRequired()
|
|
501
|
@CSRFRequired()
|
|
503
|
@view_config(
|
|
502
|
@view_config(
|
|
504
|
route_name='repo_commit_comment_history_view', request_method='POST',
|
|
503
|
route_name='repo_commit_comment_history_view', request_method='POST',
|
|
505
|
renderer='string', xhr=True)
|
|
504
|
renderer='string', xhr=True)
|
|
506
|
def repo_commit_comment_history_view(self):
|
|
505
|
def repo_commit_comment_history_view(self):
|
|
507
|
c = self.load_default_context()
|
|
506
|
c = self.load_default_context()
|
|
508
|
|
|
507
|
|
|
509
|
comment_history_id = self.request.matchdict['comment_history_id']
|
|
508
|
comment_history_id = self.request.matchdict['comment_history_id']
|
|
510
|
comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
|
|
509
|
comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
|
|
511
|
is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
|
|
510
|
is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
|
|
512
|
|
|
511
|
|
|
513
|
if is_repo_comment:
|
|
512
|
if is_repo_comment:
|
|
514
|
c.comment_history = comment_history
|
|
513
|
c.comment_history = comment_history
|
|
515
|
|
|
514
|
|
|
516
|
rendered_comment = render(
|
|
515
|
rendered_comment = render(
|
|
517
|
'rhodecode:templates/changeset/comment_history.mako',
|
|
516
|
'rhodecode:templates/changeset/comment_history.mako',
|
|
518
|
self._get_template_context(c)
|
|
517
|
self._get_template_context(c)
|
|
519
|
, self.request)
|
|
518
|
, self.request)
|
|
520
|
return rendered_comment
|
|
519
|
return rendered_comment
|
|
521
|
else:
|
|
520
|
else:
|
|
522
|
log.warning('No permissions for user %s to show comment_history_id: %s',
|
|
521
|
log.warning('No permissions for user %s to show comment_history_id: %s',
|
|
523
|
self._rhodecode_db_user, comment_history_id)
|
|
522
|
self._rhodecode_db_user, comment_history_id)
|
|
524
|
raise HTTPNotFound()
|
|
523
|
raise HTTPNotFound()
|
|
525
|
|
|
524
|
|
|
526
|
@LoginRequired()
|
|
525
|
@LoginRequired()
|
|
527
|
@NotAnonymous()
|
|
526
|
@NotAnonymous()
|
|
528
|
@HasRepoPermissionAnyDecorator(
|
|
527
|
@HasRepoPermissionAnyDecorator(
|
|
529
|
'repository.read', 'repository.write', 'repository.admin')
|
|
528
|
'repository.read', 'repository.write', 'repository.admin')
|
|
530
|
@CSRFRequired()
|
|
529
|
@CSRFRequired()
|
|
531
|
@view_config(
|
|
530
|
@view_config(
|
|
532
|
route_name='repo_commit_comment_attachment_upload', request_method='POST',
|
|
531
|
route_name='repo_commit_comment_attachment_upload', request_method='POST',
|
|
533
|
renderer='json_ext', xhr=True)
|
|
532
|
renderer='json_ext', xhr=True)
|
|
534
|
def repo_commit_comment_attachment_upload(self):
|
|
533
|
def repo_commit_comment_attachment_upload(self):
|
|
535
|
c = self.load_default_context()
|
|
534
|
c = self.load_default_context()
|
|
536
|
upload_key = 'attachment'
|
|
535
|
upload_key = 'attachment'
|
|
537
|
|
|
536
|
|
|
538
|
file_obj = self.request.POST.get(upload_key)
|
|
537
|
file_obj = self.request.POST.get(upload_key)
|
|
539
|
|
|
538
|
|
|
540
|
if file_obj is None:
|
|
539
|
if file_obj is None:
|
|
541
|
self.request.response.status = 400
|
|
540
|
self.request.response.status = 400
|
|
542
|
return {'store_fid': None,
|
|
541
|
return {'store_fid': None,
|
|
543
|
'access_path': None,
|
|
542
|
'access_path': None,
|
|
544
|
'error': '{} data field is missing'.format(upload_key)}
|
|
543
|
'error': '{} data field is missing'.format(upload_key)}
|
|
545
|
|
|
544
|
|
|
546
|
if not hasattr(file_obj, 'filename'):
|
|
545
|
if not hasattr(file_obj, 'filename'):
|
|
547
|
self.request.response.status = 400
|
|
546
|
self.request.response.status = 400
|
|
548
|
return {'store_fid': None,
|
|
547
|
return {'store_fid': None,
|
|
549
|
'access_path': None,
|
|
548
|
'access_path': None,
|
|
550
|
'error': 'filename cannot be read from the data field'}
|
|
549
|
'error': 'filename cannot be read from the data field'}
|
|
551
|
|
|
550
|
|
|
552
|
filename = file_obj.filename
|
|
551
|
filename = file_obj.filename
|
|
553
|
file_display_name = filename
|
|
552
|
file_display_name = filename
|
|
554
|
|
|
553
|
|
|
555
|
metadata = {
|
|
554
|
metadata = {
|
|
556
|
'user_uploaded': {'username': self._rhodecode_user.username,
|
|
555
|
'user_uploaded': {'username': self._rhodecode_user.username,
|
|
557
|
'user_id': self._rhodecode_user.user_id,
|
|
556
|
'user_id': self._rhodecode_user.user_id,
|
|
558
|
'ip': self._rhodecode_user.ip_addr}}
|
|
557
|
'ip': self._rhodecode_user.ip_addr}}
|
|
559
|
|
|
558
|
|
|
560
|
# TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
|
|
559
|
# TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
|
|
561
|
allowed_extensions = [
|
|
560
|
allowed_extensions = [
|
|
562
|
'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
|
|
561
|
'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
|
|
563
|
'.pptx', '.txt', '.xlsx', '.zip']
|
|
562
|
'.pptx', '.txt', '.xlsx', '.zip']
|
|
564
|
max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
|
|
563
|
max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
|
|
565
|
|
|
564
|
|
|
566
|
try:
|
|
565
|
try:
|
|
567
|
storage = store_utils.get_file_storage(self.request.registry.settings)
|
|
566
|
storage = store_utils.get_file_storage(self.request.registry.settings)
|
|
568
|
store_uid, metadata = storage.save_file(
|
|
567
|
store_uid, metadata = storage.save_file(
|
|
569
|
file_obj.file, filename, extra_metadata=metadata,
|
|
568
|
file_obj.file, filename, extra_metadata=metadata,
|
|
570
|
extensions=allowed_extensions, max_filesize=max_file_size)
|
|
569
|
extensions=allowed_extensions, max_filesize=max_file_size)
|
|
571
|
except FileNotAllowedException:
|
|
570
|
except FileNotAllowedException:
|
|
572
|
self.request.response.status = 400
|
|
571
|
self.request.response.status = 400
|
|
573
|
permitted_extensions = ', '.join(allowed_extensions)
|
|
572
|
permitted_extensions = ', '.join(allowed_extensions)
|
|
574
|
error_msg = 'File `{}` is not allowed. ' \
|
|
573
|
error_msg = 'File `{}` is not allowed. ' \
|
|
575
|
'Only following extensions are permitted: {}'.format(
|
|
574
|
'Only following extensions are permitted: {}'.format(
|
|
576
|
filename, permitted_extensions)
|
|
575
|
filename, permitted_extensions)
|
|
577
|
return {'store_fid': None,
|
|
576
|
return {'store_fid': None,
|
|
578
|
'access_path': None,
|
|
577
|
'access_path': None,
|
|
579
|
'error': error_msg}
|
|
578
|
'error': error_msg}
|
|
580
|
except FileOverSizeException:
|
|
579
|
except FileOverSizeException:
|
|
581
|
self.request.response.status = 400
|
|
580
|
self.request.response.status = 400
|
|
582
|
limit_mb = h.format_byte_size_binary(max_file_size)
|
|
581
|