##// END OF EJS Templates
report ChangesetDoesNotExistError as an error but don't lose the repo context
Mads Kiilerich -
r3573:881ae12b beta
parent child Browse files
Show More
@@ -1,125 +1,125 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changelog controller for rhodecode
6 changelog controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import request, url, session, tmpl_context as c
29 from pylons import request, url, session, tmpl_context as c
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 import rhodecode.lib.helpers as h
33 import rhodecode.lib.helpers as h
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.graphmod import _colored, _dagwalker
38 from rhodecode.lib.graphmod import _colored, _dagwalker
39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
40 from rhodecode.lib.utils2 import safe_int
40 from rhodecode.lib.utils2 import safe_int
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class ChangelogController(BaseRepoController):
45 class ChangelogController(BaseRepoController):
46
46
47 @LoginRequired()
47 @LoginRequired()
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 'repository.admin')
49 'repository.admin')
50 def __before__(self):
50 def __before__(self):
51 super(ChangelogController, self).__before__()
51 super(ChangelogController, self).__before__()
52 c.affected_files_cut_off = 60
52 c.affected_files_cut_off = 60
53
53
54 def index(self):
54 def index(self):
55 limit = 100
55 limit = 100
56 default = 20
56 default = 20
57 if request.params.get('size'):
57 if request.params.get('size'):
58 try:
58 try:
59 int_size = int(request.params.get('size'))
59 int_size = int(request.params.get('size'))
60 except ValueError:
60 except ValueError:
61 int_size = default
61 int_size = default
62 c.size = max(min(int_size, limit), 1)
62 c.size = max(min(int_size, limit), 1)
63 session['changelog_size'] = c.size
63 session['changelog_size'] = c.size
64 session.save()
64 session.save()
65 else:
65 else:
66 c.size = int(session.get('changelog_size', default))
66 c.size = int(session.get('changelog_size', default))
67 # min size must be 1
67 # min size must be 1
68 c.size = max(c.size, 1)
68 c.size = max(c.size, 1)
69 p = safe_int(request.params.get('page', 1), 1)
69 p = safe_int(request.params.get('page', 1), 1)
70 branch_name = request.params.get('branch', None)
70 branch_name = request.params.get('branch', None)
71 try:
71 try:
72 if branch_name:
72 if branch_name:
73 collection = [z for z in
73 collection = [z for z in
74 c.rhodecode_repo.get_changesets(start=0,
74 c.rhodecode_repo.get_changesets(start=0,
75 branch_name=branch_name)]
75 branch_name=branch_name)]
76 c.total_cs = len(collection)
76 c.total_cs = len(collection)
77 else:
77 else:
78 collection = c.rhodecode_repo
78 collection = c.rhodecode_repo
79 c.total_cs = len(c.rhodecode_repo)
79 c.total_cs = len(c.rhodecode_repo)
80
80
81 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
81 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
82 items_per_page=c.size, branch=branch_name)
82 items_per_page=c.size, branch=branch_name)
83 collection = list(c.pagination)
83 collection = list(c.pagination)
84 page_revisions = [x.raw_id for x in collection]
84 page_revisions = [x.raw_id for x in collection]
85 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
85 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
86 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
86 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
87 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
87 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
88 log.error(traceback.format_exc())
88 log.error(traceback.format_exc())
89 h.flash(str(e), category='warning')
89 h.flash(str(e), category='error')
90 return redirect(url('home'))
90 return redirect(url('changelog_home', repo_name=c.repo_name))
91
91
92 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
92 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
93
93
94 c.branch_name = branch_name
94 c.branch_name = branch_name
95 c.branch_filters = [('', _('All Branches'))] + \
95 c.branch_filters = [('', _('All Branches'))] + \
96 [(k, k) for k in c.rhodecode_repo.branches.keys()]
96 [(k, k) for k in c.rhodecode_repo.branches.keys()]
97
97
98 return render('changelog/changelog.html')
98 return render('changelog/changelog.html')
99
99
100 def changelog_details(self, cs):
100 def changelog_details(self, cs):
101 if request.environ.get('HTTP_X_PARTIAL_XHR'):
101 if request.environ.get('HTTP_X_PARTIAL_XHR'):
102 c.cs = c.rhodecode_repo.get_changeset(cs)
102 c.cs = c.rhodecode_repo.get_changeset(cs)
103 return render('changelog/changelog_details.html')
103 return render('changelog/changelog_details.html')
104
104
105 def _graph(self, repo, collection, repo_size, size, p):
105 def _graph(self, repo, collection, repo_size, size, p):
106 """
106 """
107 Generates a DAG graph for mercurial
107 Generates a DAG graph for mercurial
108
108
109 :param repo: repo instance
109 :param repo: repo instance
110 :param size: number of commits to show
110 :param size: number of commits to show
111 :param p: page number
111 :param p: page number
112 """
112 """
113 if not collection:
113 if not collection:
114 c.jsdata = json.dumps([])
114 c.jsdata = json.dumps([])
115 return
115 return
116
116
117 data = []
117 data = []
118 revs = [x.revision for x in collection]
118 revs = [x.revision for x in collection]
119
119
120 dag = _dagwalker(repo, revs, repo.alias)
120 dag = _dagwalker(repo, revs, repo.alias)
121 dag = _colored(dag)
121 dag = _colored(dag)
122 for (id, type, ctx, vtx, edges) in dag:
122 for (id, type, ctx, vtx, edges) in dag:
123 data.append(['', vtx, edges])
123 data.append(['', vtx, edges])
124
124
125 c.jsdata = json.dumps(data)
125 c.jsdata = json.dumps(data)
@@ -1,404 +1,404 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden, HTTPBadRequest
29 from webob.exc import HTTPForbidden, HTTPBadRequest
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from rhodecode.lib.utils import jsonify
34 from rhodecode.lib.utils import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38
38
39 import rhodecode.lib.helpers as h
39 import rhodecode.lib.helpers as h
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.utils import action_logger
42 from rhodecode.lib.utils import action_logger
43 from rhodecode.lib.compat import OrderedDict
43 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib import diffs
44 from rhodecode.lib import diffs
45 from rhodecode.model.db import ChangesetComment, ChangesetStatus
45 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 from rhodecode.model.comment import ChangesetCommentsModel
46 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.lib.diffs import LimitedDiffContainer
50 from rhodecode.lib.diffs import LimitedDiffContainer
51 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
51 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
52 from rhodecode.lib.vcs.backends.base import EmptyChangeset
52 from rhodecode.lib.vcs.backends.base import EmptyChangeset
53 from rhodecode.lib.utils2 import safe_unicode
53 from rhodecode.lib.utils2 import safe_unicode
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 def _update_with_GET(params, GET):
58 def _update_with_GET(params, GET):
59 for k in ['diff1', 'diff2', 'diff']:
59 for k in ['diff1', 'diff2', 'diff']:
60 params[k] += GET.getall(k)
60 params[k] += GET.getall(k)
61
61
62
62
63 def anchor_url(revision, path, GET):
63 def anchor_url(revision, path, GET):
64 fid = h.FID(revision, path)
64 fid = h.FID(revision, path)
65 return h.url.current(anchor=fid, **dict(GET))
65 return h.url.current(anchor=fid, **dict(GET))
66
66
67
67
68 def get_ignore_ws(fid, GET):
68 def get_ignore_ws(fid, GET):
69 ig_ws_global = GET.get('ignorews')
69 ig_ws_global = GET.get('ignorews')
70 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
70 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
71 if ig_ws:
71 if ig_ws:
72 try:
72 try:
73 return int(ig_ws[0].split(':')[-1])
73 return int(ig_ws[0].split(':')[-1])
74 except:
74 except:
75 pass
75 pass
76 return ig_ws_global
76 return ig_ws_global
77
77
78
78
79 def _ignorews_url(GET, fileid=None):
79 def _ignorews_url(GET, fileid=None):
80 fileid = str(fileid) if fileid else None
80 fileid = str(fileid) if fileid else None
81 params = defaultdict(list)
81 params = defaultdict(list)
82 _update_with_GET(params, GET)
82 _update_with_GET(params, GET)
83 lbl = _('show white space')
83 lbl = _('show white space')
84 ig_ws = get_ignore_ws(fileid, GET)
84 ig_ws = get_ignore_ws(fileid, GET)
85 ln_ctx = get_line_ctx(fileid, GET)
85 ln_ctx = get_line_ctx(fileid, GET)
86 # global option
86 # global option
87 if fileid is None:
87 if fileid is None:
88 if ig_ws is None:
88 if ig_ws is None:
89 params['ignorews'] += [1]
89 params['ignorews'] += [1]
90 lbl = _('ignore white space')
90 lbl = _('ignore white space')
91 ctx_key = 'context'
91 ctx_key = 'context'
92 ctx_val = ln_ctx
92 ctx_val = ln_ctx
93 # per file options
93 # per file options
94 else:
94 else:
95 if ig_ws is None:
95 if ig_ws is None:
96 params[fileid] += ['WS:1']
96 params[fileid] += ['WS:1']
97 lbl = _('ignore white space')
97 lbl = _('ignore white space')
98
98
99 ctx_key = fileid
99 ctx_key = fileid
100 ctx_val = 'C:%s' % ln_ctx
100 ctx_val = 'C:%s' % ln_ctx
101 # if we have passed in ln_ctx pass it along to our params
101 # if we have passed in ln_ctx pass it along to our params
102 if ln_ctx:
102 if ln_ctx:
103 params[ctx_key] += [ctx_val]
103 params[ctx_key] += [ctx_val]
104
104
105 params['anchor'] = fileid
105 params['anchor'] = fileid
106 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
106 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
107 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
107 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
108
108
109
109
110 def get_line_ctx(fid, GET):
110 def get_line_ctx(fid, GET):
111 ln_ctx_global = GET.get('context')
111 ln_ctx_global = GET.get('context')
112 if fid:
112 if fid:
113 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
113 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
114 else:
114 else:
115 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
115 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
116 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
116 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
117 if ln_ctx:
117 if ln_ctx:
118 ln_ctx = [ln_ctx]
118 ln_ctx = [ln_ctx]
119
119
120 if ln_ctx:
120 if ln_ctx:
121 retval = ln_ctx[0].split(':')[-1]
121 retval = ln_ctx[0].split(':')[-1]
122 else:
122 else:
123 retval = ln_ctx_global
123 retval = ln_ctx_global
124
124
125 try:
125 try:
126 return int(retval)
126 return int(retval)
127 except:
127 except:
128 return 3
128 return 3
129
129
130
130
131 def _context_url(GET, fileid=None):
131 def _context_url(GET, fileid=None):
132 """
132 """
133 Generates url for context lines
133 Generates url for context lines
134
134
135 :param fileid:
135 :param fileid:
136 """
136 """
137
137
138 fileid = str(fileid) if fileid else None
138 fileid = str(fileid) if fileid else None
139 ig_ws = get_ignore_ws(fileid, GET)
139 ig_ws = get_ignore_ws(fileid, GET)
140 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
140 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
141
141
142 params = defaultdict(list)
142 params = defaultdict(list)
143 _update_with_GET(params, GET)
143 _update_with_GET(params, GET)
144
144
145 # global option
145 # global option
146 if fileid is None:
146 if fileid is None:
147 if ln_ctx > 0:
147 if ln_ctx > 0:
148 params['context'] += [ln_ctx]
148 params['context'] += [ln_ctx]
149
149
150 if ig_ws:
150 if ig_ws:
151 ig_ws_key = 'ignorews'
151 ig_ws_key = 'ignorews'
152 ig_ws_val = 1
152 ig_ws_val = 1
153
153
154 # per file option
154 # per file option
155 else:
155 else:
156 params[fileid] += ['C:%s' % ln_ctx]
156 params[fileid] += ['C:%s' % ln_ctx]
157 ig_ws_key = fileid
157 ig_ws_key = fileid
158 ig_ws_val = 'WS:%s' % 1
158 ig_ws_val = 'WS:%s' % 1
159
159
160 if ig_ws:
160 if ig_ws:
161 params[ig_ws_key] += [ig_ws_val]
161 params[ig_ws_key] += [ig_ws_val]
162
162
163 lbl = _('%s line context') % ln_ctx
163 lbl = _('%s line context') % ln_ctx
164
164
165 params['anchor'] = fileid
165 params['anchor'] = fileid
166 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
166 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
167 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
167 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
168
168
169
169
170 class ChangesetController(BaseRepoController):
170 class ChangesetController(BaseRepoController):
171
171
172 @LoginRequired()
172 @LoginRequired()
173 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
173 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
174 'repository.admin')
174 'repository.admin')
175 def __before__(self):
175 def __before__(self):
176 super(ChangesetController, self).__before__()
176 super(ChangesetController, self).__before__()
177 c.affected_files_cut_off = 60
177 c.affected_files_cut_off = 60
178 repo_model = RepoModel()
178 repo_model = RepoModel()
179 c.users_array = repo_model.get_users_js()
179 c.users_array = repo_model.get_users_js()
180 c.users_groups_array = repo_model.get_users_groups_js()
180 c.users_groups_array = repo_model.get_users_groups_js()
181
181
182 def index(self, revision, method='show'):
182 def index(self, revision, method='show'):
183 c.anchor_url = anchor_url
183 c.anchor_url = anchor_url
184 c.ignorews_url = _ignorews_url
184 c.ignorews_url = _ignorews_url
185 c.context_url = _context_url
185 c.context_url = _context_url
186 c.fulldiff = fulldiff = request.GET.get('fulldiff')
186 c.fulldiff = fulldiff = request.GET.get('fulldiff')
187 #get ranges of revisions if preset
187 #get ranges of revisions if preset
188 rev_range = revision.split('...')[:2]
188 rev_range = revision.split('...')[:2]
189 enable_comments = True
189 enable_comments = True
190 try:
190 try:
191 if len(rev_range) == 2:
191 if len(rev_range) == 2:
192 enable_comments = False
192 enable_comments = False
193 rev_start = rev_range[0]
193 rev_start = rev_range[0]
194 rev_end = rev_range[1]
194 rev_end = rev_range[1]
195 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
195 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
196 end=rev_end)
196 end=rev_end)
197 else:
197 else:
198 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
198 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
199
199
200 c.cs_ranges = list(rev_ranges)
200 c.cs_ranges = list(rev_ranges)
201 if not c.cs_ranges:
201 if not c.cs_ranges:
202 raise RepositoryError('Changeset range returned empty result')
202 raise RepositoryError('Changeset range returned empty result')
203
203
204 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
204 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
205 log.error(traceback.format_exc())
205 log.error(traceback.format_exc())
206 h.flash(str(e), category='warning')
206 h.flash(str(e), category='error')
207 return redirect(url('home'))
207 return redirect(url('changeset_home', repo_name=c.repo_name))
208
208
209 c.changes = OrderedDict()
209 c.changes = OrderedDict()
210
210
211 c.lines_added = 0 # count of lines added
211 c.lines_added = 0 # count of lines added
212 c.lines_deleted = 0 # count of lines removes
212 c.lines_deleted = 0 # count of lines removes
213
213
214 c.changeset_statuses = ChangesetStatus.STATUSES
214 c.changeset_statuses = ChangesetStatus.STATUSES
215 c.comments = []
215 c.comments = []
216 c.statuses = []
216 c.statuses = []
217 c.inline_comments = []
217 c.inline_comments = []
218 c.inline_cnt = 0
218 c.inline_cnt = 0
219
219
220 # Iterate over ranges (default changeset view is always one changeset)
220 # Iterate over ranges (default changeset view is always one changeset)
221 for changeset in c.cs_ranges:
221 for changeset in c.cs_ranges:
222 inlines = []
222 inlines = []
223 if method == 'show':
223 if method == 'show':
224 c.statuses.extend([ChangesetStatusModel().get_status(
224 c.statuses.extend([ChangesetStatusModel().get_status(
225 c.rhodecode_db_repo.repo_id, changeset.raw_id)])
225 c.rhodecode_db_repo.repo_id, changeset.raw_id)])
226
226
227 c.comments.extend(ChangesetCommentsModel()\
227 c.comments.extend(ChangesetCommentsModel()\
228 .get_comments(c.rhodecode_db_repo.repo_id,
228 .get_comments(c.rhodecode_db_repo.repo_id,
229 revision=changeset.raw_id))
229 revision=changeset.raw_id))
230
230
231 #comments from PR
231 #comments from PR
232 st = ChangesetStatusModel().get_statuses(
232 st = ChangesetStatusModel().get_statuses(
233 c.rhodecode_db_repo.repo_id, changeset.raw_id,
233 c.rhodecode_db_repo.repo_id, changeset.raw_id,
234 with_revisions=True)
234 with_revisions=True)
235 # from associated statuses, check the pull requests, and
235 # from associated statuses, check the pull requests, and
236 # show comments from them
236 # show comments from them
237
237
238 prs = set([x.pull_request for x in
238 prs = set([x.pull_request for x in
239 filter(lambda x: x.pull_request != None, st)])
239 filter(lambda x: x.pull_request != None, st)])
240
240
241 for pr in prs:
241 for pr in prs:
242 c.comments.extend(pr.comments)
242 c.comments.extend(pr.comments)
243 inlines = ChangesetCommentsModel()\
243 inlines = ChangesetCommentsModel()\
244 .get_inline_comments(c.rhodecode_db_repo.repo_id,
244 .get_inline_comments(c.rhodecode_db_repo.repo_id,
245 revision=changeset.raw_id)
245 revision=changeset.raw_id)
246 c.inline_comments.extend(inlines)
246 c.inline_comments.extend(inlines)
247
247
248 c.changes[changeset.raw_id] = []
248 c.changes[changeset.raw_id] = []
249
249
250 cs2 = changeset.raw_id
250 cs2 = changeset.raw_id
251 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
251 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
252 context_lcl = get_line_ctx('', request.GET)
252 context_lcl = get_line_ctx('', request.GET)
253 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
253 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
254
254
255 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
255 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
256 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
256 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
257 diff_limit = self.cut_off_limit if not fulldiff else None
257 diff_limit = self.cut_off_limit if not fulldiff else None
258 diff_processor = diffs.DiffProcessor(_diff,
258 diff_processor = diffs.DiffProcessor(_diff,
259 vcs=c.rhodecode_repo.alias,
259 vcs=c.rhodecode_repo.alias,
260 format='gitdiff',
260 format='gitdiff',
261 diff_limit=diff_limit)
261 diff_limit=diff_limit)
262 cs_changes = OrderedDict()
262 cs_changes = OrderedDict()
263 if method == 'show':
263 if method == 'show':
264 _parsed = diff_processor.prepare()
264 _parsed = diff_processor.prepare()
265 c.limited_diff = False
265 c.limited_diff = False
266 if isinstance(_parsed, LimitedDiffContainer):
266 if isinstance(_parsed, LimitedDiffContainer):
267 c.limited_diff = True
267 c.limited_diff = True
268 for f in _parsed:
268 for f in _parsed:
269 st = f['stats']
269 st = f['stats']
270 if st[0] != 'b':
270 if st[0] != 'b':
271 c.lines_added += st[0]
271 c.lines_added += st[0]
272 c.lines_deleted += st[1]
272 c.lines_deleted += st[1]
273 fid = h.FID(changeset.raw_id, f['filename'])
273 fid = h.FID(changeset.raw_id, f['filename'])
274 diff = diff_processor.as_html(enable_comments=enable_comments,
274 diff = diff_processor.as_html(enable_comments=enable_comments,
275 parsed_lines=[f])
275 parsed_lines=[f])
276 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
276 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
277 diff, st]
277 diff, st]
278 else:
278 else:
279 # downloads/raw we only need RAW diff nothing else
279 # downloads/raw we only need RAW diff nothing else
280 diff = diff_processor.as_raw()
280 diff = diff_processor.as_raw()
281 cs_changes[''] = [None, None, None, None, diff, None]
281 cs_changes[''] = [None, None, None, None, diff, None]
282 c.changes[changeset.raw_id] = cs_changes
282 c.changes[changeset.raw_id] = cs_changes
283
283
284 #sort comments by how they were generated
284 #sort comments by how they were generated
285 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
285 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
286
286
287 # count inline comments
287 # count inline comments
288 for __, lines in c.inline_comments:
288 for __, lines in c.inline_comments:
289 for comments in lines.values():
289 for comments in lines.values():
290 c.inline_cnt += len(comments)
290 c.inline_cnt += len(comments)
291
291
292 if len(c.cs_ranges) == 1:
292 if len(c.cs_ranges) == 1:
293 c.changeset = c.cs_ranges[0]
293 c.changeset = c.cs_ranges[0]
294 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
294 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
295 for x in c.changeset.parents])
295 for x in c.changeset.parents])
296 if method == 'download':
296 if method == 'download':
297 response.content_type = 'text/plain'
297 response.content_type = 'text/plain'
298 response.content_disposition = 'attachment; filename=%s.diff' \
298 response.content_disposition = 'attachment; filename=%s.diff' \
299 % revision[:12]
299 % revision[:12]
300 return diff
300 return diff
301 elif method == 'patch':
301 elif method == 'patch':
302 response.content_type = 'text/plain'
302 response.content_type = 'text/plain'
303 c.diff = safe_unicode(diff)
303 c.diff = safe_unicode(diff)
304 return render('changeset/patch_changeset.html')
304 return render('changeset/patch_changeset.html')
305 elif method == 'raw':
305 elif method == 'raw':
306 response.content_type = 'text/plain'
306 response.content_type = 'text/plain'
307 return diff
307 return diff
308 elif method == 'show':
308 elif method == 'show':
309 if len(c.cs_ranges) == 1:
309 if len(c.cs_ranges) == 1:
310 return render('changeset/changeset.html')
310 return render('changeset/changeset.html')
311 else:
311 else:
312 return render('changeset/changeset_range.html')
312 return render('changeset/changeset_range.html')
313
313
314 def changeset_raw(self, revision):
314 def changeset_raw(self, revision):
315 return self.index(revision, method='raw')
315 return self.index(revision, method='raw')
316
316
317 def changeset_patch(self, revision):
317 def changeset_patch(self, revision):
318 return self.index(revision, method='patch')
318 return self.index(revision, method='patch')
319
319
320 def changeset_download(self, revision):
320 def changeset_download(self, revision):
321 return self.index(revision, method='download')
321 return self.index(revision, method='download')
322
322
323 @jsonify
323 @jsonify
324 def comment(self, repo_name, revision):
324 def comment(self, repo_name, revision):
325 status = request.POST.get('changeset_status')
325 status = request.POST.get('changeset_status')
326 change_status = request.POST.get('change_changeset_status')
326 change_status = request.POST.get('change_changeset_status')
327 text = request.POST.get('text')
327 text = request.POST.get('text')
328 if status and change_status:
328 if status and change_status:
329 text = text or (_('Status change -> %s')
329 text = text or (_('Status change -> %s')
330 % ChangesetStatus.get_status_lbl(status))
330 % ChangesetStatus.get_status_lbl(status))
331
331
332 c.co = comm = ChangesetCommentsModel().create(
332 c.co = comm = ChangesetCommentsModel().create(
333 text=text,
333 text=text,
334 repo=c.rhodecode_db_repo.repo_id,
334 repo=c.rhodecode_db_repo.repo_id,
335 user=c.rhodecode_user.user_id,
335 user=c.rhodecode_user.user_id,
336 revision=revision,
336 revision=revision,
337 f_path=request.POST.get('f_path'),
337 f_path=request.POST.get('f_path'),
338 line_no=request.POST.get('line'),
338 line_no=request.POST.get('line'),
339 status_change=(ChangesetStatus.get_status_lbl(status)
339 status_change=(ChangesetStatus.get_status_lbl(status)
340 if status and change_status else None)
340 if status and change_status else None)
341 )
341 )
342
342
343 # get status if set !
343 # get status if set !
344 if status and change_status:
344 if status and change_status:
345 # if latest status was from pull request and it's closed
345 # if latest status was from pull request and it's closed
346 # disallow changing status !
346 # disallow changing status !
347 # dont_allow_on_closed_pull_request = True !
347 # dont_allow_on_closed_pull_request = True !
348
348
349 try:
349 try:
350 ChangesetStatusModel().set_status(
350 ChangesetStatusModel().set_status(
351 c.rhodecode_db_repo.repo_id,
351 c.rhodecode_db_repo.repo_id,
352 status,
352 status,
353 c.rhodecode_user.user_id,
353 c.rhodecode_user.user_id,
354 comm,
354 comm,
355 revision=revision,
355 revision=revision,
356 dont_allow_on_closed_pull_request=True
356 dont_allow_on_closed_pull_request=True
357 )
357 )
358 except StatusChangeOnClosedPullRequestError:
358 except StatusChangeOnClosedPullRequestError:
359 log.error(traceback.format_exc())
359 log.error(traceback.format_exc())
360 msg = _('Changing status on a changeset associated with '
360 msg = _('Changing status on a changeset associated with '
361 'a closed pull request is not allowed')
361 'a closed pull request is not allowed')
362 h.flash(msg, category='warning')
362 h.flash(msg, category='warning')
363 return redirect(h.url('changeset_home', repo_name=repo_name,
363 return redirect(h.url('changeset_home', repo_name=repo_name,
364 revision=revision))
364 revision=revision))
365 action_logger(self.rhodecode_user,
365 action_logger(self.rhodecode_user,
366 'user_commented_revision:%s' % revision,
366 'user_commented_revision:%s' % revision,
367 c.rhodecode_db_repo, self.ip_addr, self.sa)
367 c.rhodecode_db_repo, self.ip_addr, self.sa)
368
368
369 Session().commit()
369 Session().commit()
370
370
371 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
371 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
372 return redirect(h.url('changeset_home', repo_name=repo_name,
372 return redirect(h.url('changeset_home', repo_name=repo_name,
373 revision=revision))
373 revision=revision))
374 #only ajax below
374 #only ajax below
375 data = {
375 data = {
376 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
376 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
377 }
377 }
378 if comm:
378 if comm:
379 data.update(comm.get_dict())
379 data.update(comm.get_dict())
380 data.update({'rendered_text':
380 data.update({'rendered_text':
381 render('changeset/changeset_comment_block.html')})
381 render('changeset/changeset_comment_block.html')})
382
382
383 return data
383 return data
384
384
385 @jsonify
385 @jsonify
386 def delete_comment(self, repo_name, comment_id):
386 def delete_comment(self, repo_name, comment_id):
387 co = ChangesetComment.get(comment_id)
387 co = ChangesetComment.get(comment_id)
388 owner = co.author.user_id == c.rhodecode_user.user_id
388 owner = co.author.user_id == c.rhodecode_user.user_id
389 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
389 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
390 ChangesetCommentsModel().delete(comment=co)
390 ChangesetCommentsModel().delete(comment=co)
391 Session().commit()
391 Session().commit()
392 return True
392 return True
393 else:
393 else:
394 raise HTTPForbidden()
394 raise HTTPForbidden()
395
395
396 @jsonify
396 @jsonify
397 def changeset_info(self, repo_name, revision):
397 def changeset_info(self, repo_name, revision):
398 if request.is_xhr:
398 if request.is_xhr:
399 try:
399 try:
400 return c.rhodecode_repo.get_changeset(revision)
400 return c.rhodecode_repo.get_changeset(revision)
401 except ChangesetDoesNotExistError, e:
401 except ChangesetDoesNotExistError, e:
402 return EmptyChangeset(message=str(e))
402 return EmptyChangeset(message=str(e))
403 else:
403 else:
404 raise HTTPBadRequest()
404 raise HTTPBadRequest()
@@ -1,650 +1,650 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import tempfile
29 import tempfile
30 import shutil
30 import shutil
31
31
32 from pylons import request, response, tmpl_context as c, url
32 from pylons import request, response, tmpl_context as c, url
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from rhodecode.lib.utils import jsonify
35 from rhodecode.lib.utils import jsonify
36
36
37 from rhodecode.lib import diffs
37 from rhodecode.lib import diffs
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39
39
40 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.compat import OrderedDict
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
42 str2bool
42 str2bool
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
50 NodeDoesNotExistError, ChangesetError, NodeError
50 NodeDoesNotExistError, ChangesetError, NodeError
51 from rhodecode.lib.vcs.nodes import FileNode
51 from rhodecode.lib.vcs.nodes import FileNode
52
52
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
55 from rhodecode.model.db import Repository
56
56
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
58 _context_url, get_line_ctx, get_ignore_ws
58 _context_url, get_line_ctx, get_ignore_ws
59
59
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 class FilesController(BaseRepoController):
64 class FilesController(BaseRepoController):
65
65
66 def __before__(self):
66 def __before__(self):
67 super(FilesController, self).__before__()
67 super(FilesController, self).__before__()
68 c.cut_off_limit = self.cut_off_limit
68 c.cut_off_limit = self.cut_off_limit
69
69
70 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
70 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
71 """
71 """
72 Safe way to get changeset if error occur it redirects to tip with
72 Safe way to get changeset if error occur it redirects to tip with
73 proper message
73 proper message
74
74
75 :param rev: revision to fetch
75 :param rev: revision to fetch
76 :param repo_name: repo name to redirect after
76 :param repo_name: repo name to redirect after
77 """
77 """
78
78
79 try:
79 try:
80 return c.rhodecode_repo.get_changeset(rev)
80 return c.rhodecode_repo.get_changeset(rev)
81 except EmptyRepositoryError, e:
81 except EmptyRepositoryError, e:
82 if not redirect_after:
82 if not redirect_after:
83 return None
83 return None
84 url_ = url('files_add_home',
84 url_ = url('files_add_home',
85 repo_name=c.repo_name,
85 repo_name=c.repo_name,
86 revision=0, f_path='')
86 revision=0, f_path='')
87 add_new = h.link_to(_('click here to add new file'), url_)
87 add_new = h.link_to(_('click here to add new file'), url_)
88 h.flash(h.literal(_('There are no files yet %s') % add_new),
88 h.flash(h.literal(_('There are no files yet %s') % add_new),
89 category='warning')
89 category='warning')
90 redirect(h.url('summary_home', repo_name=repo_name))
90 redirect(h.url('summary_home', repo_name=repo_name))
91
91
92 except RepositoryError, e:
92 except RepositoryError, e: # including ChangesetDoesNotExistError
93 h.flash(str(e), category='warning')
93 h.flash(str(e), category='error')
94 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
94 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
95
95
96 def __get_filenode_or_redirect(self, repo_name, cs, path):
96 def __get_filenode_or_redirect(self, repo_name, cs, path):
97 """
97 """
98 Returns file_node, if error occurs or given path is directory,
98 Returns file_node, if error occurs or given path is directory,
99 it'll redirect to top level path
99 it'll redirect to top level path
100
100
101 :param repo_name: repo_name
101 :param repo_name: repo_name
102 :param cs: given changeset
102 :param cs: given changeset
103 :param path: path to lookup
103 :param path: path to lookup
104 """
104 """
105
105
106 try:
106 try:
107 file_node = cs.get_node(path)
107 file_node = cs.get_node(path)
108 if file_node.is_dir():
108 if file_node.is_dir():
109 raise RepositoryError('given path is a directory')
109 raise RepositoryError('given path is a directory')
110 except RepositoryError, e:
110 except RepositoryError, e:
111 h.flash(str(e), category='warning')
111 h.flash(str(e), category='error')
112 redirect(h.url('files_home', repo_name=repo_name,
112 redirect(h.url('files_home', repo_name=repo_name,
113 revision=cs.raw_id))
113 revision=cs.raw_id))
114
114
115 return file_node
115 return file_node
116
116
117 @LoginRequired()
117 @LoginRequired()
118 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
119 'repository.admin')
119 'repository.admin')
120 def index(self, repo_name, revision, f_path, annotate=False):
120 def index(self, repo_name, revision, f_path, annotate=False):
121 # redirect to given revision from form if given
121 # redirect to given revision from form if given
122 post_revision = request.POST.get('at_rev', None)
122 post_revision = request.POST.get('at_rev', None)
123 if post_revision:
123 if post_revision:
124 cs = self.__get_cs_or_redirect(post_revision, repo_name)
124 cs = self.__get_cs_or_redirect(post_revision, repo_name)
125 redirect(url('files_home', repo_name=c.repo_name,
125 redirect(url('files_home', repo_name=c.repo_name,
126 revision=cs.raw_id, f_path=f_path))
126 revision=cs.raw_id, f_path=f_path))
127
127
128 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
128 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
129 c.branch = request.GET.get('branch', None)
129 c.branch = request.GET.get('branch', None)
130 c.f_path = f_path
130 c.f_path = f_path
131 c.annotate = annotate
131 c.annotate = annotate
132 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
132 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
133 cur_rev = c.changeset.revision
133 cur_rev = c.changeset.revision
134
134
135 # prev link
135 # prev link
136 try:
136 try:
137 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
137 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
138 c.url_prev = url('files_home', repo_name=c.repo_name,
138 c.url_prev = url('files_home', repo_name=c.repo_name,
139 revision=prev_rev.raw_id, f_path=f_path)
139 revision=prev_rev.raw_id, f_path=f_path)
140 if c.branch:
140 if c.branch:
141 c.url_prev += '?branch=%s' % c.branch
141 c.url_prev += '?branch=%s' % c.branch
142 except (ChangesetDoesNotExistError, VCSError):
142 except (ChangesetDoesNotExistError, VCSError):
143 c.url_prev = '#'
143 c.url_prev = '#'
144
144
145 # next link
145 # next link
146 try:
146 try:
147 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
147 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
148 c.url_next = url('files_home', repo_name=c.repo_name,
148 c.url_next = url('files_home', repo_name=c.repo_name,
149 revision=next_rev.raw_id, f_path=f_path)
149 revision=next_rev.raw_id, f_path=f_path)
150 if c.branch:
150 if c.branch:
151 c.url_next += '?branch=%s' % c.branch
151 c.url_next += '?branch=%s' % c.branch
152 except (ChangesetDoesNotExistError, VCSError):
152 except (ChangesetDoesNotExistError, VCSError):
153 c.url_next = '#'
153 c.url_next = '#'
154
154
155 # files or dirs
155 # files or dirs
156 try:
156 try:
157 c.file = c.changeset.get_node(f_path)
157 c.file = c.changeset.get_node(f_path)
158
158
159 if c.file.is_file():
159 if c.file.is_file():
160 c.load_full_history = False
160 c.load_full_history = False
161 file_last_cs = c.file.last_changeset
161 file_last_cs = c.file.last_changeset
162 c.file_changeset = (c.changeset
162 c.file_changeset = (c.changeset
163 if c.changeset.revision < file_last_cs.revision
163 if c.changeset.revision < file_last_cs.revision
164 else file_last_cs)
164 else file_last_cs)
165 #determine if we're on branch head
165 #determine if we're on branch head
166 _branches = c.rhodecode_repo.branches
166 _branches = c.rhodecode_repo.branches
167 c.on_branch_head = revision in _branches.keys() + _branches.values()
167 c.on_branch_head = revision in _branches.keys() + _branches.values()
168 _hist = []
168 _hist = []
169 c.file_history = []
169 c.file_history = []
170 if c.load_full_history:
170 if c.load_full_history:
171 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
171 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
172
172
173 c.authors = []
173 c.authors = []
174 for a in set([x.author for x in _hist]):
174 for a in set([x.author for x in _hist]):
175 c.authors.append((h.email(a), h.person(a)))
175 c.authors.append((h.email(a), h.person(a)))
176 else:
176 else:
177 c.authors = c.file_history = []
177 c.authors = c.file_history = []
178 except RepositoryError, e:
178 except RepositoryError, e:
179 h.flash(str(e), category='warning')
179 h.flash(str(e), category='warning')
180 redirect(h.url('files_home', repo_name=repo_name,
180 redirect(h.url('files_home', repo_name=repo_name,
181 revision='tip'))
181 revision='tip'))
182
182
183 if request.environ.get('HTTP_X_PARTIAL_XHR'):
183 if request.environ.get('HTTP_X_PARTIAL_XHR'):
184 return render('files/files_ypjax.html')
184 return render('files/files_ypjax.html')
185
185
186 return render('files/files.html')
186 return render('files/files.html')
187
187
188 def history(self, repo_name, revision, f_path, annotate=False):
188 def history(self, repo_name, revision, f_path, annotate=False):
189 if request.environ.get('HTTP_X_PARTIAL_XHR'):
189 if request.environ.get('HTTP_X_PARTIAL_XHR'):
190 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
190 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
191 c.f_path = f_path
191 c.f_path = f_path
192 c.annotate = annotate
192 c.annotate = annotate
193 c.file = c.changeset.get_node(f_path)
193 c.file = c.changeset.get_node(f_path)
194 if c.file.is_file():
194 if c.file.is_file():
195 file_last_cs = c.file.last_changeset
195 file_last_cs = c.file.last_changeset
196 c.file_changeset = (c.changeset
196 c.file_changeset = (c.changeset
197 if c.changeset.revision < file_last_cs.revision
197 if c.changeset.revision < file_last_cs.revision
198 else file_last_cs)
198 else file_last_cs)
199 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
199 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
200 c.authors = []
200 c.authors = []
201 for a in set([x.author for x in _hist]):
201 for a in set([x.author for x in _hist]):
202 c.authors.append((h.email(a), h.person(a)))
202 c.authors.append((h.email(a), h.person(a)))
203 return render('files/files_history_box.html')
203 return render('files/files_history_box.html')
204
204
205 @LoginRequired()
205 @LoginRequired()
206 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
206 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
207 'repository.admin')
207 'repository.admin')
208 def rawfile(self, repo_name, revision, f_path):
208 def rawfile(self, repo_name, revision, f_path):
209 cs = self.__get_cs_or_redirect(revision, repo_name)
209 cs = self.__get_cs_or_redirect(revision, repo_name)
210 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
210 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
211
211
212 response.content_disposition = 'attachment; filename=%s' % \
212 response.content_disposition = 'attachment; filename=%s' % \
213 safe_str(f_path.split(Repository.url_sep())[-1])
213 safe_str(f_path.split(Repository.url_sep())[-1])
214
214
215 response.content_type = file_node.mimetype
215 response.content_type = file_node.mimetype
216 return file_node.content
216 return file_node.content
217
217
218 @LoginRequired()
218 @LoginRequired()
219 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
219 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
220 'repository.admin')
220 'repository.admin')
221 def raw(self, repo_name, revision, f_path):
221 def raw(self, repo_name, revision, f_path):
222 cs = self.__get_cs_or_redirect(revision, repo_name)
222 cs = self.__get_cs_or_redirect(revision, repo_name)
223 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
223 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
224
224
225 raw_mimetype_mapping = {
225 raw_mimetype_mapping = {
226 # map original mimetype to a mimetype used for "show as raw"
226 # map original mimetype to a mimetype used for "show as raw"
227 # you can also provide a content-disposition to override the
227 # you can also provide a content-disposition to override the
228 # default "attachment" disposition.
228 # default "attachment" disposition.
229 # orig_type: (new_type, new_dispo)
229 # orig_type: (new_type, new_dispo)
230
230
231 # show images inline:
231 # show images inline:
232 'image/x-icon': ('image/x-icon', 'inline'),
232 'image/x-icon': ('image/x-icon', 'inline'),
233 'image/png': ('image/png', 'inline'),
233 'image/png': ('image/png', 'inline'),
234 'image/gif': ('image/gif', 'inline'),
234 'image/gif': ('image/gif', 'inline'),
235 'image/jpeg': ('image/jpeg', 'inline'),
235 'image/jpeg': ('image/jpeg', 'inline'),
236 'image/svg+xml': ('image/svg+xml', 'inline'),
236 'image/svg+xml': ('image/svg+xml', 'inline'),
237 }
237 }
238
238
239 mimetype = file_node.mimetype
239 mimetype = file_node.mimetype
240 try:
240 try:
241 mimetype, dispo = raw_mimetype_mapping[mimetype]
241 mimetype, dispo = raw_mimetype_mapping[mimetype]
242 except KeyError:
242 except KeyError:
243 # we don't know anything special about this, handle it safely
243 # we don't know anything special about this, handle it safely
244 if file_node.is_binary:
244 if file_node.is_binary:
245 # do same as download raw for binary files
245 # do same as download raw for binary files
246 mimetype, dispo = 'application/octet-stream', 'attachment'
246 mimetype, dispo = 'application/octet-stream', 'attachment'
247 else:
247 else:
248 # do not just use the original mimetype, but force text/plain,
248 # do not just use the original mimetype, but force text/plain,
249 # otherwise it would serve text/html and that might be unsafe.
249 # otherwise it would serve text/html and that might be unsafe.
250 # Note: underlying vcs library fakes text/plain mimetype if the
250 # Note: underlying vcs library fakes text/plain mimetype if the
251 # mimetype can not be determined and it thinks it is not
251 # mimetype can not be determined and it thinks it is not
252 # binary.This might lead to erroneous text display in some
252 # binary.This might lead to erroneous text display in some
253 # cases, but helps in other cases, like with text files
253 # cases, but helps in other cases, like with text files
254 # without extension.
254 # without extension.
255 mimetype, dispo = 'text/plain', 'inline'
255 mimetype, dispo = 'text/plain', 'inline'
256
256
257 if dispo == 'attachment':
257 if dispo == 'attachment':
258 dispo = 'attachment; filename=%s' % \
258 dispo = 'attachment; filename=%s' % \
259 safe_str(f_path.split(os.sep)[-1])
259 safe_str(f_path.split(os.sep)[-1])
260
260
261 response.content_disposition = dispo
261 response.content_disposition = dispo
262 response.content_type = mimetype
262 response.content_type = mimetype
263 return file_node.content
263 return file_node.content
264
264
265 @LoginRequired()
265 @LoginRequired()
266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
267 def edit(self, repo_name, revision, f_path):
267 def edit(self, repo_name, revision, f_path):
268 repo = c.rhodecode_db_repo
268 repo = c.rhodecode_db_repo
269 if repo.enable_locking and repo.locked[0]:
269 if repo.enable_locking and repo.locked[0]:
270 h.flash(_('This repository is has been locked by %s on %s')
270 h.flash(_('This repository is has been locked by %s on %s')
271 % (h.person_by_id(repo.locked[0]),
271 % (h.person_by_id(repo.locked[0]),
272 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
272 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
273 'warning')
273 'warning')
274 return redirect(h.url('files_home',
274 return redirect(h.url('files_home',
275 repo_name=repo_name, revision='tip'))
275 repo_name=repo_name, revision='tip'))
276
276
277 # check if revision is a branch identifier- basically we cannot
277 # check if revision is a branch identifier- basically we cannot
278 # create multiple heads via file editing
278 # create multiple heads via file editing
279 _branches = repo.scm_instance.branches
279 _branches = repo.scm_instance.branches
280 # check if revision is a branch name or branch hash
280 # check if revision is a branch name or branch hash
281 if revision not in _branches.keys() + _branches.values():
281 if revision not in _branches.keys() + _branches.values():
282 h.flash(_('You can only edit files with revision '
282 h.flash(_('You can only edit files with revision '
283 'being a valid branch '), category='warning')
283 'being a valid branch '), category='warning')
284 return redirect(h.url('files_home',
284 return redirect(h.url('files_home',
285 repo_name=repo_name, revision='tip',
285 repo_name=repo_name, revision='tip',
286 f_path=f_path))
286 f_path=f_path))
287
287
288 r_post = request.POST
288 r_post = request.POST
289
289
290 c.cs = self.__get_cs_or_redirect(revision, repo_name)
290 c.cs = self.__get_cs_or_redirect(revision, repo_name)
291 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
291 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
292
292
293 if c.file.is_binary:
293 if c.file.is_binary:
294 return redirect(url('files_home', repo_name=c.repo_name,
294 return redirect(url('files_home', repo_name=c.repo_name,
295 revision=c.cs.raw_id, f_path=f_path))
295 revision=c.cs.raw_id, f_path=f_path))
296 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
296 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
297 c.f_path = f_path
297 c.f_path = f_path
298
298
299 if r_post:
299 if r_post:
300
300
301 old_content = c.file.content
301 old_content = c.file.content
302 sl = old_content.splitlines(1)
302 sl = old_content.splitlines(1)
303 first_line = sl[0] if sl else ''
303 first_line = sl[0] if sl else ''
304 # modes: 0 - Unix, 1 - Mac, 2 - DOS
304 # modes: 0 - Unix, 1 - Mac, 2 - DOS
305 mode = detect_mode(first_line, 0)
305 mode = detect_mode(first_line, 0)
306 content = convert_line_endings(r_post.get('content'), mode)
306 content = convert_line_endings(r_post.get('content'), mode)
307
307
308 message = r_post.get('message') or c.default_message
308 message = r_post.get('message') or c.default_message
309 author = self.rhodecode_user.full_contact
309 author = self.rhodecode_user.full_contact
310
310
311 if content == old_content:
311 if content == old_content:
312 h.flash(_('No changes'),
312 h.flash(_('No changes'),
313 category='warning')
313 category='warning')
314 return redirect(url('changeset_home', repo_name=c.repo_name,
314 return redirect(url('changeset_home', repo_name=c.repo_name,
315 revision='tip'))
315 revision='tip'))
316 try:
316 try:
317 self.scm_model.commit_change(repo=c.rhodecode_repo,
317 self.scm_model.commit_change(repo=c.rhodecode_repo,
318 repo_name=repo_name, cs=c.cs,
318 repo_name=repo_name, cs=c.cs,
319 user=self.rhodecode_user.user_id,
319 user=self.rhodecode_user.user_id,
320 author=author, message=message,
320 author=author, message=message,
321 content=content, f_path=f_path)
321 content=content, f_path=f_path)
322 h.flash(_('Successfully committed to %s') % f_path,
322 h.flash(_('Successfully committed to %s') % f_path,
323 category='success')
323 category='success')
324
324
325 except Exception:
325 except Exception:
326 log.error(traceback.format_exc())
326 log.error(traceback.format_exc())
327 h.flash(_('Error occurred during commit'), category='error')
327 h.flash(_('Error occurred during commit'), category='error')
328 return redirect(url('changeset_home',
328 return redirect(url('changeset_home',
329 repo_name=c.repo_name, revision='tip'))
329 repo_name=c.repo_name, revision='tip'))
330
330
331 return render('files/files_edit.html')
331 return render('files/files_edit.html')
332
332
333 @LoginRequired()
333 @LoginRequired()
334 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
334 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
335 def add(self, repo_name, revision, f_path):
335 def add(self, repo_name, revision, f_path):
336
336
337 repo = Repository.get_by_repo_name(repo_name)
337 repo = Repository.get_by_repo_name(repo_name)
338 if repo.enable_locking and repo.locked[0]:
338 if repo.enable_locking and repo.locked[0]:
339 h.flash(_('This repository is has been locked by %s on %s')
339 h.flash(_('This repository is has been locked by %s on %s')
340 % (h.person_by_id(repo.locked[0]),
340 % (h.person_by_id(repo.locked[0]),
341 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
341 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
342 'warning')
342 'warning')
343 return redirect(h.url('files_home',
343 return redirect(h.url('files_home',
344 repo_name=repo_name, revision='tip'))
344 repo_name=repo_name, revision='tip'))
345
345
346 r_post = request.POST
346 r_post = request.POST
347 c.cs = self.__get_cs_or_redirect(revision, repo_name,
347 c.cs = self.__get_cs_or_redirect(revision, repo_name,
348 redirect_after=False)
348 redirect_after=False)
349 if c.cs is None:
349 if c.cs is None:
350 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
350 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
351 c.default_message = (_('Added file via RhodeCode'))
351 c.default_message = (_('Added file via RhodeCode'))
352 c.f_path = f_path
352 c.f_path = f_path
353
353
354 if r_post:
354 if r_post:
355 unix_mode = 0
355 unix_mode = 0
356 content = convert_line_endings(r_post.get('content'), unix_mode)
356 content = convert_line_endings(r_post.get('content'), unix_mode)
357
357
358 message = r_post.get('message') or c.default_message
358 message = r_post.get('message') or c.default_message
359 filename = r_post.get('filename')
359 filename = r_post.get('filename')
360 location = r_post.get('location')
360 location = r_post.get('location')
361 file_obj = r_post.get('upload_file', None)
361 file_obj = r_post.get('upload_file', None)
362
362
363 if file_obj is not None and hasattr(file_obj, 'filename'):
363 if file_obj is not None and hasattr(file_obj, 'filename'):
364 filename = file_obj.filename
364 filename = file_obj.filename
365 content = file_obj.file
365 content = file_obj.file
366
366
367 if not content:
367 if not content:
368 h.flash(_('No content'), category='warning')
368 h.flash(_('No content'), category='warning')
369 return redirect(url('changeset_home', repo_name=c.repo_name,
369 return redirect(url('changeset_home', repo_name=c.repo_name,
370 revision='tip'))
370 revision='tip'))
371 if not filename:
371 if not filename:
372 h.flash(_('No filename'), category='warning')
372 h.flash(_('No filename'), category='warning')
373 return redirect(url('changeset_home', repo_name=c.repo_name,
373 return redirect(url('changeset_home', repo_name=c.repo_name,
374 revision='tip'))
374 revision='tip'))
375 if location.startswith('/') or location.startswith('.') or '../' in location:
375 if location.startswith('/') or location.startswith('.') or '../' in location:
376 h.flash(_('location must be relative path and must not '
376 h.flash(_('location must be relative path and must not '
377 'contain .. in path'), category='warning')
377 'contain .. in path'), category='warning')
378 return redirect(url('changeset_home', repo_name=c.repo_name,
378 return redirect(url('changeset_home', repo_name=c.repo_name,
379 revision='tip'))
379 revision='tip'))
380 location = os.path.normpath(location)
380 location = os.path.normpath(location)
381 filename = os.path.basename(filename)
381 filename = os.path.basename(filename)
382 node_path = os.path.join(location, filename)
382 node_path = os.path.join(location, filename)
383 author = self.rhodecode_user.full_contact
383 author = self.rhodecode_user.full_contact
384
384
385 try:
385 try:
386 self.scm_model.create_node(repo=c.rhodecode_repo,
386 self.scm_model.create_node(repo=c.rhodecode_repo,
387 repo_name=repo_name, cs=c.cs,
387 repo_name=repo_name, cs=c.cs,
388 user=self.rhodecode_user.user_id,
388 user=self.rhodecode_user.user_id,
389 author=author, message=message,
389 author=author, message=message,
390 content=content, f_path=node_path)
390 content=content, f_path=node_path)
391 h.flash(_('Successfully committed to %s') % node_path,
391 h.flash(_('Successfully committed to %s') % node_path,
392 category='success')
392 category='success')
393 except (NodeError, NodeAlreadyExistsError), e:
393 except (NodeError, NodeAlreadyExistsError), e:
394 h.flash(_(e), category='error')
394 h.flash(_(e), category='error')
395 except Exception:
395 except Exception:
396 log.error(traceback.format_exc())
396 log.error(traceback.format_exc())
397 h.flash(_('Error occurred during commit'), category='error')
397 h.flash(_('Error occurred during commit'), category='error')
398 return redirect(url('changeset_home',
398 return redirect(url('changeset_home',
399 repo_name=c.repo_name, revision='tip'))
399 repo_name=c.repo_name, revision='tip'))
400
400
401 return render('files/files_add.html')
401 return render('files/files_add.html')
402
402
403 @LoginRequired()
403 @LoginRequired()
404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
405 'repository.admin')
405 'repository.admin')
406 def archivefile(self, repo_name, fname):
406 def archivefile(self, repo_name, fname):
407
407
408 fileformat = None
408 fileformat = None
409 revision = None
409 revision = None
410 ext = None
410 ext = None
411 subrepos = request.GET.get('subrepos') == 'true'
411 subrepos = request.GET.get('subrepos') == 'true'
412
412
413 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
413 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
414 archive_spec = fname.split(ext_data[1])
414 archive_spec = fname.split(ext_data[1])
415 if len(archive_spec) == 2 and archive_spec[1] == '':
415 if len(archive_spec) == 2 and archive_spec[1] == '':
416 fileformat = a_type or ext_data[1]
416 fileformat = a_type or ext_data[1]
417 revision = archive_spec[0]
417 revision = archive_spec[0]
418 ext = ext_data[1]
418 ext = ext_data[1]
419
419
420 try:
420 try:
421 dbrepo = RepoModel().get_by_repo_name(repo_name)
421 dbrepo = RepoModel().get_by_repo_name(repo_name)
422 if dbrepo.enable_downloads is False:
422 if dbrepo.enable_downloads is False:
423 return _('downloads disabled')
423 return _('downloads disabled')
424
424
425 if c.rhodecode_repo.alias == 'hg':
425 if c.rhodecode_repo.alias == 'hg':
426 # patch and reset hooks section of UI config to not run any
426 # patch and reset hooks section of UI config to not run any
427 # hooks on fetching archives with subrepos
427 # hooks on fetching archives with subrepos
428 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
428 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
429 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
429 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
430
430
431 cs = c.rhodecode_repo.get_changeset(revision)
431 cs = c.rhodecode_repo.get_changeset(revision)
432 content_type = settings.ARCHIVE_SPECS[fileformat][0]
432 content_type = settings.ARCHIVE_SPECS[fileformat][0]
433 except ChangesetDoesNotExistError:
433 except ChangesetDoesNotExistError:
434 return _('Unknown revision %s') % revision
434 return _('Unknown revision %s') % revision
435 except EmptyRepositoryError:
435 except EmptyRepositoryError:
436 return _('Empty repository')
436 return _('Empty repository')
437 except (ImproperArchiveTypeError, KeyError):
437 except (ImproperArchiveTypeError, KeyError):
438 return _('Unknown archive type')
438 return _('Unknown archive type')
439 # archive cache
439 # archive cache
440 from rhodecode import CONFIG
440 from rhodecode import CONFIG
441 rev_name = cs.raw_id[:12]
441 rev_name = cs.raw_id[:12]
442 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
442 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
443 safe_str(rev_name), ext)
443 safe_str(rev_name), ext)
444
444
445 use_cached_archive = False # defines if we use cached version of archive
445 use_cached_archive = False # defines if we use cached version of archive
446 archive_cache_enabled = CONFIG.get('archive_cache_dir')
446 archive_cache_enabled = CONFIG.get('archive_cache_dir')
447 if not subrepos and archive_cache_enabled:
447 if not subrepos and archive_cache_enabled:
448 #check if we it's ok to write
448 #check if we it's ok to write
449 if not os.path.isdir(CONFIG['archive_cache_dir']):
449 if not os.path.isdir(CONFIG['archive_cache_dir']):
450 os.makedirs(CONFIG['archive_cache_dir'])
450 os.makedirs(CONFIG['archive_cache_dir'])
451 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
451 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
452 if os.path.isfile(cached_archive_path):
452 if os.path.isfile(cached_archive_path):
453 log.debug('Found cached archive in %s' % cached_archive_path)
453 log.debug('Found cached archive in %s' % cached_archive_path)
454 fd, archive = None, cached_archive_path
454 fd, archive = None, cached_archive_path
455 use_cached_archive = True
455 use_cached_archive = True
456 else:
456 else:
457 log.debug('Archive %s is not yet cached' % (archive_name))
457 log.debug('Archive %s is not yet cached' % (archive_name))
458
458
459 if not use_cached_archive:
459 if not use_cached_archive:
460 #generate new archive
460 #generate new archive
461 try:
461 try:
462 fd, archive = tempfile.mkstemp()
462 fd, archive = tempfile.mkstemp()
463 t = open(archive, 'wb')
463 t = open(archive, 'wb')
464 log.debug('Creating new temp archive in %s' % archive)
464 log.debug('Creating new temp archive in %s' % archive)
465 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
465 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
466 if archive_cache_enabled:
466 if archive_cache_enabled:
467 #if we generated the archive and use cache rename that
467 #if we generated the archive and use cache rename that
468 log.debug('Storing new archive in %s' % cached_archive_path)
468 log.debug('Storing new archive in %s' % cached_archive_path)
469 shutil.move(archive, cached_archive_path)
469 shutil.move(archive, cached_archive_path)
470 archive = cached_archive_path
470 archive = cached_archive_path
471 finally:
471 finally:
472 t.close()
472 t.close()
473
473
474 def get_chunked_archive(archive):
474 def get_chunked_archive(archive):
475 stream = open(archive, 'rb')
475 stream = open(archive, 'rb')
476 while True:
476 while True:
477 data = stream.read(16 * 1024)
477 data = stream.read(16 * 1024)
478 if not data:
478 if not data:
479 stream.close()
479 stream.close()
480 if fd: # fd means we used temporary file
480 if fd: # fd means we used temporary file
481 os.close(fd)
481 os.close(fd)
482 if not archive_cache_enabled:
482 if not archive_cache_enabled:
483 log.debug('Destroing temp archive %s' % archive)
483 log.debug('Destroing temp archive %s' % archive)
484 os.remove(archive)
484 os.remove(archive)
485 break
485 break
486 yield data
486 yield data
487
487
488 response.content_disposition = str('attachment; filename=%s' % (archive_name))
488 response.content_disposition = str('attachment; filename=%s' % (archive_name))
489 response.content_type = str(content_type)
489 response.content_type = str(content_type)
490 return get_chunked_archive(archive)
490 return get_chunked_archive(archive)
491
491
492 @LoginRequired()
492 @LoginRequired()
493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
494 'repository.admin')
494 'repository.admin')
495 def diff(self, repo_name, f_path):
495 def diff(self, repo_name, f_path):
496 ignore_whitespace = request.GET.get('ignorews') == '1'
496 ignore_whitespace = request.GET.get('ignorews') == '1'
497 line_context = request.GET.get('context', 3)
497 line_context = request.GET.get('context', 3)
498 diff1 = request.GET.get('diff1', '')
498 diff1 = request.GET.get('diff1', '')
499 diff2 = request.GET.get('diff2', '')
499 diff2 = request.GET.get('diff2', '')
500 c.action = request.GET.get('diff')
500 c.action = request.GET.get('diff')
501 c.no_changes = diff1 == diff2
501 c.no_changes = diff1 == diff2
502 c.f_path = f_path
502 c.f_path = f_path
503 c.big_diff = False
503 c.big_diff = False
504 c.anchor_url = anchor_url
504 c.anchor_url = anchor_url
505 c.ignorews_url = _ignorews_url
505 c.ignorews_url = _ignorews_url
506 c.context_url = _context_url
506 c.context_url = _context_url
507 c.changes = OrderedDict()
507 c.changes = OrderedDict()
508 c.changes[diff2] = []
508 c.changes[diff2] = []
509
509
510 #special case if we want a show rev only, it's impl here
510 #special case if we want a show rev only, it's impl here
511 #to reduce JS and callbacks
511 #to reduce JS and callbacks
512
512
513 if request.GET.get('show_rev'):
513 if request.GET.get('show_rev'):
514 if str2bool(request.GET.get('annotate', 'False')):
514 if str2bool(request.GET.get('annotate', 'False')):
515 _url = url('files_annotate_home', repo_name=c.repo_name,
515 _url = url('files_annotate_home', repo_name=c.repo_name,
516 revision=diff1, f_path=c.f_path)
516 revision=diff1, f_path=c.f_path)
517 else:
517 else:
518 _url = url('files_home', repo_name=c.repo_name,
518 _url = url('files_home', repo_name=c.repo_name,
519 revision=diff1, f_path=c.f_path)
519 revision=diff1, f_path=c.f_path)
520
520
521 return redirect(_url)
521 return redirect(_url)
522 try:
522 try:
523 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
523 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
524 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
524 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
525 try:
525 try:
526 node1 = c.changeset_1.get_node(f_path)
526 node1 = c.changeset_1.get_node(f_path)
527 if node1.is_dir():
527 if node1.is_dir():
528 raise NodeError('%s path is a %s not a file'
528 raise NodeError('%s path is a %s not a file'
529 % (node1, type(node1)))
529 % (node1, type(node1)))
530 except NodeDoesNotExistError:
530 except NodeDoesNotExistError:
531 c.changeset_1 = EmptyChangeset(cs=diff1,
531 c.changeset_1 = EmptyChangeset(cs=diff1,
532 revision=c.changeset_1.revision,
532 revision=c.changeset_1.revision,
533 repo=c.rhodecode_repo)
533 repo=c.rhodecode_repo)
534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
535 else:
535 else:
536 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
536 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
537 node1 = FileNode(f_path, '', changeset=c.changeset_1)
537 node1 = FileNode(f_path, '', changeset=c.changeset_1)
538
538
539 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
539 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
540 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
540 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
541 try:
541 try:
542 node2 = c.changeset_2.get_node(f_path)
542 node2 = c.changeset_2.get_node(f_path)
543 if node2.is_dir():
543 if node2.is_dir():
544 raise NodeError('%s path is a %s not a file'
544 raise NodeError('%s path is a %s not a file'
545 % (node2, type(node2)))
545 % (node2, type(node2)))
546 except NodeDoesNotExistError:
546 except NodeDoesNotExistError:
547 c.changeset_2 = EmptyChangeset(cs=diff2,
547 c.changeset_2 = EmptyChangeset(cs=diff2,
548 revision=c.changeset_2.revision,
548 revision=c.changeset_2.revision,
549 repo=c.rhodecode_repo)
549 repo=c.rhodecode_repo)
550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
551 else:
551 else:
552 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
552 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
553 node2 = FileNode(f_path, '', changeset=c.changeset_2)
553 node2 = FileNode(f_path, '', changeset=c.changeset_2)
554 except (RepositoryError, NodeError):
554 except (RepositoryError, NodeError):
555 log.error(traceback.format_exc())
555 log.error(traceback.format_exc())
556 return redirect(url('files_home', repo_name=c.repo_name,
556 return redirect(url('files_home', repo_name=c.repo_name,
557 f_path=f_path))
557 f_path=f_path))
558
558
559 if c.action == 'download':
559 if c.action == 'download':
560 _diff = diffs.get_gitdiff(node1, node2,
560 _diff = diffs.get_gitdiff(node1, node2,
561 ignore_whitespace=ignore_whitespace,
561 ignore_whitespace=ignore_whitespace,
562 context=line_context)
562 context=line_context)
563 diff = diffs.DiffProcessor(_diff, format='gitdiff')
563 diff = diffs.DiffProcessor(_diff, format='gitdiff')
564
564
565 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
565 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
566 response.content_type = 'text/plain'
566 response.content_type = 'text/plain'
567 response.content_disposition = (
567 response.content_disposition = (
568 'attachment; filename=%s' % diff_name
568 'attachment; filename=%s' % diff_name
569 )
569 )
570 return diff.as_raw()
570 return diff.as_raw()
571
571
572 elif c.action == 'raw':
572 elif c.action == 'raw':
573 _diff = diffs.get_gitdiff(node1, node2,
573 _diff = diffs.get_gitdiff(node1, node2,
574 ignore_whitespace=ignore_whitespace,
574 ignore_whitespace=ignore_whitespace,
575 context=line_context)
575 context=line_context)
576 diff = diffs.DiffProcessor(_diff, format='gitdiff')
576 diff = diffs.DiffProcessor(_diff, format='gitdiff')
577 response.content_type = 'text/plain'
577 response.content_type = 'text/plain'
578 return diff.as_raw()
578 return diff.as_raw()
579
579
580 else:
580 else:
581 fid = h.FID(diff2, node2.path)
581 fid = h.FID(diff2, node2.path)
582 line_context_lcl = get_line_ctx(fid, request.GET)
582 line_context_lcl = get_line_ctx(fid, request.GET)
583 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
583 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
584
584
585 lim = request.GET.get('fulldiff') or self.cut_off_limit
585 lim = request.GET.get('fulldiff') or self.cut_off_limit
586 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
586 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
587 filenode_new=node2,
587 filenode_new=node2,
588 cut_off_limit=lim,
588 cut_off_limit=lim,
589 ignore_whitespace=ign_whitespace_lcl,
589 ignore_whitespace=ign_whitespace_lcl,
590 line_context=line_context_lcl,
590 line_context=line_context_lcl,
591 enable_comments=False)
591 enable_comments=False)
592 op = ''
592 op = ''
593 filename = node1.path
593 filename = node1.path
594 cs_changes = {
594 cs_changes = {
595 'fid': [cs1, cs2, op, filename, diff, st]
595 'fid': [cs1, cs2, op, filename, diff, st]
596 }
596 }
597 c.changes = cs_changes
597 c.changes = cs_changes
598
598
599 return render('files/file_diff.html')
599 return render('files/file_diff.html')
600
600
601 def _get_node_history(self, cs, f_path, changesets=None):
601 def _get_node_history(self, cs, f_path, changesets=None):
602 """
602 """
603 get changesets history for given node
603 get changesets history for given node
604
604
605 :param cs: changeset to calculate history
605 :param cs: changeset to calculate history
606 :param f_path: path for node to calculate history for
606 :param f_path: path for node to calculate history for
607 :param changesets: if passed don't calculate history and take
607 :param changesets: if passed don't calculate history and take
608 changesets defined in this list
608 changesets defined in this list
609 """
609 """
610 # calculate history based on tip
610 # calculate history based on tip
611 tip_cs = c.rhodecode_repo.get_changeset()
611 tip_cs = c.rhodecode_repo.get_changeset()
612 if changesets is None:
612 if changesets is None:
613 try:
613 try:
614 changesets = tip_cs.get_file_history(f_path)
614 changesets = tip_cs.get_file_history(f_path)
615 except (NodeDoesNotExistError, ChangesetError):
615 except (NodeDoesNotExistError, ChangesetError):
616 #this node is not present at tip !
616 #this node is not present at tip !
617 changesets = cs.get_file_history(f_path)
617 changesets = cs.get_file_history(f_path)
618 hist_l = []
618 hist_l = []
619
619
620 changesets_group = ([], _("Changesets"))
620 changesets_group = ([], _("Changesets"))
621 branches_group = ([], _("Branches"))
621 branches_group = ([], _("Branches"))
622 tags_group = ([], _("Tags"))
622 tags_group = ([], _("Tags"))
623 _hg = cs.repository.alias == 'hg'
623 _hg = cs.repository.alias == 'hg'
624 for chs in changesets:
624 for chs in changesets:
625 #_branch = '(%s)' % chs.branch if _hg else ''
625 #_branch = '(%s)' % chs.branch if _hg else ''
626 _branch = chs.branch
626 _branch = chs.branch
627 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
627 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
628 changesets_group[0].append((chs.raw_id, n_desc,))
628 changesets_group[0].append((chs.raw_id, n_desc,))
629 hist_l.append(changesets_group)
629 hist_l.append(changesets_group)
630
630
631 for name, chs in c.rhodecode_repo.branches.items():
631 for name, chs in c.rhodecode_repo.branches.items():
632 branches_group[0].append((chs, name),)
632 branches_group[0].append((chs, name),)
633 hist_l.append(branches_group)
633 hist_l.append(branches_group)
634
634
635 for name, chs in c.rhodecode_repo.tags.items():
635 for name, chs in c.rhodecode_repo.tags.items():
636 tags_group[0].append((chs, name),)
636 tags_group[0].append((chs, name),)
637 hist_l.append(tags_group)
637 hist_l.append(tags_group)
638
638
639 return hist_l, changesets
639 return hist_l, changesets
640
640
641 @LoginRequired()
641 @LoginRequired()
642 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
642 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
643 'repository.admin')
643 'repository.admin')
644 @jsonify
644 @jsonify
645 def nodelist(self, repo_name, revision, f_path):
645 def nodelist(self, repo_name, revision, f_path):
646 if request.environ.get('HTTP_X_PARTIAL_XHR'):
646 if request.environ.get('HTTP_X_PARTIAL_XHR'):
647 cs = self.__get_cs_or_redirect(revision, repo_name)
647 cs = self.__get_cs_or_redirect(revision, repo_name)
648 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
648 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
649 flat=False)
649 flat=False)
650 return {'nodes': _d + _f}
650 return {'nodes': _d + _f}
General Comments 0
You need to be logged in to leave comments. Login now