##// END OF EJS Templates
auth decorators are not used anymore on __before__...
marcink -
r3749:b950b884 beta
parent child Browse files
Show More
@@ -1,57 +1,57 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.bookmarks
3 rhodecode.controllers.bookmarks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Bookmarks controller for rhodecode
6 Bookmarks controller for rhodecode
7
7
8 :created_on: Dec 1, 2011
8 :created_on: Dec 1, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-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 import logging
25 import logging
26
26
27 from pylons import tmpl_context as c
27 from pylons import tmpl_context as c
28
28
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.base import BaseRepoController, render
30 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.compat import OrderedDict
31 from rhodecode.lib.compat import OrderedDict
32 from webob.exc import HTTPNotFound
32 from webob.exc import HTTPNotFound
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 class BookmarksController(BaseRepoController):
37 class BookmarksController(BaseRepoController):
38
38
39 def __before__(self):
40 super(BookmarksController, self).__before__()
41
39 @LoginRequired()
42 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
44 'repository.admin')
42 def __before__(self):
43 super(BookmarksController, self).__before__()
44
45 def index(self):
45 def index(self):
46 if c.rhodecode_repo.alias != 'hg':
46 if c.rhodecode_repo.alias != 'hg':
47 raise HTTPNotFound()
47 raise HTTPNotFound()
48
48
49 c.repo_bookmarks = OrderedDict()
49 c.repo_bookmarks = OrderedDict()
50
50
51 bookmarks = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
51 bookmarks = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
52 name, hash_ in c.rhodecode_repo._repo._bookmarks.items()]
52 name, hash_ in c.rhodecode_repo._repo._bookmarks.items()]
53 ordered_tags = sorted(bookmarks, key=lambda x: x[1].date, reverse=True)
53 ordered_tags = sorted(bookmarks, key=lambda x: x[1].date, reverse=True)
54 for name, cs_book in ordered_tags:
54 for name, cs_book in ordered_tags:
55 c.repo_bookmarks[name] = cs_book
55 c.repo_bookmarks[name] = cs_book
56
56
57 return render('bookmarks/bookmarks.html')
57 return render('bookmarks/bookmarks.html')
@@ -1,76 +1,75 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.branches
3 rhodecode.controllers.branches
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 branches controller for rhodecode
6 branches 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 binascii
27 import binascii
28
28
29 from pylons import tmpl_context as c
29 from pylons import tmpl_context as c
30
30
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.lib.base import BaseRepoController, render
33 from rhodecode.lib.compat import OrderedDict
33 from rhodecode.lib.compat import OrderedDict
34 from rhodecode.lib.utils2 import safe_unicode
34 from rhodecode.lib.utils2 import safe_unicode
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class BranchesController(BaseRepoController):
39 class BranchesController(BaseRepoController):
40
40
41 def __before__(self):
42 super(BranchesController, self).__before__()
43
41 @LoginRequired()
44 @LoginRequired()
42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
45 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 'repository.admin')
46 'repository.admin')
44 def __before__(self):
45 super(BranchesController, self).__before__()
46
47 def index(self):
47 def index(self):
48
48
49 def _branchtags(localrepo):
49 def _branchtags(localrepo):
50 bt_closed = {}
50 bt_closed = {}
51 for bn, heads in localrepo.branchmap().iteritems():
51 for bn, heads in localrepo.branchmap().iteritems():
52 tip = heads[-1]
52 tip = heads[-1]
53 if 'close' in localrepo.changelog.read(tip)[5]:
53 if 'close' in localrepo.changelog.read(tip)[5]:
54 bt_closed[bn] = tip
54 bt_closed[bn] = tip
55 return bt_closed
55 return bt_closed
56
56
57 cs_g = c.rhodecode_repo.get_changeset
57 cs_g = c.rhodecode_repo.get_changeset
58
58
59 c.repo_closed_branches = {}
59 c.repo_closed_branches = {}
60 if c.rhodecode_db_repo.repo_type == 'hg':
60 if c.rhodecode_db_repo.repo_type == 'hg':
61 bt_closed = _branchtags(c.rhodecode_repo._repo)
61 bt_closed = _branchtags(c.rhodecode_repo._repo)
62 _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),)
62 _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),)
63 for n, h in bt_closed.items()]
63 for n, h in bt_closed.items()]
64
64
65 c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
65 c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
66 key=lambda ctx: ctx[0],
66 key=lambda ctx: ctx[0],
67 reverse=False))
67 reverse=False))
68
68
69 _branches = [(safe_unicode(n), cs_g(h))
69 _branches = [(safe_unicode(n), cs_g(h))
70 for n, h in c.rhodecode_repo.branches.items()]
70 for n, h in c.rhodecode_repo.branches.items()]
71 c.repo_branches = OrderedDict(sorted(_branches,
71 c.repo_branches = OrderedDict(sorted(_branches,
72 key=lambda ctx: ctx[0],
72 key=lambda ctx: ctx[0],
73 reverse=False))
73 reverse=False))
74
74
75
76 return render('branches/branches.html')
75 return render('branches/branches.html')
@@ -1,119 +1,122 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()
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 'repository.admin')
50 def __before__(self):
47 def __before__(self):
51 super(ChangelogController, self).__before__()
48 super(ChangelogController, self).__before__()
52 c.affected_files_cut_off = 60
49 c.affected_files_cut_off = 60
53
50
51 def _graph(self, repo, revs_int, repo_size, size, p):
52 """
53 Generates a DAG graph for repo
54
55 :param repo:
56 :param revs_int:
57 :param repo_size:
58 :param size:
59 :param p:
60 """
61 if not revs_int:
62 c.jsdata = json.dumps([])
63 return
64
65 data = []
66 revs = revs_int
67
68 dag = _dagwalker(repo, revs, repo.alias)
69 dag = _colored(dag)
70 for (id, type, ctx, vtx, edges) in dag:
71 data.append(['', vtx, edges])
72
73 c.jsdata = json.dumps(data)
74
75 @LoginRequired()
76 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
77 'repository.admin')
54 def index(self):
78 def index(self):
55 limit = 100
79 limit = 100
56 default = 20
80 default = 20
57 if request.GET.get('size'):
81 if request.GET.get('size'):
58 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
82 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
59 session['changelog_size'] = c.size
83 session['changelog_size'] = c.size
60 session.save()
84 session.save()
61 else:
85 else:
62 c.size = int(session.get('changelog_size', default))
86 c.size = int(session.get('changelog_size', default))
63 # min size must be 1
87 # min size must be 1
64 c.size = max(c.size, 1)
88 c.size = max(c.size, 1)
65 p = safe_int(request.GET.get('page', 1), 1)
89 p = safe_int(request.GET.get('page', 1), 1)
66 branch_name = request.GET.get('branch', None)
90 branch_name = request.GET.get('branch', None)
67 try:
91 try:
68 collection = c.rhodecode_repo.get_changesets(start=0,
92 collection = c.rhodecode_repo.get_changesets(start=0,
69 branch_name=branch_name)
93 branch_name=branch_name)
70 c.total_cs = len(collection)
94 c.total_cs = len(collection)
71
95
72 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
96 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
73 items_per_page=c.size, branch=branch_name)
97 items_per_page=c.size, branch=branch_name)
74 collection = list(c.pagination)
98 collection = list(c.pagination)
75 page_revisions = [x.raw_id for x in c.pagination]
99 page_revisions = [x.raw_id for x in c.pagination]
76 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
100 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
77 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
101 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
78 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
102 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
79 log.error(traceback.format_exc())
103 log.error(traceback.format_exc())
80 h.flash(str(e), category='error')
104 h.flash(str(e), category='error')
81 return redirect(url('changelog_home', repo_name=c.repo_name))
105 return redirect(url('changelog_home', repo_name=c.repo_name))
82
106
83 c.branch_name = branch_name
107 c.branch_name = branch_name
84 c.branch_filters = [('', _('All Branches'))] + \
108 c.branch_filters = [('', _('All Branches'))] + \
85 [(k, k) for k in c.rhodecode_repo.branches.keys()]
109 [(k, k) for k in c.rhodecode_repo.branches.keys()]
86
110
87 self._graph(c.rhodecode_repo, [x.revision for x in c.pagination],
111 self._graph(c.rhodecode_repo, [x.revision for x in c.pagination],
88 c.total_cs, c.size, p)
112 c.total_cs, c.size, p)
89
113
90 return render('changelog/changelog.html')
114 return render('changelog/changelog.html')
91
115
116 @LoginRequired()
117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 'repository.admin')
92 def changelog_details(self, cs):
119 def changelog_details(self, cs):
93 if request.environ.get('HTTP_X_PARTIAL_XHR'):
120 if request.environ.get('HTTP_X_PARTIAL_XHR'):
94 c.cs = c.rhodecode_repo.get_changeset(cs)
121 c.cs = c.rhodecode_repo.get_changeset(cs)
95 return render('changelog/changelog_details.html')
122 return render('changelog/changelog_details.html')
96
97 def _graph(self, repo, revs_int, repo_size, size, p):
98 """
99 Generates a DAG graph for repo
100
101 :param repo:
102 :param revs_int:
103 :param repo_size:
104 :param size:
105 :param p:
106 """
107 if not revs_int:
108 c.jsdata = json.dumps([])
109 return
110
111 data = []
112 revs = revs_int
113
114 dag = _dagwalker(repo, revs, repo.alias)
115 dag = _colored(dag)
116 for (id, type, ctx, vtx, edges) in dag:
117 data.append(['', vtx, edges])
118
119 c.jsdata = json.dumps(data)
@@ -1,416 +1,437 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, HTTPNotFound
29 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
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 NotAnonymous
41 NotAnonymous
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import action_logger
43 from rhodecode.lib.utils import action_logger
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.lib.diffs import LimitedDiffContainer
51 from rhodecode.lib.diffs import LimitedDiffContainer
52 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
52 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
53 from rhodecode.lib.vcs.backends.base import EmptyChangeset
53 from rhodecode.lib.vcs.backends.base import EmptyChangeset
54 from rhodecode.lib.utils2 import safe_unicode
54 from rhodecode.lib.utils2 import safe_unicode
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 def _update_with_GET(params, GET):
59 def _update_with_GET(params, GET):
60 for k in ['diff1', 'diff2', 'diff']:
60 for k in ['diff1', 'diff2', 'diff']:
61 params[k] += GET.getall(k)
61 params[k] += GET.getall(k)
62
62
63
63
64 def anchor_url(revision, path, GET):
64 def anchor_url(revision, path, GET):
65 fid = h.FID(revision, path)
65 fid = h.FID(revision, path)
66 return h.url.current(anchor=fid, **dict(GET))
66 return h.url.current(anchor=fid, **dict(GET))
67
67
68
68
69 def get_ignore_ws(fid, GET):
69 def get_ignore_ws(fid, GET):
70 ig_ws_global = GET.get('ignorews')
70 ig_ws_global = GET.get('ignorews')
71 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
71 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
72 if ig_ws:
72 if ig_ws:
73 try:
73 try:
74 return int(ig_ws[0].split(':')[-1])
74 return int(ig_ws[0].split(':')[-1])
75 except Exception:
75 except Exception:
76 pass
76 pass
77 return ig_ws_global
77 return ig_ws_global
78
78
79
79
80 def _ignorews_url(GET, fileid=None):
80 def _ignorews_url(GET, fileid=None):
81 fileid = str(fileid) if fileid else None
81 fileid = str(fileid) if fileid else None
82 params = defaultdict(list)
82 params = defaultdict(list)
83 _update_with_GET(params, GET)
83 _update_with_GET(params, GET)
84 lbl = _('Show white space')
84 lbl = _('Show white space')
85 ig_ws = get_ignore_ws(fileid, GET)
85 ig_ws = get_ignore_ws(fileid, GET)
86 ln_ctx = get_line_ctx(fileid, GET)
86 ln_ctx = get_line_ctx(fileid, GET)
87 # global option
87 # global option
88 if fileid is None:
88 if fileid is None:
89 if ig_ws is None:
89 if ig_ws is None:
90 params['ignorews'] += [1]
90 params['ignorews'] += [1]
91 lbl = _('Ignore white space')
91 lbl = _('Ignore white space')
92 ctx_key = 'context'
92 ctx_key = 'context'
93 ctx_val = ln_ctx
93 ctx_val = ln_ctx
94 # per file options
94 # per file options
95 else:
95 else:
96 if ig_ws is None:
96 if ig_ws is None:
97 params[fileid] += ['WS:1']
97 params[fileid] += ['WS:1']
98 lbl = _('Ignore white space')
98 lbl = _('Ignore white space')
99
99
100 ctx_key = fileid
100 ctx_key = fileid
101 ctx_val = 'C:%s' % ln_ctx
101 ctx_val = 'C:%s' % ln_ctx
102 # if we have passed in ln_ctx pass it along to our params
102 # if we have passed in ln_ctx pass it along to our params
103 if ln_ctx:
103 if ln_ctx:
104 params[ctx_key] += [ctx_val]
104 params[ctx_key] += [ctx_val]
105
105
106 params['anchor'] = fileid
106 params['anchor'] = fileid
107 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
107 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
108 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
108 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
109
109
110
110
111 def get_line_ctx(fid, GET):
111 def get_line_ctx(fid, GET):
112 ln_ctx_global = GET.get('context')
112 ln_ctx_global = GET.get('context')
113 if fid:
113 if fid:
114 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
114 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
115 else:
115 else:
116 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
116 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
117 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
117 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
118 if ln_ctx:
118 if ln_ctx:
119 ln_ctx = [ln_ctx]
119 ln_ctx = [ln_ctx]
120
120
121 if ln_ctx:
121 if ln_ctx:
122 retval = ln_ctx[0].split(':')[-1]
122 retval = ln_ctx[0].split(':')[-1]
123 else:
123 else:
124 retval = ln_ctx_global
124 retval = ln_ctx_global
125
125
126 try:
126 try:
127 return int(retval)
127 return int(retval)
128 except Exception:
128 except Exception:
129 return 3
129 return 3
130
130
131
131
132 def _context_url(GET, fileid=None):
132 def _context_url(GET, fileid=None):
133 """
133 """
134 Generates url for context lines
134 Generates url for context lines
135
135
136 :param fileid:
136 :param fileid:
137 """
137 """
138
138
139 fileid = str(fileid) if fileid else None
139 fileid = str(fileid) if fileid else None
140 ig_ws = get_ignore_ws(fileid, GET)
140 ig_ws = get_ignore_ws(fileid, GET)
141 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
141 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
142
142
143 params = defaultdict(list)
143 params = defaultdict(list)
144 _update_with_GET(params, GET)
144 _update_with_GET(params, GET)
145
145
146 # global option
146 # global option
147 if fileid is None:
147 if fileid is None:
148 if ln_ctx > 0:
148 if ln_ctx > 0:
149 params['context'] += [ln_ctx]
149 params['context'] += [ln_ctx]
150
150
151 if ig_ws:
151 if ig_ws:
152 ig_ws_key = 'ignorews'
152 ig_ws_key = 'ignorews'
153 ig_ws_val = 1
153 ig_ws_val = 1
154
154
155 # per file option
155 # per file option
156 else:
156 else:
157 params[fileid] += ['C:%s' % ln_ctx]
157 params[fileid] += ['C:%s' % ln_ctx]
158 ig_ws_key = fileid
158 ig_ws_key = fileid
159 ig_ws_val = 'WS:%s' % 1
159 ig_ws_val = 'WS:%s' % 1
160
160
161 if ig_ws:
161 if ig_ws:
162 params[ig_ws_key] += [ig_ws_val]
162 params[ig_ws_key] += [ig_ws_val]
163
163
164 lbl = _('%s line context') % ln_ctx
164 lbl = _('%s line context') % ln_ctx
165
165
166 params['anchor'] = fileid
166 params['anchor'] = fileid
167 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
167 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
168 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
168 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
169
169
170
170
171 class ChangesetController(BaseRepoController):
171 class ChangesetController(BaseRepoController):
172
172
173 @LoginRequired()
174 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
175 'repository.admin')
176 def __before__(self):
173 def __before__(self):
177 super(ChangesetController, self).__before__()
174 super(ChangesetController, self).__before__()
178 c.affected_files_cut_off = 60
175 c.affected_files_cut_off = 60
179 repo_model = RepoModel()
176 repo_model = RepoModel()
180 c.users_array = repo_model.get_users_js()
177 c.users_array = repo_model.get_users_js()
181 c.users_groups_array = repo_model.get_users_groups_js()
178 c.users_groups_array = repo_model.get_users_groups_js()
182
179
180 @LoginRequired()
181 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
182 'repository.admin')
183 def index(self, revision, method='show'):
183 def index(self, revision, method='show'):
184 c.anchor_url = anchor_url
184 c.anchor_url = anchor_url
185 c.ignorews_url = _ignorews_url
185 c.ignorews_url = _ignorews_url
186 c.context_url = _context_url
186 c.context_url = _context_url
187 c.fulldiff = fulldiff = request.GET.get('fulldiff')
187 c.fulldiff = fulldiff = request.GET.get('fulldiff')
188 #get ranges of revisions if preset
188 #get ranges of revisions if preset
189 rev_range = revision.split('...')[:2]
189 rev_range = revision.split('...')[:2]
190 enable_comments = True
190 enable_comments = True
191 try:
191 try:
192 if len(rev_range) == 2:
192 if len(rev_range) == 2:
193 enable_comments = False
193 enable_comments = False
194 rev_start = rev_range[0]
194 rev_start = rev_range[0]
195 rev_end = rev_range[1]
195 rev_end = rev_range[1]
196 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
196 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
197 end=rev_end)
197 end=rev_end)
198 else:
198 else:
199 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
199 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
200
200
201 c.cs_ranges = list(rev_ranges)
201 c.cs_ranges = list(rev_ranges)
202 if not c.cs_ranges:
202 if not c.cs_ranges:
203 raise RepositoryError('Changeset range returned empty result')
203 raise RepositoryError('Changeset range returned empty result')
204
204
205 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
205 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
206 log.error(traceback.format_exc())
206 log.error(traceback.format_exc())
207 h.flash(str(e), category='error')
207 h.flash(str(e), category='error')
208 raise HTTPNotFound()
208 raise HTTPNotFound()
209
209
210 c.changes = OrderedDict()
210 c.changes = OrderedDict()
211
211
212 c.lines_added = 0 # count of lines added
212 c.lines_added = 0 # count of lines added
213 c.lines_deleted = 0 # count of lines removes
213 c.lines_deleted = 0 # count of lines removes
214
214
215 c.changeset_statuses = ChangesetStatus.STATUSES
215 c.changeset_statuses = ChangesetStatus.STATUSES
216 c.comments = []
216 c.comments = []
217 c.statuses = []
217 c.statuses = []
218 c.inline_comments = []
218 c.inline_comments = []
219 c.inline_cnt = 0
219 c.inline_cnt = 0
220
220
221 # Iterate over ranges (default changeset view is always one changeset)
221 # Iterate over ranges (default changeset view is always one changeset)
222 for changeset in c.cs_ranges:
222 for changeset in c.cs_ranges:
223 inlines = []
223 inlines = []
224 if method == 'show':
224 if method == 'show':
225 c.statuses.extend([ChangesetStatusModel().get_status(
225 c.statuses.extend([ChangesetStatusModel().get_status(
226 c.rhodecode_db_repo.repo_id, changeset.raw_id)])
226 c.rhodecode_db_repo.repo_id, changeset.raw_id)])
227
227
228 c.comments.extend(ChangesetCommentsModel()\
228 c.comments.extend(ChangesetCommentsModel()\
229 .get_comments(c.rhodecode_db_repo.repo_id,
229 .get_comments(c.rhodecode_db_repo.repo_id,
230 revision=changeset.raw_id))
230 revision=changeset.raw_id))
231
231
232 #comments from PR
232 #comments from PR
233 st = ChangesetStatusModel().get_statuses(
233 st = ChangesetStatusModel().get_statuses(
234 c.rhodecode_db_repo.repo_id, changeset.raw_id,
234 c.rhodecode_db_repo.repo_id, changeset.raw_id,
235 with_revisions=True)
235 with_revisions=True)
236 # from associated statuses, check the pull requests, and
236 # from associated statuses, check the pull requests, and
237 # show comments from them
237 # show comments from them
238
238
239 prs = set([x.pull_request for x in
239 prs = set([x.pull_request for x in
240 filter(lambda x: x.pull_request != None, st)])
240 filter(lambda x: x.pull_request != None, st)])
241
241
242 for pr in prs:
242 for pr in prs:
243 c.comments.extend(pr.comments)
243 c.comments.extend(pr.comments)
244 inlines = ChangesetCommentsModel()\
244 inlines = ChangesetCommentsModel()\
245 .get_inline_comments(c.rhodecode_db_repo.repo_id,
245 .get_inline_comments(c.rhodecode_db_repo.repo_id,
246 revision=changeset.raw_id)
246 revision=changeset.raw_id)
247 c.inline_comments.extend(inlines)
247 c.inline_comments.extend(inlines)
248
248
249 c.changes[changeset.raw_id] = []
249 c.changes[changeset.raw_id] = []
250
250
251 cs2 = changeset.raw_id
251 cs2 = changeset.raw_id
252 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
252 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
253 context_lcl = get_line_ctx('', request.GET)
253 context_lcl = get_line_ctx('', request.GET)
254 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
254 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
255
255
256 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
256 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
257 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
257 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
258 diff_limit = self.cut_off_limit if not fulldiff else None
258 diff_limit = self.cut_off_limit if not fulldiff else None
259 diff_processor = diffs.DiffProcessor(_diff,
259 diff_processor = diffs.DiffProcessor(_diff,
260 vcs=c.rhodecode_repo.alias,
260 vcs=c.rhodecode_repo.alias,
261 format='gitdiff',
261 format='gitdiff',
262 diff_limit=diff_limit)
262 diff_limit=diff_limit)
263 cs_changes = OrderedDict()
263 cs_changes = OrderedDict()
264 if method == 'show':
264 if method == 'show':
265 _parsed = diff_processor.prepare()
265 _parsed = diff_processor.prepare()
266 c.limited_diff = False
266 c.limited_diff = False
267 if isinstance(_parsed, LimitedDiffContainer):
267 if isinstance(_parsed, LimitedDiffContainer):
268 c.limited_diff = True
268 c.limited_diff = True
269 for f in _parsed:
269 for f in _parsed:
270 st = f['stats']
270 st = f['stats']
271 if st[0] != 'b':
271 if st[0] != 'b':
272 c.lines_added += st[0]
272 c.lines_added += st[0]
273 c.lines_deleted += st[1]
273 c.lines_deleted += st[1]
274 fid = h.FID(changeset.raw_id, f['filename'])
274 fid = h.FID(changeset.raw_id, f['filename'])
275 diff = diff_processor.as_html(enable_comments=enable_comments,
275 diff = diff_processor.as_html(enable_comments=enable_comments,
276 parsed_lines=[f])
276 parsed_lines=[f])
277 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
277 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
278 diff, st]
278 diff, st]
279 else:
279 else:
280 # downloads/raw we only need RAW diff nothing else
280 # downloads/raw we only need RAW diff nothing else
281 diff = diff_processor.as_raw()
281 diff = diff_processor.as_raw()
282 cs_changes[''] = [None, None, None, None, diff, None]
282 cs_changes[''] = [None, None, None, None, diff, None]
283 c.changes[changeset.raw_id] = cs_changes
283 c.changes[changeset.raw_id] = cs_changes
284
284
285 #sort comments by how they were generated
285 #sort comments by how they were generated
286 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
286 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
287
287
288 # count inline comments
288 # count inline comments
289 for __, lines in c.inline_comments:
289 for __, lines in c.inline_comments:
290 for comments in lines.values():
290 for comments in lines.values():
291 c.inline_cnt += len(comments)
291 c.inline_cnt += len(comments)
292
292
293 if len(c.cs_ranges) == 1:
293 if len(c.cs_ranges) == 1:
294 c.changeset = c.cs_ranges[0]
294 c.changeset = c.cs_ranges[0]
295 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
295 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
296 for x in c.changeset.parents])
296 for x in c.changeset.parents])
297 if method == 'download':
297 if method == 'download':
298 response.content_type = 'text/plain'
298 response.content_type = 'text/plain'
299 response.content_disposition = 'attachment; filename=%s.diff' \
299 response.content_disposition = 'attachment; filename=%s.diff' \
300 % revision[:12]
300 % revision[:12]
301 return diff
301 return diff
302 elif method == 'patch':
302 elif method == 'patch':
303 response.content_type = 'text/plain'
303 response.content_type = 'text/plain'
304 c.diff = safe_unicode(diff)
304 c.diff = safe_unicode(diff)
305 return render('changeset/patch_changeset.html')
305 return render('changeset/patch_changeset.html')
306 elif method == 'raw':
306 elif method == 'raw':
307 response.content_type = 'text/plain'
307 response.content_type = 'text/plain'
308 return diff
308 return diff
309 elif method == 'show':
309 elif method == 'show':
310 if len(c.cs_ranges) == 1:
310 if len(c.cs_ranges) == 1:
311 return render('changeset/changeset.html')
311 return render('changeset/changeset.html')
312 else:
312 else:
313 return render('changeset/changeset_range.html')
313 return render('changeset/changeset_range.html')
314
314
315 @LoginRequired()
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
317 'repository.admin')
315 def changeset_raw(self, revision):
318 def changeset_raw(self, revision):
316 return self.index(revision, method='raw')
319 return self.index(revision, method='raw')
317
320
321 @LoginRequired()
322 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
323 'repository.admin')
318 def changeset_patch(self, revision):
324 def changeset_patch(self, revision):
319 return self.index(revision, method='patch')
325 return self.index(revision, method='patch')
320
326
327 @LoginRequired()
328 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
329 'repository.admin')
321 def changeset_download(self, revision):
330 def changeset_download(self, revision):
322 return self.index(revision, method='download')
331 return self.index(revision, method='download')
323
332
333 @LoginRequired()
324 @NotAnonymous()
334 @NotAnonymous()
335 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
336 'repository.admin')
325 @jsonify
337 @jsonify
326 def comment(self, repo_name, revision):
338 def comment(self, repo_name, revision):
327 status = request.POST.get('changeset_status')
339 status = request.POST.get('changeset_status')
328 change_status = request.POST.get('change_changeset_status')
340 change_status = request.POST.get('change_changeset_status')
329 text = request.POST.get('text')
341 text = request.POST.get('text')
330 if status and change_status:
342 if status and change_status:
331 text = text or (_('Status change -> %s')
343 text = text or (_('Status change -> %s')
332 % ChangesetStatus.get_status_lbl(status))
344 % ChangesetStatus.get_status_lbl(status))
333
345
334 c.co = comm = ChangesetCommentsModel().create(
346 c.co = comm = ChangesetCommentsModel().create(
335 text=text,
347 text=text,
336 repo=c.rhodecode_db_repo.repo_id,
348 repo=c.rhodecode_db_repo.repo_id,
337 user=c.rhodecode_user.user_id,
349 user=c.rhodecode_user.user_id,
338 revision=revision,
350 revision=revision,
339 f_path=request.POST.get('f_path'),
351 f_path=request.POST.get('f_path'),
340 line_no=request.POST.get('line'),
352 line_no=request.POST.get('line'),
341 status_change=(ChangesetStatus.get_status_lbl(status)
353 status_change=(ChangesetStatus.get_status_lbl(status)
342 if status and change_status else None)
354 if status and change_status else None)
343 )
355 )
344
356
345 # get status if set !
357 # get status if set !
346 if status and change_status:
358 if status and change_status:
347 # if latest status was from pull request and it's closed
359 # if latest status was from pull request and it's closed
348 # disallow changing status !
360 # disallow changing status !
349 # dont_allow_on_closed_pull_request = True !
361 # dont_allow_on_closed_pull_request = True !
350
362
351 try:
363 try:
352 ChangesetStatusModel().set_status(
364 ChangesetStatusModel().set_status(
353 c.rhodecode_db_repo.repo_id,
365 c.rhodecode_db_repo.repo_id,
354 status,
366 status,
355 c.rhodecode_user.user_id,
367 c.rhodecode_user.user_id,
356 comm,
368 comm,
357 revision=revision,
369 revision=revision,
358 dont_allow_on_closed_pull_request=True
370 dont_allow_on_closed_pull_request=True
359 )
371 )
360 except StatusChangeOnClosedPullRequestError:
372 except StatusChangeOnClosedPullRequestError:
361 log.error(traceback.format_exc())
373 log.error(traceback.format_exc())
362 msg = _('Changing status on a changeset associated with '
374 msg = _('Changing status on a changeset associated with '
363 'a closed pull request is not allowed')
375 'a closed pull request is not allowed')
364 h.flash(msg, category='warning')
376 h.flash(msg, category='warning')
365 return redirect(h.url('changeset_home', repo_name=repo_name,
377 return redirect(h.url('changeset_home', repo_name=repo_name,
366 revision=revision))
378 revision=revision))
367 action_logger(self.rhodecode_user,
379 action_logger(self.rhodecode_user,
368 'user_commented_revision:%s' % revision,
380 'user_commented_revision:%s' % revision,
369 c.rhodecode_db_repo, self.ip_addr, self.sa)
381 c.rhodecode_db_repo, self.ip_addr, self.sa)
370
382
371 Session().commit()
383 Session().commit()
372
384
373 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
385 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
374 return redirect(h.url('changeset_home', repo_name=repo_name,
386 return redirect(h.url('changeset_home', repo_name=repo_name,
375 revision=revision))
387 revision=revision))
376 #only ajax below
388 #only ajax below
377 data = {
389 data = {
378 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
390 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
379 }
391 }
380 if comm:
392 if comm:
381 data.update(comm.get_dict())
393 data.update(comm.get_dict())
382 data.update({'rendered_text':
394 data.update({'rendered_text':
383 render('changeset/changeset_comment_block.html')})
395 render('changeset/changeset_comment_block.html')})
384
396
385 return data
397 return data
386
398
399 @LoginRequired()
387 @NotAnonymous()
400 @NotAnonymous()
401 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
402 'repository.admin')
388 def preview_comment(self):
403 def preview_comment(self):
389 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
390 raise HTTPBadRequest()
405 raise HTTPBadRequest()
391 text = request.POST.get('text')
406 text = request.POST.get('text')
392 if text:
407 if text:
393 return h.rst_w_mentions(text)
408 return h.rst_w_mentions(text)
394 return ''
409 return ''
395
410
411 @LoginRequired()
396 @NotAnonymous()
412 @NotAnonymous()
413 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
414 'repository.admin')
397 @jsonify
415 @jsonify
398 def delete_comment(self, repo_name, comment_id):
416 def delete_comment(self, repo_name, comment_id):
399 co = ChangesetComment.get(comment_id)
417 co = ChangesetComment.get(comment_id)
400 owner = co.author.user_id == c.rhodecode_user.user_id
418 owner = co.author.user_id == c.rhodecode_user.user_id
401 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
419 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
402 ChangesetCommentsModel().delete(comment=co)
420 ChangesetCommentsModel().delete(comment=co)
403 Session().commit()
421 Session().commit()
404 return True
422 return True
405 else:
423 else:
406 raise HTTPForbidden()
424 raise HTTPForbidden()
407
425
426 @LoginRequired()
427 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
428 'repository.admin')
408 @jsonify
429 @jsonify
409 def changeset_info(self, repo_name, revision):
430 def changeset_info(self, repo_name, revision):
410 if request.is_xhr:
431 if request.is_xhr:
411 try:
432 try:
412 return c.rhodecode_repo.get_changeset(revision)
433 return c.rhodecode_repo.get_changeset(revision)
413 except ChangesetDoesNotExistError, e:
434 except ChangesetDoesNotExistError, e:
414 return EmptyChangeset(message=str(e))
435 return EmptyChangeset(message=str(e))
415 else:
436 else:
416 raise HTTPBadRequest()
437 raise HTTPBadRequest()
@@ -1,269 +1,269 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.compare
3 rhodecode.controllers.compare
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 compare controller for pylons showing differences between two
6 compare controller for pylons showing differences between two
7 repos, branches, bookmarks or tips
7 repos, branches, bookmarks or tips
8
8
9 :created_on: May 6, 2012
9 :created_on: May 6, 2012
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
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import re
29 import re
30
30
31 from webob.exc import HTTPNotFound
31 from webob.exc import HTTPNotFound
32 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
36 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
37 from rhodecode.lib.vcs.utils import safe_str
37 from rhodecode.lib.vcs.utils import safe_str
38 from rhodecode.lib.vcs.utils.hgcompat import scmutil
38 from rhodecode.lib.vcs.utils.hgcompat import scmutil
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib import diffs, unionrepo
42 from rhodecode.lib import diffs, unionrepo
43
43
44 from rhodecode.model.db import Repository
44 from rhodecode.model.db import Repository
45 from webob.exc import HTTPBadRequest
45 from webob.exc import HTTPBadRequest
46 from rhodecode.lib.diffs import LimitedDiffContainer
46 from rhodecode.lib.diffs import LimitedDiffContainer
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class CompareController(BaseRepoController):
52 class CompareController(BaseRepoController):
53
53
54 @LoginRequired()
55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
56 'repository.admin')
57 def __before__(self):
54 def __before__(self):
58 super(CompareController, self).__before__()
55 super(CompareController, self).__before__()
59
56
60 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
61 partial=False):
58 partial=False):
62 """
59 """
63 Safe way to get changeset if error occur it redirects to changeset with
60 Safe way to get changeset if error occur it redirects to changeset with
64 proper message. If partial is set then don't do redirect raise Exception
61 proper message. If partial is set then don't do redirect raise Exception
65 instead
62 instead
66
63
67 :param rev: revision to fetch
64 :param rev: revision to fetch
68 :param repo: repo instance
65 :param repo: repo instance
69 """
66 """
70
67
71 try:
68 try:
72 type_, rev = rev
69 type_, rev = rev
73 return repo.scm_instance.get_changeset(rev)
70 return repo.scm_instance.get_changeset(rev)
74 except EmptyRepositoryError, e:
71 except EmptyRepositoryError, e:
75 if not redirect_after:
72 if not redirect_after:
76 return None
73 return None
77 h.flash(h.literal(_('There are no changesets yet')),
74 h.flash(h.literal(_('There are no changesets yet')),
78 category='warning')
75 category='warning')
79 redirect(url('summary_home', repo_name=repo.repo_name))
76 redirect(url('summary_home', repo_name=repo.repo_name))
80
77
81 except RepositoryError, e:
78 except RepositoryError, e:
82 log.error(traceback.format_exc())
79 log.error(traceback.format_exc())
83 h.flash(str(e), category='warning')
80 h.flash(str(e), category='warning')
84 if not partial:
81 if not partial:
85 redirect(h.url('summary_home', repo_name=repo.repo_name))
82 redirect(h.url('summary_home', repo_name=repo.repo_name))
86 raise HTTPBadRequest()
83 raise HTTPBadRequest()
87
84
85 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
86 """
87 Returns a list of changesets that can be merged from org_repo@org_ref
88 to other_repo@other_ref ... and the ancestor that would be used for merge
89
90 :param org_repo:
91 :param org_ref:
92 :param other_repo:
93 :param other_ref:
94 :param tmp:
95 """
96
97 ancestor = None
98
99 if alias == 'hg':
100 # lookup up the exact node id
101 _revset_predicates = {
102 'branch': 'branch',
103 'book': 'bookmark',
104 'tag': 'tag',
105 'rev': 'id',
106 }
107
108 org_rev_spec = "max(%s('%s'))" % (_revset_predicates[org_ref[0]],
109 safe_str(org_ref[1]))
110 org_revs = scmutil.revrange(org_repo._repo, [org_rev_spec])
111 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
112
113 other_rev_spec = "max(%s('%s'))" % (_revset_predicates[other_ref[0]],
114 safe_str(other_ref[1]))
115 other_revs = scmutil.revrange(other_repo._repo, [other_rev_spec])
116 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
117
118 #case two independent repos
119 if org_repo != other_repo:
120 hgrepo = unionrepo.unionrepository(other_repo.baseui,
121 other_repo.path,
122 org_repo.path)
123 # all the changesets we are looking for will be in other_repo,
124 # so rev numbers from hgrepo can be used in other_repo
125
126 #no remote compare do it on the same repository
127 else:
128 hgrepo = other_repo._repo
129
130 if merge:
131 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
132 (other_rev, org_rev, org_rev)]
133
134 ancestors = scmutil.revrange(hgrepo,
135 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
136 if ancestors:
137 # pick arbitrary ancestor - but there is usually only one
138 ancestor = hgrepo[ancestors[0]].hex()
139 else:
140 # TODO: have both + and - changesets
141 revs = ["id('%s') :: id('%s') - id('%s')" %
142 (org_rev, other_rev, org_rev)]
143
144 changesets = [other_repo.get_changeset(cs)
145 for cs in scmutil.revrange(hgrepo, revs)]
146
147 elif alias == 'git':
148 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
149 so, se = org_repo.run_git_command(
150 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
151 other_ref[1])
152 )
153 changesets = [org_repo.get_changeset(cs)
154 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
155
156 return changesets, ancestor
157
158 @LoginRequired()
159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
160 'repository.admin')
88 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
161 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
89 # org_ref will be evaluated in org_repo
162 # org_ref will be evaluated in org_repo
90 org_repo = c.rhodecode_db_repo.repo_name
163 org_repo = c.rhodecode_db_repo.repo_name
91 org_ref = (org_ref_type, org_ref)
164 org_ref = (org_ref_type, org_ref)
92 # other_ref will be evaluated in other_repo
165 # other_ref will be evaluated in other_repo
93 other_ref = (other_ref_type, other_ref)
166 other_ref = (other_ref_type, other_ref)
94 other_repo = request.GET.get('other_repo', org_repo)
167 other_repo = request.GET.get('other_repo', org_repo)
95 # If merge is True:
168 # If merge is True:
96 # Show what org would get if merged with other:
169 # Show what org would get if merged with other:
97 # List changesets that are ancestors of other but not of org.
170 # List changesets that are ancestors of other but not of org.
98 # New changesets in org is thus ignored.
171 # New changesets in org is thus ignored.
99 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
172 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
100 # If merge is False:
173 # If merge is False:
101 # Make a raw diff from org to other, no matter if related or not.
174 # Make a raw diff from org to other, no matter if related or not.
102 # Changesets in one and not in the other will be ignored
175 # Changesets in one and not in the other will be ignored
103 merge = bool(request.GET.get('merge'))
176 merge = bool(request.GET.get('merge'))
104 # fulldiff disables cut_off_limit
177 # fulldiff disables cut_off_limit
105 c.fulldiff = request.GET.get('fulldiff')
178 c.fulldiff = request.GET.get('fulldiff')
106 # partial uses compare_cs.html template directly
179 # partial uses compare_cs.html template directly
107 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
180 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
108 # as_form puts hidden input field with changeset revisions
181 # as_form puts hidden input field with changeset revisions
109 c.as_form = partial and request.GET.get('as_form')
182 c.as_form = partial and request.GET.get('as_form')
110 # swap url for compare_diff page - never partial and never as_form
183 # swap url for compare_diff page - never partial and never as_form
111 c.swap_url = h.url('compare_url',
184 c.swap_url = h.url('compare_url',
112 repo_name=other_repo,
185 repo_name=other_repo,
113 org_ref_type=other_ref[0], org_ref=other_ref[1],
186 org_ref_type=other_ref[0], org_ref=other_ref[1],
114 other_repo=org_repo,
187 other_repo=org_repo,
115 other_ref_type=org_ref[0], other_ref=org_ref[1],
188 other_ref_type=org_ref[0], other_ref=org_ref[1],
116 merge=merge or '')
189 merge=merge or '')
117
190
118 org_repo = Repository.get_by_repo_name(org_repo)
191 org_repo = Repository.get_by_repo_name(org_repo)
119 other_repo = Repository.get_by_repo_name(other_repo)
192 other_repo = Repository.get_by_repo_name(other_repo)
120
193
121 if org_repo is None:
194 if org_repo is None:
122 log.error('Could not find org repo %s' % org_repo)
195 log.error('Could not find org repo %s' % org_repo)
123 raise HTTPNotFound
196 raise HTTPNotFound
124 if other_repo is None:
197 if other_repo is None:
125 log.error('Could not find other repo %s' % other_repo)
198 log.error('Could not find other repo %s' % other_repo)
126 raise HTTPNotFound
199 raise HTTPNotFound
127
200
128 if org_repo != other_repo and h.is_git(org_repo):
201 if org_repo != other_repo and h.is_git(org_repo):
129 log.error('compare of two remote repos not available for GIT REPOS')
202 log.error('compare of two remote repos not available for GIT REPOS')
130 raise HTTPNotFound
203 raise HTTPNotFound
131
204
132 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
205 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
133 log.error('compare of two different kind of remote repos not available')
206 log.error('compare of two different kind of remote repos not available')
134 raise HTTPNotFound
207 raise HTTPNotFound
135
208
136 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
209 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
137 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
210 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
138
211
139 c.org_repo = org_repo
212 c.org_repo = org_repo
140 c.other_repo = other_repo
213 c.other_repo = other_repo
141 c.org_ref = org_ref[1]
214 c.org_ref = org_ref[1]
142 c.other_ref = other_ref[1]
215 c.other_ref = other_ref[1]
143 c.org_ref_type = org_ref[0]
216 c.org_ref_type = org_ref[0]
144 c.other_ref_type = other_ref[0]
217 c.other_ref_type = other_ref[0]
145
218
146 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
219 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
147 org_repo.scm_instance, org_ref,
220 org_repo.scm_instance, org_ref,
148 other_repo.scm_instance, other_ref,
221 other_repo.scm_instance, other_ref,
149 merge)
222 merge)
150
223
151 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
224 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
152 c.cs_ranges])
225 c.cs_ranges])
153 if partial:
226 if partial:
154 assert c.ancestor
227 assert c.ancestor
155 return render('compare/compare_cs.html')
228 return render('compare/compare_cs.html')
156
229
157 if c.ancestor:
230 if c.ancestor:
158 assert merge
231 assert merge
159 # case we want a simple diff without incoming changesets,
232 # case we want a simple diff without incoming changesets,
160 # previewing what will be merged.
233 # previewing what will be merged.
161 # Make the diff on the other repo (which is known to have other_ref)
234 # Make the diff on the other repo (which is known to have other_ref)
162 log.debug('Using ancestor %s as org_ref instead of %s'
235 log.debug('Using ancestor %s as org_ref instead of %s'
163 % (c.ancestor, org_ref))
236 % (c.ancestor, org_ref))
164 org_ref = ('rev', c.ancestor)
237 org_ref = ('rev', c.ancestor)
165 org_repo = other_repo
238 org_repo = other_repo
166
239
167 diff_limit = self.cut_off_limit if not c.fulldiff else None
240 diff_limit = self.cut_off_limit if not c.fulldiff else None
168
241
169 log.debug('running diff between %s@%s and %s@%s'
242 log.debug('running diff between %s@%s and %s@%s'
170 % (org_repo.scm_instance.path, org_ref,
243 % (org_repo.scm_instance.path, org_ref,
171 other_repo.scm_instance.path, other_ref))
244 other_repo.scm_instance.path, other_ref))
172 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
245 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
173
246
174 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
247 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
175 diff_limit=diff_limit)
248 diff_limit=diff_limit)
176 _parsed = diff_processor.prepare()
249 _parsed = diff_processor.prepare()
177
250
178 c.limited_diff = False
251 c.limited_diff = False
179 if isinstance(_parsed, LimitedDiffContainer):
252 if isinstance(_parsed, LimitedDiffContainer):
180 c.limited_diff = True
253 c.limited_diff = True
181
254
182 c.files = []
255 c.files = []
183 c.changes = {}
256 c.changes = {}
184 c.lines_added = 0
257 c.lines_added = 0
185 c.lines_deleted = 0
258 c.lines_deleted = 0
186 for f in _parsed:
259 for f in _parsed:
187 st = f['stats']
260 st = f['stats']
188 if st[0] != 'b':
261 if st[0] != 'b':
189 c.lines_added += st[0]
262 c.lines_added += st[0]
190 c.lines_deleted += st[1]
263 c.lines_deleted += st[1]
191 fid = h.FID('', f['filename'])
264 fid = h.FID('', f['filename'])
192 c.files.append([fid, f['operation'], f['filename'], f['stats']])
265 c.files.append([fid, f['operation'], f['filename'], f['stats']])
193 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
266 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
194 c.changes[fid] = [f['operation'], f['filename'], diff]
267 c.changes[fid] = [f['operation'], f['filename'], diff]
195
268
196 return render('compare/compare_diff.html')
269 return render('compare/compare_diff.html')
197
198 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
199 """
200 Returns a list of changesets that can be merged from org_repo@org_ref
201 to other_repo@other_ref ... and the ancestor that would be used for merge
202
203 :param org_repo:
204 :param org_ref:
205 :param other_repo:
206 :param other_ref:
207 :param tmp:
208 """
209
210 ancestor = None
211
212 if alias == 'hg':
213 # lookup up the exact node id
214 _revset_predicates = {
215 'branch': 'branch',
216 'book': 'bookmark',
217 'tag': 'tag',
218 'rev': 'id',
219 }
220
221 org_rev_spec = "max(%s('%s'))" % (_revset_predicates[org_ref[0]],
222 safe_str(org_ref[1]))
223 org_revs = scmutil.revrange(org_repo._repo, [org_rev_spec])
224 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
225
226 other_rev_spec = "max(%s('%s'))" % (_revset_predicates[other_ref[0]],
227 safe_str(other_ref[1]))
228 other_revs = scmutil.revrange(other_repo._repo, [other_rev_spec])
229 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
230
231 #case two independent repos
232 if org_repo != other_repo:
233 hgrepo = unionrepo.unionrepository(other_repo.baseui,
234 other_repo.path,
235 org_repo.path)
236 # all the changesets we are looking for will be in other_repo,
237 # so rev numbers from hgrepo can be used in other_repo
238
239 #no remote compare do it on the same repository
240 else:
241 hgrepo = other_repo._repo
242
243 if merge:
244 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
245 (other_rev, org_rev, org_rev)]
246
247 ancestors = scmutil.revrange(hgrepo,
248 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
249 if ancestors:
250 # pick arbitrary ancestor - but there is usually only one
251 ancestor = hgrepo[ancestors[0]].hex()
252 else:
253 # TODO: have both + and - changesets
254 revs = ["id('%s') :: id('%s') - id('%s')" %
255 (org_rev, other_rev, org_rev)]
256
257 changesets = [other_repo.get_changeset(cs)
258 for cs in scmutil.revrange(hgrepo, revs)]
259
260 elif alias == 'git':
261 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
262 so, se = org_repo.run_git_command(
263 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
264 other_ref[1])
265 )
266 changesets = [org_repo.get_changeset(cs)
267 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
268
269 return changesets, ancestor
@@ -1,647 +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 from webob.exc import HTTPNotFound
59 from webob.exc import HTTPNotFound
60
60
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 class FilesController(BaseRepoController):
65 class FilesController(BaseRepoController):
66
66
67 def __before__(self):
67 def __before__(self):
68 super(FilesController, self).__before__()
68 super(FilesController, self).__before__()
69 c.cut_off_limit = self.cut_off_limit
69 c.cut_off_limit = self.cut_off_limit
70
70
71 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
71 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
72 """
72 """
73 Safe way to get changeset if error occur it redirects to tip with
73 Safe way to get changeset if error occur it redirects to tip with
74 proper message
74 proper message
75
75
76 :param rev: revision to fetch
76 :param rev: revision to fetch
77 :param repo_name: repo name to redirect after
77 :param repo_name: repo name to redirect after
78 """
78 """
79
79
80 try:
80 try:
81 return c.rhodecode_repo.get_changeset(rev)
81 return c.rhodecode_repo.get_changeset(rev)
82 except EmptyRepositoryError, e:
82 except EmptyRepositoryError, e:
83 if not redirect_after:
83 if not redirect_after:
84 return None
84 return None
85 url_ = url('files_add_home',
85 url_ = url('files_add_home',
86 repo_name=c.repo_name,
86 repo_name=c.repo_name,
87 revision=0, f_path='')
87 revision=0, f_path='')
88 add_new = h.link_to(_('Click here to add new file'), url_)
88 add_new = h.link_to(_('Click here to add new file'), url_)
89 h.flash(h.literal(_('There are no files yet %s') % add_new),
89 h.flash(h.literal(_('There are no files yet %s') % add_new),
90 category='warning')
90 category='warning')
91 redirect(h.url('summary_home', repo_name=repo_name))
91 redirect(h.url('summary_home', repo_name=repo_name))
92
92
93 except RepositoryError, e: # including ChangesetDoesNotExistError
93 except RepositoryError, e: # including ChangesetDoesNotExistError
94 h.flash(str(e), category='error')
94 h.flash(str(e), category='error')
95 raise HTTPNotFound()
95 raise HTTPNotFound()
96
96
97 def __get_filenode_or_redirect(self, repo_name, cs, path):
97 def __get_filenode_or_redirect(self, repo_name, cs, path):
98 """
98 """
99 Returns file_node, if error occurs or given path is directory,
99 Returns file_node, if error occurs or given path is directory,
100 it'll redirect to top level path
100 it'll redirect to top level path
101
101
102 :param repo_name: repo_name
102 :param repo_name: repo_name
103 :param cs: given changeset
103 :param cs: given changeset
104 :param path: path to lookup
104 :param path: path to lookup
105 """
105 """
106
106
107 try:
107 try:
108 file_node = cs.get_node(path)
108 file_node = cs.get_node(path)
109 if file_node.is_dir():
109 if file_node.is_dir():
110 raise RepositoryError('given path is a directory')
110 raise RepositoryError('given path is a directory')
111 except RepositoryError, e:
111 except RepositoryError, e:
112 h.flash(str(e), category='error')
112 h.flash(str(e), category='error')
113 raise HTTPNotFound()
113 raise HTTPNotFound()
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
125
126 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
126 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 c.branch = request.GET.get('branch', None)
127 c.branch = request.GET.get('branch', None)
128 c.f_path = f_path
128 c.f_path = f_path
129 c.annotate = annotate
129 c.annotate = annotate
130 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
130 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 cur_rev = c.changeset.revision
131 cur_rev = c.changeset.revision
132
132
133 # prev link
133 # prev link
134 try:
134 try:
135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 c.url_prev = url('files_home', repo_name=c.repo_name,
136 c.url_prev = url('files_home', repo_name=c.repo_name,
137 revision=prev_rev.raw_id, f_path=f_path)
137 revision=prev_rev.raw_id, f_path=f_path)
138 if c.branch:
138 if c.branch:
139 c.url_prev += '?branch=%s' % c.branch
139 c.url_prev += '?branch=%s' % c.branch
140 except (ChangesetDoesNotExistError, VCSError):
140 except (ChangesetDoesNotExistError, VCSError):
141 c.url_prev = '#'
141 c.url_prev = '#'
142
142
143 # next link
143 # next link
144 try:
144 try:
145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 c.url_next = url('files_home', repo_name=c.repo_name,
146 c.url_next = url('files_home', repo_name=c.repo_name,
147 revision=next_rev.raw_id, f_path=f_path)
147 revision=next_rev.raw_id, f_path=f_path)
148 if c.branch:
148 if c.branch:
149 c.url_next += '?branch=%s' % c.branch
149 c.url_next += '?branch=%s' % c.branch
150 except (ChangesetDoesNotExistError, VCSError):
150 except (ChangesetDoesNotExistError, VCSError):
151 c.url_next = '#'
151 c.url_next = '#'
152
152
153 # files or dirs
153 # files or dirs
154 try:
154 try:
155 c.file = c.changeset.get_node(f_path)
155 c.file = c.changeset.get_node(f_path)
156
156
157 if c.file.is_file():
157 if c.file.is_file():
158 c.load_full_history = False
158 c.load_full_history = False
159 file_last_cs = c.file.last_changeset
159 file_last_cs = c.file.last_changeset
160 c.file_changeset = (c.changeset
160 c.file_changeset = (c.changeset
161 if c.changeset.revision < file_last_cs.revision
161 if c.changeset.revision < file_last_cs.revision
162 else file_last_cs)
162 else file_last_cs)
163 #determine if we're on branch head
163 #determine if we're on branch head
164 _branches = c.rhodecode_repo.branches
164 _branches = c.rhodecode_repo.branches
165 c.on_branch_head = revision in _branches.keys() + _branches.values()
165 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 _hist = []
166 _hist = []
167 c.file_history = []
167 c.file_history = []
168 if c.load_full_history:
168 if c.load_full_history:
169 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
169 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170
170
171 c.authors = []
171 c.authors = []
172 for a in set([x.author for x in _hist]):
172 for a in set([x.author for x in _hist]):
173 c.authors.append((h.email(a), h.person(a)))
173 c.authors.append((h.email(a), h.person(a)))
174 else:
174 else:
175 c.authors = c.file_history = []
175 c.authors = c.file_history = []
176 except RepositoryError, e:
176 except RepositoryError, e:
177 h.flash(str(e), category='error')
177 h.flash(str(e), category='error')
178 raise HTTPNotFound()
178 raise HTTPNotFound()
179
179
180 if request.environ.get('HTTP_X_PARTIAL_XHR'):
180 if request.environ.get('HTTP_X_PARTIAL_XHR'):
181 return render('files/files_ypjax.html')
181 return render('files/files_ypjax.html')
182
182
183 return render('files/files.html')
183 return render('files/files.html')
184
184
185 @LoginRequired()
186 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
187 'repository.admin')
185 def history(self, repo_name, revision, f_path, annotate=False):
188 def history(self, repo_name, revision, f_path, annotate=False):
186 if request.environ.get('HTTP_X_PARTIAL_XHR'):
189 if request.environ.get('HTTP_X_PARTIAL_XHR'):
187 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
190 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
188 c.f_path = f_path
191 c.f_path = f_path
189 c.annotate = annotate
192 c.annotate = annotate
190 c.file = c.changeset.get_node(f_path)
193 c.file = c.changeset.get_node(f_path)
191 if c.file.is_file():
194 if c.file.is_file():
192 file_last_cs = c.file.last_changeset
195 file_last_cs = c.file.last_changeset
193 c.file_changeset = (c.changeset
196 c.file_changeset = (c.changeset
194 if c.changeset.revision < file_last_cs.revision
197 if c.changeset.revision < file_last_cs.revision
195 else file_last_cs)
198 else file_last_cs)
196 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)
197 c.authors = []
200 c.authors = []
198 for a in set([x.author for x in _hist]):
201 for a in set([x.author for x in _hist]):
199 c.authors.append((h.email(a), h.person(a)))
202 c.authors.append((h.email(a), h.person(a)))
200 return render('files/files_history_box.html')
203 return render('files/files_history_box.html')
201
204
202 @LoginRequired()
205 @LoginRequired()
203 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
206 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
204 'repository.admin')
207 'repository.admin')
205 def rawfile(self, repo_name, revision, f_path):
208 def rawfile(self, repo_name, revision, f_path):
206 cs = self.__get_cs_or_redirect(revision, repo_name)
209 cs = self.__get_cs_or_redirect(revision, repo_name)
207 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)
208
211
209 response.content_disposition = 'attachment; filename=%s' % \
212 response.content_disposition = 'attachment; filename=%s' % \
210 safe_str(f_path.split(Repository.url_sep())[-1])
213 safe_str(f_path.split(Repository.url_sep())[-1])
211
214
212 response.content_type = file_node.mimetype
215 response.content_type = file_node.mimetype
213 return file_node.content
216 return file_node.content
214
217
215 @LoginRequired()
218 @LoginRequired()
216 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
219 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
217 'repository.admin')
220 'repository.admin')
218 def raw(self, repo_name, revision, f_path):
221 def raw(self, repo_name, revision, f_path):
219 cs = self.__get_cs_or_redirect(revision, repo_name)
222 cs = self.__get_cs_or_redirect(revision, repo_name)
220 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)
221
224
222 raw_mimetype_mapping = {
225 raw_mimetype_mapping = {
223 # map original mimetype to a mimetype used for "show as raw"
226 # map original mimetype to a mimetype used for "show as raw"
224 # you can also provide a content-disposition to override the
227 # you can also provide a content-disposition to override the
225 # default "attachment" disposition.
228 # default "attachment" disposition.
226 # orig_type: (new_type, new_dispo)
229 # orig_type: (new_type, new_dispo)
227
230
228 # show images inline:
231 # show images inline:
229 'image/x-icon': ('image/x-icon', 'inline'),
232 'image/x-icon': ('image/x-icon', 'inline'),
230 'image/png': ('image/png', 'inline'),
233 'image/png': ('image/png', 'inline'),
231 'image/gif': ('image/gif', 'inline'),
234 'image/gif': ('image/gif', 'inline'),
232 'image/jpeg': ('image/jpeg', 'inline'),
235 'image/jpeg': ('image/jpeg', 'inline'),
233 'image/svg+xml': ('image/svg+xml', 'inline'),
236 'image/svg+xml': ('image/svg+xml', 'inline'),
234 }
237 }
235
238
236 mimetype = file_node.mimetype
239 mimetype = file_node.mimetype
237 try:
240 try:
238 mimetype, dispo = raw_mimetype_mapping[mimetype]
241 mimetype, dispo = raw_mimetype_mapping[mimetype]
239 except KeyError:
242 except KeyError:
240 # we don't know anything special about this, handle it safely
243 # we don't know anything special about this, handle it safely
241 if file_node.is_binary:
244 if file_node.is_binary:
242 # do same as download raw for binary files
245 # do same as download raw for binary files
243 mimetype, dispo = 'application/octet-stream', 'attachment'
246 mimetype, dispo = 'application/octet-stream', 'attachment'
244 else:
247 else:
245 # do not just use the original mimetype, but force text/plain,
248 # do not just use the original mimetype, but force text/plain,
246 # otherwise it would serve text/html and that might be unsafe.
249 # otherwise it would serve text/html and that might be unsafe.
247 # Note: underlying vcs library fakes text/plain mimetype if the
250 # Note: underlying vcs library fakes text/plain mimetype if the
248 # mimetype can not be determined and it thinks it is not
251 # mimetype can not be determined and it thinks it is not
249 # binary.This might lead to erroneous text display in some
252 # binary.This might lead to erroneous text display in some
250 # cases, but helps in other cases, like with text files
253 # cases, but helps in other cases, like with text files
251 # without extension.
254 # without extension.
252 mimetype, dispo = 'text/plain', 'inline'
255 mimetype, dispo = 'text/plain', 'inline'
253
256
254 if dispo == 'attachment':
257 if dispo == 'attachment':
255 dispo = 'attachment; filename=%s' % \
258 dispo = 'attachment; filename=%s' % \
256 safe_str(f_path.split(os.sep)[-1])
259 safe_str(f_path.split(os.sep)[-1])
257
260
258 response.content_disposition = dispo
261 response.content_disposition = dispo
259 response.content_type = mimetype
262 response.content_type = mimetype
260 return file_node.content
263 return file_node.content
261
264
262 @LoginRequired()
265 @LoginRequired()
263 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
264 def edit(self, repo_name, revision, f_path):
267 def edit(self, repo_name, revision, f_path):
265 repo = c.rhodecode_db_repo
268 repo = c.rhodecode_db_repo
266 if repo.enable_locking and repo.locked[0]:
269 if repo.enable_locking and repo.locked[0]:
267 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')
268 % (h.person_by_id(repo.locked[0]),
271 % (h.person_by_id(repo.locked[0]),
269 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
272 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
270 'warning')
273 'warning')
271 return redirect(h.url('files_home',
274 return redirect(h.url('files_home',
272 repo_name=repo_name, revision='tip'))
275 repo_name=repo_name, revision='tip'))
273
276
274 # check if revision is a branch identifier- basically we cannot
277 # check if revision is a branch identifier- basically we cannot
275 # create multiple heads via file editing
278 # create multiple heads via file editing
276 _branches = repo.scm_instance.branches
279 _branches = repo.scm_instance.branches
277 # check if revision is a branch name or branch hash
280 # check if revision is a branch name or branch hash
278 if revision not in _branches.keys() + _branches.values():
281 if revision not in _branches.keys() + _branches.values():
279 h.flash(_('You can only edit files with revision '
282 h.flash(_('You can only edit files with revision '
280 'being a valid branch '), category='warning')
283 'being a valid branch '), category='warning')
281 return redirect(h.url('files_home',
284 return redirect(h.url('files_home',
282 repo_name=repo_name, revision='tip',
285 repo_name=repo_name, revision='tip',
283 f_path=f_path))
286 f_path=f_path))
284
287
285 r_post = request.POST
288 r_post = request.POST
286
289
287 c.cs = self.__get_cs_or_redirect(revision, repo_name)
290 c.cs = self.__get_cs_or_redirect(revision, repo_name)
288 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)
289
292
290 if c.file.is_binary:
293 if c.file.is_binary:
291 return redirect(url('files_home', repo_name=c.repo_name,
294 return redirect(url('files_home', repo_name=c.repo_name,
292 revision=c.cs.raw_id, f_path=f_path))
295 revision=c.cs.raw_id, f_path=f_path))
293 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
296 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
294 c.f_path = f_path
297 c.f_path = f_path
295
298
296 if r_post:
299 if r_post:
297
300
298 old_content = c.file.content
301 old_content = c.file.content
299 sl = old_content.splitlines(1)
302 sl = old_content.splitlines(1)
300 first_line = sl[0] if sl else ''
303 first_line = sl[0] if sl else ''
301 # modes: 0 - Unix, 1 - Mac, 2 - DOS
304 # modes: 0 - Unix, 1 - Mac, 2 - DOS
302 mode = detect_mode(first_line, 0)
305 mode = detect_mode(first_line, 0)
303 content = convert_line_endings(r_post.get('content'), mode)
306 content = convert_line_endings(r_post.get('content'), mode)
304
307
305 message = r_post.get('message') or c.default_message
308 message = r_post.get('message') or c.default_message
306 author = self.rhodecode_user.full_contact
309 author = self.rhodecode_user.full_contact
307
310
308 if content == old_content:
311 if content == old_content:
309 h.flash(_('No changes'), category='warning')
312 h.flash(_('No changes'), category='warning')
310 return redirect(url('changeset_home', repo_name=c.repo_name,
313 return redirect(url('changeset_home', repo_name=c.repo_name,
311 revision='tip'))
314 revision='tip'))
312 try:
315 try:
313 self.scm_model.commit_change(repo=c.rhodecode_repo,
316 self.scm_model.commit_change(repo=c.rhodecode_repo,
314 repo_name=repo_name, cs=c.cs,
317 repo_name=repo_name, cs=c.cs,
315 user=self.rhodecode_user.user_id,
318 user=self.rhodecode_user.user_id,
316 author=author, message=message,
319 author=author, message=message,
317 content=content, f_path=f_path)
320 content=content, f_path=f_path)
318 h.flash(_('Successfully committed to %s') % f_path,
321 h.flash(_('Successfully committed to %s') % f_path,
319 category='success')
322 category='success')
320
323
321 except Exception:
324 except Exception:
322 log.error(traceback.format_exc())
325 log.error(traceback.format_exc())
323 h.flash(_('Error occurred during commit'), category='error')
326 h.flash(_('Error occurred during commit'), category='error')
324 return redirect(url('changeset_home',
327 return redirect(url('changeset_home',
325 repo_name=c.repo_name, revision='tip'))
328 repo_name=c.repo_name, revision='tip'))
326
329
327 return render('files/files_edit.html')
330 return render('files/files_edit.html')
328
331
329 @LoginRequired()
332 @LoginRequired()
330 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
331 def add(self, repo_name, revision, f_path):
334 def add(self, repo_name, revision, f_path):
332
335
333 repo = Repository.get_by_repo_name(repo_name)
336 repo = Repository.get_by_repo_name(repo_name)
334 if repo.enable_locking and repo.locked[0]:
337 if repo.enable_locking and repo.locked[0]:
335 h.flash(_('This repository is has been locked by %s on %s')
338 h.flash(_('This repository is has been locked by %s on %s')
336 % (h.person_by_id(repo.locked[0]),
339 % (h.person_by_id(repo.locked[0]),
337 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
340 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
338 'warning')
341 'warning')
339 return redirect(h.url('files_home',
342 return redirect(h.url('files_home',
340 repo_name=repo_name, revision='tip'))
343 repo_name=repo_name, revision='tip'))
341
344
342 r_post = request.POST
345 r_post = request.POST
343 c.cs = self.__get_cs_or_redirect(revision, repo_name,
346 c.cs = self.__get_cs_or_redirect(revision, repo_name,
344 redirect_after=False)
347 redirect_after=False)
345 if c.cs is None:
348 if c.cs is None:
346 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
349 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
347 c.default_message = (_('Added file via RhodeCode'))
350 c.default_message = (_('Added file via RhodeCode'))
348 c.f_path = f_path
351 c.f_path = f_path
349
352
350 if r_post:
353 if r_post:
351 unix_mode = 0
354 unix_mode = 0
352 content = convert_line_endings(r_post.get('content'), unix_mode)
355 content = convert_line_endings(r_post.get('content'), unix_mode)
353
356
354 message = r_post.get('message') or c.default_message
357 message = r_post.get('message') or c.default_message
355 filename = r_post.get('filename')
358 filename = r_post.get('filename')
356 location = r_post.get('location')
359 location = r_post.get('location')
357 file_obj = r_post.get('upload_file', None)
360 file_obj = r_post.get('upload_file', None)
358
361
359 if file_obj is not None and hasattr(file_obj, 'filename'):
362 if file_obj is not None and hasattr(file_obj, 'filename'):
360 filename = file_obj.filename
363 filename = file_obj.filename
361 content = file_obj.file
364 content = file_obj.file
362
365
363 if not content:
366 if not content:
364 h.flash(_('No content'), category='warning')
367 h.flash(_('No content'), category='warning')
365 return redirect(url('changeset_home', repo_name=c.repo_name,
368 return redirect(url('changeset_home', repo_name=c.repo_name,
366 revision='tip'))
369 revision='tip'))
367 if not filename:
370 if not filename:
368 h.flash(_('No filename'), category='warning')
371 h.flash(_('No filename'), category='warning')
369 return redirect(url('changeset_home', repo_name=c.repo_name,
372 return redirect(url('changeset_home', repo_name=c.repo_name,
370 revision='tip'))
373 revision='tip'))
371 if location.startswith('/') or location.startswith('.') or '../' in location:
374 if location.startswith('/') or location.startswith('.') or '../' in location:
372 h.flash(_('Location must be relative path and must not '
375 h.flash(_('Location must be relative path and must not '
373 'contain .. in path'), category='warning')
376 'contain .. in path'), category='warning')
374 return redirect(url('changeset_home', repo_name=c.repo_name,
377 return redirect(url('changeset_home', repo_name=c.repo_name,
375 revision='tip'))
378 revision='tip'))
376 if location:
379 if location:
377 location = os.path.normpath(location)
380 location = os.path.normpath(location)
378 filename = os.path.basename(filename)
381 filename = os.path.basename(filename)
379 node_path = os.path.join(location, filename)
382 node_path = os.path.join(location, filename)
380 author = self.rhodecode_user.full_contact
383 author = self.rhodecode_user.full_contact
381
384
382 try:
385 try:
383 self.scm_model.create_node(repo=c.rhodecode_repo,
386 self.scm_model.create_node(repo=c.rhodecode_repo,
384 repo_name=repo_name, cs=c.cs,
387 repo_name=repo_name, cs=c.cs,
385 user=self.rhodecode_user.user_id,
388 user=self.rhodecode_user.user_id,
386 author=author, message=message,
389 author=author, message=message,
387 content=content, f_path=node_path)
390 content=content, f_path=node_path)
388 h.flash(_('Successfully committed to %s') % node_path,
391 h.flash(_('Successfully committed to %s') % node_path,
389 category='success')
392 category='success')
390 except (NodeError, NodeAlreadyExistsError), e:
393 except (NodeError, NodeAlreadyExistsError), e:
391 h.flash(_(e), category='error')
394 h.flash(_(e), category='error')
392 except Exception:
395 except Exception:
393 log.error(traceback.format_exc())
396 log.error(traceback.format_exc())
394 h.flash(_('Error occurred during commit'), category='error')
397 h.flash(_('Error occurred during commit'), category='error')
395 return redirect(url('changeset_home',
398 return redirect(url('changeset_home',
396 repo_name=c.repo_name, revision='tip'))
399 repo_name=c.repo_name, revision='tip'))
397
400
398 return render('files/files_add.html')
401 return render('files/files_add.html')
399
402
400 @LoginRequired()
403 @LoginRequired()
401 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
402 'repository.admin')
405 'repository.admin')
403 def archivefile(self, repo_name, fname):
406 def archivefile(self, repo_name, fname):
404
407
405 fileformat = None
408 fileformat = None
406 revision = None
409 revision = None
407 ext = None
410 ext = None
408 subrepos = request.GET.get('subrepos') == 'true'
411 subrepos = request.GET.get('subrepos') == 'true'
409
412
410 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
413 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
411 archive_spec = fname.split(ext_data[1])
414 archive_spec = fname.split(ext_data[1])
412 if len(archive_spec) == 2 and archive_spec[1] == '':
415 if len(archive_spec) == 2 and archive_spec[1] == '':
413 fileformat = a_type or ext_data[1]
416 fileformat = a_type or ext_data[1]
414 revision = archive_spec[0]
417 revision = archive_spec[0]
415 ext = ext_data[1]
418 ext = ext_data[1]
416
419
417 try:
420 try:
418 dbrepo = RepoModel().get_by_repo_name(repo_name)
421 dbrepo = RepoModel().get_by_repo_name(repo_name)
419 if not dbrepo.enable_downloads:
422 if not dbrepo.enable_downloads:
420 return _('Downloads disabled')
423 return _('Downloads disabled')
421
424
422 if c.rhodecode_repo.alias == 'hg':
425 if c.rhodecode_repo.alias == 'hg':
423 # 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
424 # hooks on fetching archives with subrepos
427 # hooks on fetching archives with subrepos
425 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
428 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
426 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
429 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
427
430
428 cs = c.rhodecode_repo.get_changeset(revision)
431 cs = c.rhodecode_repo.get_changeset(revision)
429 content_type = settings.ARCHIVE_SPECS[fileformat][0]
432 content_type = settings.ARCHIVE_SPECS[fileformat][0]
430 except ChangesetDoesNotExistError:
433 except ChangesetDoesNotExistError:
431 return _('Unknown revision %s') % revision
434 return _('Unknown revision %s') % revision
432 except EmptyRepositoryError:
435 except EmptyRepositoryError:
433 return _('Empty repository')
436 return _('Empty repository')
434 except (ImproperArchiveTypeError, KeyError):
437 except (ImproperArchiveTypeError, KeyError):
435 return _('Unknown archive type')
438 return _('Unknown archive type')
436 # archive cache
439 # archive cache
437 from rhodecode import CONFIG
440 from rhodecode import CONFIG
438 rev_name = cs.raw_id[:12]
441 rev_name = cs.raw_id[:12]
439 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
442 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
440 safe_str(rev_name), ext)
443 safe_str(rev_name), ext)
441
444
442 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
443 archive_cache_enabled = CONFIG.get('archive_cache_dir')
446 archive_cache_enabled = CONFIG.get('archive_cache_dir')
444 if not subrepos and archive_cache_enabled:
447 if not subrepos and archive_cache_enabled:
445 #check if we it's ok to write
448 #check if we it's ok to write
446 if not os.path.isdir(CONFIG['archive_cache_dir']):
449 if not os.path.isdir(CONFIG['archive_cache_dir']):
447 os.makedirs(CONFIG['archive_cache_dir'])
450 os.makedirs(CONFIG['archive_cache_dir'])
448 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)
449 if os.path.isfile(cached_archive_path):
452 if os.path.isfile(cached_archive_path):
450 log.debug('Found cached archive in %s' % cached_archive_path)
453 log.debug('Found cached archive in %s' % cached_archive_path)
451 fd, archive = None, cached_archive_path
454 fd, archive = None, cached_archive_path
452 use_cached_archive = True
455 use_cached_archive = True
453 else:
456 else:
454 log.debug('Archive %s is not yet cached' % (archive_name))
457 log.debug('Archive %s is not yet cached' % (archive_name))
455
458
456 if not use_cached_archive:
459 if not use_cached_archive:
457 #generate new archive
460 #generate new archive
458 try:
461 try:
459 fd, archive = tempfile.mkstemp()
462 fd, archive = tempfile.mkstemp()
460 t = open(archive, 'wb')
463 t = open(archive, 'wb')
461 log.debug('Creating new temp archive in %s' % archive)
464 log.debug('Creating new temp archive in %s' % archive)
462 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
465 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
463 if archive_cache_enabled:
466 if archive_cache_enabled:
464 #if we generated the archive and use cache rename that
467 #if we generated the archive and use cache rename that
465 log.debug('Storing new archive in %s' % cached_archive_path)
468 log.debug('Storing new archive in %s' % cached_archive_path)
466 shutil.move(archive, cached_archive_path)
469 shutil.move(archive, cached_archive_path)
467 archive = cached_archive_path
470 archive = cached_archive_path
468 finally:
471 finally:
469 t.close()
472 t.close()
470
473
471 def get_chunked_archive(archive):
474 def get_chunked_archive(archive):
472 stream = open(archive, 'rb')
475 stream = open(archive, 'rb')
473 while True:
476 while True:
474 data = stream.read(16 * 1024)
477 data = stream.read(16 * 1024)
475 if not data:
478 if not data:
476 stream.close()
479 stream.close()
477 if fd: # fd means we used temporary file
480 if fd: # fd means we used temporary file
478 os.close(fd)
481 os.close(fd)
479 if not archive_cache_enabled:
482 if not archive_cache_enabled:
480 log.debug('Destroing temp archive %s' % archive)
483 log.debug('Destroing temp archive %s' % archive)
481 os.remove(archive)
484 os.remove(archive)
482 break
485 break
483 yield data
486 yield data
484
487
485 response.content_disposition = str('attachment; filename=%s' % (archive_name))
488 response.content_disposition = str('attachment; filename=%s' % (archive_name))
486 response.content_type = str(content_type)
489 response.content_type = str(content_type)
487 return get_chunked_archive(archive)
490 return get_chunked_archive(archive)
488
491
489 @LoginRequired()
492 @LoginRequired()
490 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
491 'repository.admin')
494 'repository.admin')
492 def diff(self, repo_name, f_path):
495 def diff(self, repo_name, f_path):
493 ignore_whitespace = request.GET.get('ignorews') == '1'
496 ignore_whitespace = request.GET.get('ignorews') == '1'
494 line_context = request.GET.get('context', 3)
497 line_context = request.GET.get('context', 3)
495 diff1 = request.GET.get('diff1', '')
498 diff1 = request.GET.get('diff1', '')
496 diff2 = request.GET.get('diff2', '')
499 diff2 = request.GET.get('diff2', '')
497 c.action = request.GET.get('diff')
500 c.action = request.GET.get('diff')
498 c.no_changes = diff1 == diff2
501 c.no_changes = diff1 == diff2
499 c.f_path = f_path
502 c.f_path = f_path
500 c.big_diff = False
503 c.big_diff = False
501 c.anchor_url = anchor_url
504 c.anchor_url = anchor_url
502 c.ignorews_url = _ignorews_url
505 c.ignorews_url = _ignorews_url
503 c.context_url = _context_url
506 c.context_url = _context_url
504 c.changes = OrderedDict()
507 c.changes = OrderedDict()
505 c.changes[diff2] = []
508 c.changes[diff2] = []
506
509
507 #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
508 #to reduce JS and callbacks
511 #to reduce JS and callbacks
509
512
510 if request.GET.get('show_rev'):
513 if request.GET.get('show_rev'):
511 if str2bool(request.GET.get('annotate', 'False')):
514 if str2bool(request.GET.get('annotate', 'False')):
512 _url = url('files_annotate_home', repo_name=c.repo_name,
515 _url = url('files_annotate_home', repo_name=c.repo_name,
513 revision=diff1, f_path=c.f_path)
516 revision=diff1, f_path=c.f_path)
514 else:
517 else:
515 _url = url('files_home', repo_name=c.repo_name,
518 _url = url('files_home', repo_name=c.repo_name,
516 revision=diff1, f_path=c.f_path)
519 revision=diff1, f_path=c.f_path)
517
520
518 return redirect(_url)
521 return redirect(_url)
519 try:
522 try:
520 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
523 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
521 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
524 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
522 try:
525 try:
523 node1 = c.changeset_1.get_node(f_path)
526 node1 = c.changeset_1.get_node(f_path)
524 if node1.is_dir():
527 if node1.is_dir():
525 raise NodeError('%s path is a %s not a file'
528 raise NodeError('%s path is a %s not a file'
526 % (node1, type(node1)))
529 % (node1, type(node1)))
527 except NodeDoesNotExistError:
530 except NodeDoesNotExistError:
528 c.changeset_1 = EmptyChangeset(cs=diff1,
531 c.changeset_1 = EmptyChangeset(cs=diff1,
529 revision=c.changeset_1.revision,
532 revision=c.changeset_1.revision,
530 repo=c.rhodecode_repo)
533 repo=c.rhodecode_repo)
531 node1 = FileNode(f_path, '', changeset=c.changeset_1)
534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
532 else:
535 else:
533 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
536 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
537 node1 = FileNode(f_path, '', changeset=c.changeset_1)
535
538
536 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
539 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
537 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
540 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
538 try:
541 try:
539 node2 = c.changeset_2.get_node(f_path)
542 node2 = c.changeset_2.get_node(f_path)
540 if node2.is_dir():
543 if node2.is_dir():
541 raise NodeError('%s path is a %s not a file'
544 raise NodeError('%s path is a %s not a file'
542 % (node2, type(node2)))
545 % (node2, type(node2)))
543 except NodeDoesNotExistError:
546 except NodeDoesNotExistError:
544 c.changeset_2 = EmptyChangeset(cs=diff2,
547 c.changeset_2 = EmptyChangeset(cs=diff2,
545 revision=c.changeset_2.revision,
548 revision=c.changeset_2.revision,
546 repo=c.rhodecode_repo)
549 repo=c.rhodecode_repo)
547 node2 = FileNode(f_path, '', changeset=c.changeset_2)
550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
548 else:
551 else:
549 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
552 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
553 node2 = FileNode(f_path, '', changeset=c.changeset_2)
551 except (RepositoryError, NodeError):
554 except (RepositoryError, NodeError):
552 log.error(traceback.format_exc())
555 log.error(traceback.format_exc())
553 return redirect(url('files_home', repo_name=c.repo_name,
556 return redirect(url('files_home', repo_name=c.repo_name,
554 f_path=f_path))
557 f_path=f_path))
555
558
556 if c.action == 'download':
559 if c.action == 'download':
557 _diff = diffs.get_gitdiff(node1, node2,
560 _diff = diffs.get_gitdiff(node1, node2,
558 ignore_whitespace=ignore_whitespace,
561 ignore_whitespace=ignore_whitespace,
559 context=line_context)
562 context=line_context)
560 diff = diffs.DiffProcessor(_diff, format='gitdiff')
563 diff = diffs.DiffProcessor(_diff, format='gitdiff')
561
564
562 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
565 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
563 response.content_type = 'text/plain'
566 response.content_type = 'text/plain'
564 response.content_disposition = (
567 response.content_disposition = (
565 'attachment; filename=%s' % diff_name
568 'attachment; filename=%s' % diff_name
566 )
569 )
567 return diff.as_raw()
570 return diff.as_raw()
568
571
569 elif c.action == 'raw':
572 elif c.action == 'raw':
570 _diff = diffs.get_gitdiff(node1, node2,
573 _diff = diffs.get_gitdiff(node1, node2,
571 ignore_whitespace=ignore_whitespace,
574 ignore_whitespace=ignore_whitespace,
572 context=line_context)
575 context=line_context)
573 diff = diffs.DiffProcessor(_diff, format='gitdiff')
576 diff = diffs.DiffProcessor(_diff, format='gitdiff')
574 response.content_type = 'text/plain'
577 response.content_type = 'text/plain'
575 return diff.as_raw()
578 return diff.as_raw()
576
579
577 else:
580 else:
578 fid = h.FID(diff2, node2.path)
581 fid = h.FID(diff2, node2.path)
579 line_context_lcl = get_line_ctx(fid, request.GET)
582 line_context_lcl = get_line_ctx(fid, request.GET)
580 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
583 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
581
584
582 lim = request.GET.get('fulldiff') or self.cut_off_limit
585 lim = request.GET.get('fulldiff') or self.cut_off_limit
583 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
586 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
584 filenode_new=node2,
587 filenode_new=node2,
585 cut_off_limit=lim,
588 cut_off_limit=lim,
586 ignore_whitespace=ign_whitespace_lcl,
589 ignore_whitespace=ign_whitespace_lcl,
587 line_context=line_context_lcl,
590 line_context=line_context_lcl,
588 enable_comments=False)
591 enable_comments=False)
589 op = ''
592 op = ''
590 filename = node1.path
593 filename = node1.path
591 cs_changes = {
594 cs_changes = {
592 'fid': [cs1, cs2, op, filename, diff, st]
595 'fid': [cs1, cs2, op, filename, diff, st]
593 }
596 }
594 c.changes = cs_changes
597 c.changes = cs_changes
595
598
596 return render('files/file_diff.html')
599 return render('files/file_diff.html')
597
600
598 def _get_node_history(self, cs, f_path, changesets=None):
601 def _get_node_history(self, cs, f_path, changesets=None):
599 """
602 """
600 get changesets history for given node
603 get changesets history for given node
601
604
602 :param cs: changeset to calculate history
605 :param cs: changeset to calculate history
603 :param f_path: path for node to calculate history for
606 :param f_path: path for node to calculate history for
604 :param changesets: if passed don't calculate history and take
607 :param changesets: if passed don't calculate history and take
605 changesets defined in this list
608 changesets defined in this list
606 """
609 """
607 # calculate history based on tip
610 # calculate history based on tip
608 tip_cs = c.rhodecode_repo.get_changeset()
611 tip_cs = c.rhodecode_repo.get_changeset()
609 if changesets is None:
612 if changesets is None:
610 try:
613 try:
611 changesets = tip_cs.get_file_history(f_path)
614 changesets = tip_cs.get_file_history(f_path)
612 except (NodeDoesNotExistError, ChangesetError):
615 except (NodeDoesNotExistError, ChangesetError):
613 #this node is not present at tip !
616 #this node is not present at tip !
614 changesets = cs.get_file_history(f_path)
617 changesets = cs.get_file_history(f_path)
615 hist_l = []
618 hist_l = []
616
619
617 changesets_group = ([], _("Changesets"))
620 changesets_group = ([], _("Changesets"))
618 branches_group = ([], _("Branches"))
621 branches_group = ([], _("Branches"))
619 tags_group = ([], _("Tags"))
622 tags_group = ([], _("Tags"))
620 _hg = cs.repository.alias == 'hg'
623 _hg = cs.repository.alias == 'hg'
621 for chs in changesets:
624 for chs in changesets:
622 #_branch = '(%s)' % chs.branch if _hg else ''
625 #_branch = '(%s)' % chs.branch if _hg else ''
623 _branch = chs.branch
626 _branch = chs.branch
624 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)
625 changesets_group[0].append((chs.raw_id, n_desc,))
628 changesets_group[0].append((chs.raw_id, n_desc,))
626 hist_l.append(changesets_group)
629 hist_l.append(changesets_group)
627
630
628 for name, chs in c.rhodecode_repo.branches.items():
631 for name, chs in c.rhodecode_repo.branches.items():
629 branches_group[0].append((chs, name),)
632 branches_group[0].append((chs, name),)
630 hist_l.append(branches_group)
633 hist_l.append(branches_group)
631
634
632 for name, chs in c.rhodecode_repo.tags.items():
635 for name, chs in c.rhodecode_repo.tags.items():
633 tags_group[0].append((chs, name),)
636 tags_group[0].append((chs, name),)
634 hist_l.append(tags_group)
637 hist_l.append(tags_group)
635
638
636 return hist_l, changesets
639 return hist_l, changesets
637
640
638 @LoginRequired()
641 @LoginRequired()
639 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
642 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
640 'repository.admin')
643 'repository.admin')
641 @jsonify
644 @jsonify
642 def nodelist(self, repo_name, revision, f_path):
645 def nodelist(self, repo_name, revision, f_path):
643 if request.environ.get('HTTP_X_PARTIAL_XHR'):
646 if request.environ.get('HTTP_X_PARTIAL_XHR'):
644 cs = self.__get_cs_or_redirect(revision, repo_name)
647 cs = self.__get_cs_or_redirect(revision, repo_name)
645 _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,
646 flat=False)
649 flat=False)
647 return {'nodes': _d + _f}
650 return {'nodes': _d + _f}
@@ -1,58 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.followers
3 rhodecode.controllers.followers
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Followers controller for rhodecode
6 Followers controller for rhodecode
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-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 import logging
25 import logging
26
26
27 from pylons import tmpl_context as c, request
27 from pylons import tmpl_context as c, request
28
28
29 from rhodecode.lib.helpers import Page
29 from rhodecode.lib.helpers import Page
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.model.db import Repository, User, UserFollowing
32 from rhodecode.model.db import Repository, User, UserFollowing
33 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.utils2 import safe_int
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class FollowersController(BaseRepoController):
38 class FollowersController(BaseRepoController):
39
39
40 def __before__(self):
41 super(FollowersController, self).__before__()
42
40 @LoginRequired()
43 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
44 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 'repository.admin')
45 'repository.admin')
43 def __before__(self):
44 super(FollowersController, self).__before__()
45
46 def followers(self, repo_name):
46 def followers(self, repo_name):
47 p = safe_int(request.GET.get('page', 1), 1)
47 p = safe_int(request.GET.get('page', 1), 1)
48 repo_id = c.rhodecode_db_repo.repo_id
48 repo_id = c.rhodecode_db_repo.repo_id
49 d = UserFollowing.get_repo_followers(repo_id)\
49 d = UserFollowing.get_repo_followers(repo_id)\
50 .order_by(UserFollowing.follows_from)
50 .order_by(UserFollowing.follows_from)
51 c.followers_pager = Page(d, page=p, items_per_page=20)
51 c.followers_pager = Page(d, page=p, items_per_page=20)
52
52
53 c.followers_data = render('/followers/followers_data.html')
53 c.followers_data = render('/followers/followers_data.html')
54
54
55 if request.environ.get('HTTP_X_PARTIAL_XHR'):
55 if request.environ.get('HTTP_X_PARTIAL_XHR'):
56 return c.followers_data
56 return c.followers_data
57
57
58 return render('/followers/followers.html')
58 return render('/followers/followers.html')
@@ -1,191 +1,193 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.forks
3 rhodecode.controllers.forks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 forks controller for rhodecode
6 forks controller for rhodecode
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-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 import logging
25 import logging
26 import formencode
26 import formencode
27 import traceback
27 import traceback
28 from formencode import htmlfill
28 from formencode import htmlfill
29
29
30 from pylons import tmpl_context as c, request, url
30 from pylons import tmpl_context as c, request, url
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
35
35
36 from rhodecode.lib.helpers import Page
36 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
39 HasPermissionAnyDecorator
39 HasPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User,\
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User,\
42 RhodeCodeUi
42 RhodeCodeUi
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.forms import RepoForkForm
45 from rhodecode.model.scm import ScmModel, RepoGroupList
45 from rhodecode.model.scm import ScmModel, RepoGroupList
46 from rhodecode.lib.utils2 import safe_int
46 from rhodecode.lib.utils2 import safe_int
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class ForksController(BaseRepoController):
51 class ForksController(BaseRepoController):
52
52
53 @LoginRequired()
54 def __before__(self):
53 def __before__(self):
55 super(ForksController, self).__before__()
54 super(ForksController, self).__before__()
56
55
57 def __load_defaults(self):
56 def __load_defaults(self):
58 acl_groups = RepoGroupList(RepoGroup.query().all(),
57 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 perm_set=['group.write', 'group.admin'])
58 perm_set=['group.write', 'group.admin'])
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
59 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
62 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
61 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
63 c.landing_revs_choices = choices
62 c.landing_revs_choices = choices
64 c.can_update = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active
63 c.can_update = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active
65
64
66 def __load_data(self, repo_name=None):
65 def __load_data(self, repo_name=None):
67 """
66 """
68 Load defaults settings for edit, and update
67 Load defaults settings for edit, and update
69
68
70 :param repo_name:
69 :param repo_name:
71 """
70 """
72 self.__load_defaults()
71 self.__load_defaults()
73
72
74 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
73 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
75 repo = db_repo.scm_instance
74 repo = db_repo.scm_instance
76
75
77 if c.repo_info is None:
76 if c.repo_info is None:
78 h.not_mapped_error(repo_name)
77 h.not_mapped_error(repo_name)
79 return redirect(url('repos'))
78 return redirect(url('repos'))
80
79
81 c.default_user_id = User.get_default_user().user_id
80 c.default_user_id = User.get_default_user().user_id
82 c.in_public_journal = UserFollowing.query()\
81 c.in_public_journal = UserFollowing.query()\
83 .filter(UserFollowing.user_id == c.default_user_id)\
82 .filter(UserFollowing.user_id == c.default_user_id)\
84 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
83 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
85
84
86 if c.repo_info.stats:
85 if c.repo_info.stats:
87 last_rev = c.repo_info.stats.stat_on_revision+1
86 last_rev = c.repo_info.stats.stat_on_revision+1
88 else:
87 else:
89 last_rev = 0
88 last_rev = 0
90 c.stats_revision = last_rev
89 c.stats_revision = last_rev
91
90
92 c.repo_last_rev = repo.count() if repo.revisions else 0
91 c.repo_last_rev = repo.count() if repo.revisions else 0
93
92
94 if last_rev == 0 or c.repo_last_rev == 0:
93 if last_rev == 0 or c.repo_last_rev == 0:
95 c.stats_percentage = 0
94 c.stats_percentage = 0
96 else:
95 else:
97 c.stats_percentage = '%.2f' % ((float((last_rev)) /
96 c.stats_percentage = '%.2f' % ((float((last_rev)) /
98 c.repo_last_rev) * 100)
97 c.repo_last_rev) * 100)
99
98
100 defaults = RepoModel()._get_defaults(repo_name)
99 defaults = RepoModel()._get_defaults(repo_name)
101 # alter the description to indicate a fork
100 # alter the description to indicate a fork
102 defaults['description'] = ('fork of repository: %s \n%s'
101 defaults['description'] = ('fork of repository: %s \n%s'
103 % (defaults['repo_name'],
102 % (defaults['repo_name'],
104 defaults['description']))
103 defaults['description']))
105 # add suffix to fork
104 # add suffix to fork
106 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
105 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
107
106
108 return defaults
107 return defaults
109
108
109 @LoginRequired()
110 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
110 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
111 'repository.admin')
111 'repository.admin')
112 def forks(self, repo_name):
112 def forks(self, repo_name):
113 p = safe_int(request.GET.get('page', 1), 1)
113 p = safe_int(request.GET.get('page', 1), 1)
114 repo_id = c.rhodecode_db_repo.repo_id
114 repo_id = c.rhodecode_db_repo.repo_id
115 d = []
115 d = []
116 for r in Repository.get_repo_forks(repo_id):
116 for r in Repository.get_repo_forks(repo_id):
117 if not HasRepoPermissionAny(
117 if not HasRepoPermissionAny(
118 'repository.read', 'repository.write', 'repository.admin'
118 'repository.read', 'repository.write', 'repository.admin'
119 )(r.repo_name, 'get forks check'):
119 )(r.repo_name, 'get forks check'):
120 continue
120 continue
121 d.append(r)
121 d.append(r)
122 c.forks_pager = Page(d, page=p, items_per_page=20)
122 c.forks_pager = Page(d, page=p, items_per_page=20)
123
123
124 c.forks_data = render('/forks/forks_data.html')
124 c.forks_data = render('/forks/forks_data.html')
125
125
126 if request.environ.get('HTTP_X_PARTIAL_XHR'):
126 if request.environ.get('HTTP_X_PARTIAL_XHR'):
127 return c.forks_data
127 return c.forks_data
128
128
129 return render('/forks/forks.html')
129 return render('/forks/forks.html')
130
130
131 @LoginRequired()
131 @NotAnonymous()
132 @NotAnonymous()
132 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
133 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
133 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
134 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
134 'repository.admin')
135 'repository.admin')
135 def fork(self, repo_name):
136 def fork(self, repo_name):
136 c.repo_info = Repository.get_by_repo_name(repo_name)
137 c.repo_info = Repository.get_by_repo_name(repo_name)
137 if not c.repo_info:
138 if not c.repo_info:
138 h.not_mapped_error(repo_name)
139 h.not_mapped_error(repo_name)
139 return redirect(url('home'))
140 return redirect(url('home'))
140
141
141 defaults = self.__load_data(repo_name)
142 defaults = self.__load_data(repo_name)
142
143
143 return htmlfill.render(
144 return htmlfill.render(
144 render('forks/fork.html'),
145 render('forks/fork.html'),
145 defaults=defaults,
146 defaults=defaults,
146 encoding="UTF-8",
147 encoding="UTF-8",
147 force_defaults=False
148 force_defaults=False
148 )
149 )
149
150
151 @LoginRequired()
150 @NotAnonymous()
152 @NotAnonymous()
151 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
153 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
152 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 'repository.admin')
155 'repository.admin')
154 def fork_create(self, repo_name):
156 def fork_create(self, repo_name):
155 self.__load_defaults()
157 self.__load_defaults()
156 c.repo_info = Repository.get_by_repo_name(repo_name)
158 c.repo_info = Repository.get_by_repo_name(repo_name)
157 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
159 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
158 repo_groups=c.repo_groups_choices,
160 repo_groups=c.repo_groups_choices,
159 landing_revs=c.landing_revs_choices)()
161 landing_revs=c.landing_revs_choices)()
160 form_result = {}
162 form_result = {}
161 try:
163 try:
162 form_result = _form.to_python(dict(request.POST))
164 form_result = _form.to_python(dict(request.POST))
163
165
164 # an approximation that is better than nothing
166 # an approximation that is better than nothing
165 if not RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active:
167 if not RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active:
166 form_result['update_after_clone'] = False
168 form_result['update_after_clone'] = False
167
169
168 # create fork is done sometimes async on celery, db transaction
170 # create fork is done sometimes async on celery, db transaction
169 # management is handled there.
171 # management is handled there.
170 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
172 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
171 fork_url = h.link_to(form_result['repo_name_full'],
173 fork_url = h.link_to(form_result['repo_name_full'],
172 h.url('summary_home', repo_name=form_result['repo_name_full']))
174 h.url('summary_home', repo_name=form_result['repo_name_full']))
173
175
174 h.flash(h.literal(_('Forked repository %s as %s') \
176 h.flash(h.literal(_('Forked repository %s as %s') \
175 % (repo_name, fork_url)),
177 % (repo_name, fork_url)),
176 category='success')
178 category='success')
177 except formencode.Invalid, errors:
179 except formencode.Invalid, errors:
178 c.new_repo = errors.value['repo_name']
180 c.new_repo = errors.value['repo_name']
179
181
180 return htmlfill.render(
182 return htmlfill.render(
181 render('forks/fork.html'),
183 render('forks/fork.html'),
182 defaults=errors.value,
184 defaults=errors.value,
183 errors=errors.error_dict or {},
185 errors=errors.error_dict or {},
184 prefix_error=False,
186 prefix_error=False,
185 encoding="UTF-8")
187 encoding="UTF-8")
186 except Exception:
188 except Exception:
187 log.error(traceback.format_exc())
189 log.error(traceback.format_exc())
188 h.flash(_('An error occurred during repository forking %s') %
190 h.flash(_('An error occurred during repository forking %s') %
189 repo_name, category='error')
191 repo_name, category='error')
190
192
191 return redirect(h.url('summary_home', repo_name=repo_name))
193 return redirect(h.url('summary_home', repo_name=repo_name))
@@ -1,87 +1,89 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.home
3 rhodecode.controllers.home
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Home controller for Rhodecode
6 Home controller for Rhodecode
7
7
8 :created_on: Feb 18, 2010
8 :created_on: Feb 18, 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
27
28 from pylons import tmpl_context as c, request
28 from pylons import tmpl_context as c, request
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from webob.exc import HTTPBadRequest
30 from webob.exc import HTTPBadRequest
31 from sqlalchemy.sql.expression import func
31 from sqlalchemy.sql.expression import func
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.compat import json
35 from rhodecode.lib.compat import json
36 from rhodecode.lib.auth import LoginRequired
36 from rhodecode.lib.auth import LoginRequired
37 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.base import BaseController, render
38 from rhodecode.model.db import Repository
38 from rhodecode.model.db import Repository
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40
40
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class HomeController(BaseController):
45 class HomeController(BaseController):
46
46
47 @LoginRequired()
48 def __before__(self):
47 def __before__(self):
49 super(HomeController, self).__before__()
48 super(HomeController, self).__before__()
50
49
50 @LoginRequired()
51 def index(self):
51 def index(self):
52 c.groups = self.scm_model.get_repos_groups()
52 c.groups = self.scm_model.get_repos_groups()
53 c.group = None
53 c.group = None
54
54
55 if not c.visual.lightweight_dashboard:
55 if not c.visual.lightweight_dashboard:
56 c.repos_list = self.scm_model.get_repos()
56 c.repos_list = self.scm_model.get_repos()
57 ## lightweight version of dashboard
57 ## lightweight version of dashboard
58 else:
58 else:
59 c.repos_list = Repository.query()\
59 c.repos_list = Repository.query()\
60 .filter(Repository.group_id == None)\
60 .filter(Repository.group_id == None)\
61 .order_by(func.lower(Repository.repo_name))\
61 .order_by(func.lower(Repository.repo_name))\
62 .all()
62 .all()
63
63
64 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
64 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
65 admin=False)
65 admin=False)
66 #json used to render the grid
66 #json used to render the grid
67 c.data = json.dumps(repos_data)
67 c.data = json.dumps(repos_data)
68
68
69 return render('/index.html')
69 return render('/index.html')
70
70
71 @LoginRequired()
71 def repo_switcher(self):
72 def repo_switcher(self):
72 if request.is_xhr:
73 if request.is_xhr:
73 all_repos = Repository.query().order_by(Repository.repo_name).all()
74 all_repos = Repository.query().order_by(Repository.repo_name).all()
74 c.repos_list = self.scm_model.get_repos(all_repos,
75 c.repos_list = self.scm_model.get_repos(all_repos,
75 sort_key='name_sort',
76 sort_key='name_sort',
76 simple=True)
77 simple=True)
77 return render('/repo_switcher_list.html')
78 return render('/repo_switcher_list.html')
78 else:
79 else:
79 raise HTTPBadRequest()
80 raise HTTPBadRequest()
80
81
82 @LoginRequired()
81 def branch_tag_switcher(self, repo_name):
83 def branch_tag_switcher(self, repo_name):
82 if request.is_xhr:
84 if request.is_xhr:
83 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
85 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
84 if c.rhodecode_db_repo:
86 if c.rhodecode_db_repo:
85 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
87 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
86 return render('/switch_to_list.html')
88 return render('/switch_to_list.html')
87 raise HTTPBadRequest()
89 raise HTTPBadRequest()
@@ -1,379 +1,379 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.journal
3 rhodecode.controllers.journal
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Journal controller for pylons
6 Journal controller for pylons
7
7
8 :created_on: Nov 21, 2010
8 :created_on: Nov 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 import logging
25 import logging
26 from itertools import groupby
26 from itertools import groupby
27
27
28 from sqlalchemy import or_
28 from sqlalchemy import or_
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30 from sqlalchemy.sql.expression import func
30 from sqlalchemy.sql.expression import func
31
31
32 from webhelpers.paginate import Page
32 from webhelpers.paginate import Page
33 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
33 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
34
34
35 from webob.exc import HTTPBadRequest
35 from webob.exc import HTTPBadRequest
36 from pylons import request, tmpl_context as c, response, url
36 from pylons import request, tmpl_context as c, response, url
37 from pylons.i18n.translation import _
37 from pylons.i18n.translation import _
38
38
39 import rhodecode.lib.helpers as h
39 import rhodecode.lib.helpers as h
40 from rhodecode.lib.auth import LoginRequired, NotAnonymous
40 from rhodecode.lib.auth import LoginRequired, NotAnonymous
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
42 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45 from rhodecode.controllers.admin.admin import _journal_filter
45 from rhodecode.controllers.admin.admin import _journal_filter
46 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.repo import RepoModel
47 from rhodecode.lib.compat import json
47 from rhodecode.lib.compat import json
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class JournalController(BaseController):
52 class JournalController(BaseController):
53
53
54 def __before__(self):
54 def __before__(self):
55 super(JournalController, self).__before__()
55 super(JournalController, self).__before__()
56 self.language = 'en-us'
56 self.language = 'en-us'
57 self.ttl = "5"
57 self.ttl = "5"
58 self.feed_nr = 20
58 self.feed_nr = 20
59 c.search_term = request.GET.get('filter')
59 c.search_term = request.GET.get('filter')
60
60
61 def _get_daily_aggregate(self, journal):
62 groups = []
63 for k, g in groupby(journal, lambda x: x.action_as_day):
64 user_group = []
65 #groupby username if it's a present value, else fallback to journal username
66 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
67 l = list(g2)
68 user_group.append((l[0].user, l))
69
70 groups.append((k, user_group,))
71
72 return groups
73
74 def _get_journal_data(self, following_repos):
75 repo_ids = [x.follows_repository.repo_id for x in following_repos
76 if x.follows_repository is not None]
77 user_ids = [x.follows_user.user_id for x in following_repos
78 if x.follows_user is not None]
79
80 filtering_criterion = None
81
82 if repo_ids and user_ids:
83 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
84 UserLog.user_id.in_(user_ids))
85 if repo_ids and not user_ids:
86 filtering_criterion = UserLog.repository_id.in_(repo_ids)
87 if not repo_ids and user_ids:
88 filtering_criterion = UserLog.user_id.in_(user_ids)
89 if filtering_criterion is not None:
90 journal = self.sa.query(UserLog)\
91 .options(joinedload(UserLog.user))\
92 .options(joinedload(UserLog.repository))
93 #filter
94 try:
95 journal = _journal_filter(journal, c.search_term)
96 except Exception:
97 # we want this to crash for now
98 raise
99 journal = journal.filter(filtering_criterion)\
100 .order_by(UserLog.action_date.desc())
101 else:
102 journal = []
103
104 return journal
105
106 def _atom_feed(self, repos, public=True):
107 journal = self._get_journal_data(repos)
108 if public:
109 _link = url('public_journal_atom', qualified=True)
110 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
111 'atom feed')
112 else:
113 _link = url('journal_atom', qualified=True)
114 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
115
116 feed = Atom1Feed(title=_desc,
117 link=_link,
118 description=_desc,
119 language=self.language,
120 ttl=self.ttl)
121
122 for entry in journal[:self.feed_nr]:
123 user = entry.user
124 if user is None:
125 #fix deleted users
126 user = AttributeDict({'short_contact': entry.username,
127 'email': '',
128 'full_contact': ''})
129 action, action_extra, ico = h.action_parser(entry, feed=True)
130 title = "%s - %s %s" % (user.short_contact, action(),
131 entry.repository.repo_name)
132 desc = action_extra()
133 _url = None
134 if entry.repository is not None:
135 _url = url('changelog_home',
136 repo_name=entry.repository.repo_name,
137 qualified=True)
138
139 feed.add_item(title=title,
140 pubdate=entry.action_date,
141 link=_url or url('', qualified=True),
142 author_email=user.email,
143 author_name=user.full_contact,
144 description=desc)
145
146 response.content_type = feed.mime_type
147 return feed.writeString('utf-8')
148
149 def _rss_feed(self, repos, public=True):
150 journal = self._get_journal_data(repos)
151 if public:
152 _link = url('public_journal_atom', qualified=True)
153 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
154 'rss feed')
155 else:
156 _link = url('journal_atom', qualified=True)
157 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
158
159 feed = Rss201rev2Feed(title=_desc,
160 link=_link,
161 description=_desc,
162 language=self.language,
163 ttl=self.ttl)
164
165 for entry in journal[:self.feed_nr]:
166 user = entry.user
167 if user is None:
168 #fix deleted users
169 user = AttributeDict({'short_contact': entry.username,
170 'email': '',
171 'full_contact': ''})
172 action, action_extra, ico = h.action_parser(entry, feed=True)
173 title = "%s - %s %s" % (user.short_contact, action(),
174 entry.repository.repo_name)
175 desc = action_extra()
176 _url = None
177 if entry.repository is not None:
178 _url = url('changelog_home',
179 repo_name=entry.repository.repo_name,
180 qualified=True)
181
182 feed.add_item(title=title,
183 pubdate=entry.action_date,
184 link=_url or url('', qualified=True),
185 author_email=user.email,
186 author_name=user.full_contact,
187 description=desc)
188
189 response.content_type = feed.mime_type
190 return feed.writeString('utf-8')
191
61 @LoginRequired()
192 @LoginRequired()
62 @NotAnonymous()
193 @NotAnonymous()
63 def index(self):
194 def index(self):
64 # Return a rendered template
195 # Return a rendered template
65 p = safe_int(request.GET.get('page', 1), 1)
196 p = safe_int(request.GET.get('page', 1), 1)
66 c.user = User.get(self.rhodecode_user.user_id)
197 c.user = User.get(self.rhodecode_user.user_id)
67 c.following = self.sa.query(UserFollowing)\
198 c.following = self.sa.query(UserFollowing)\
68 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
199 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
69 .options(joinedload(UserFollowing.follows_repository))\
200 .options(joinedload(UserFollowing.follows_repository))\
70 .all()
201 .all()
71
202
72 journal = self._get_journal_data(c.following)
203 journal = self._get_journal_data(c.following)
73
204
74 def url_generator(**kw):
205 def url_generator(**kw):
75 return url.current(filter=c.search_term, **kw)
206 return url.current(filter=c.search_term, **kw)
76
207
77 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
208 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
78 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
209 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
79
210
80 c.journal_data = render('journal/journal_data.html')
211 c.journal_data = render('journal/journal_data.html')
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
212 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 return c.journal_data
213 return c.journal_data
83
214
84 repos_list = Session().query(Repository)\
215 repos_list = Session().query(Repository)\
85 .filter(Repository.user_id ==
216 .filter(Repository.user_id ==
86 self.rhodecode_user.user_id)\
217 self.rhodecode_user.user_id)\
87 .order_by(func.lower(Repository.repo_name)).all()
218 .order_by(func.lower(Repository.repo_name)).all()
88
219
89 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
220 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
90 admin=True)
221 admin=True)
91 #json used to render the grid
222 #json used to render the grid
92 c.data = json.dumps(repos_data)
223 c.data = json.dumps(repos_data)
93
224
94 watched_repos_data = []
225 watched_repos_data = []
95
226
96 ## watched repos
227 ## watched repos
97 _render = RepoModel._render_datatable
228 _render = RepoModel._render_datatable
98
229
99 def quick_menu(repo_name):
230 def quick_menu(repo_name):
100 return _render('quick_menu', repo_name)
231 return _render('quick_menu', repo_name)
101
232
102 def repo_lnk(name, rtype, private, fork_of):
233 def repo_lnk(name, rtype, private, fork_of):
103 return _render('repo_name', name, rtype, private, fork_of,
234 return _render('repo_name', name, rtype, private, fork_of,
104 short_name=False, admin=False)
235 short_name=False, admin=False)
105
236
106 def last_rev(repo_name, cs_cache):
237 def last_rev(repo_name, cs_cache):
107 return _render('revision', repo_name, cs_cache.get('revision'),
238 return _render('revision', repo_name, cs_cache.get('revision'),
108 cs_cache.get('raw_id'), cs_cache.get('author'),
239 cs_cache.get('raw_id'), cs_cache.get('author'),
109 cs_cache.get('message'))
240 cs_cache.get('message'))
110
241
111 def desc(desc):
242 def desc(desc):
112 from pylons import tmpl_context as c
243 from pylons import tmpl_context as c
113 if c.visual.stylify_metatags:
244 if c.visual.stylify_metatags:
114 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
245 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
115 else:
246 else:
116 return h.urlify_text(h.truncate(desc, 60))
247 return h.urlify_text(h.truncate(desc, 60))
117
248
118 def repo_actions(repo_name):
249 def repo_actions(repo_name):
119 return _render('repo_actions', repo_name)
250 return _render('repo_actions', repo_name)
120
251
121 def owner_actions(user_id, username):
252 def owner_actions(user_id, username):
122 return _render('user_name', user_id, username)
253 return _render('user_name', user_id, username)
123
254
124 def toogle_follow(repo_id):
255 def toogle_follow(repo_id):
125 return _render('toggle_follow', repo_id)
256 return _render('toggle_follow', repo_id)
126
257
127 for entry in c.following:
258 for entry in c.following:
128 repo = entry.follows_repository
259 repo = entry.follows_repository
129 cs_cache = repo.changeset_cache
260 cs_cache = repo.changeset_cache
130 row = {
261 row = {
131 "menu": quick_menu(repo.repo_name),
262 "menu": quick_menu(repo.repo_name),
132 "raw_name": repo.repo_name.lower(),
263 "raw_name": repo.repo_name.lower(),
133 "name": repo_lnk(repo.repo_name, repo.repo_type,
264 "name": repo_lnk(repo.repo_name, repo.repo_type,
134 repo.private, repo.fork),
265 repo.private, repo.fork),
135 "last_changeset": last_rev(repo.repo_name, cs_cache),
266 "last_changeset": last_rev(repo.repo_name, cs_cache),
136 "raw_tip": cs_cache.get('revision'),
267 "raw_tip": cs_cache.get('revision'),
137 "action": toogle_follow(repo.repo_id)
268 "action": toogle_follow(repo.repo_id)
138 }
269 }
139
270
140 watched_repos_data.append(row)
271 watched_repos_data.append(row)
141
272
142 c.watched_data = json.dumps({
273 c.watched_data = json.dumps({
143 "totalRecords": len(c.following),
274 "totalRecords": len(c.following),
144 "startIndex": 0,
275 "startIndex": 0,
145 "sort": "name",
276 "sort": "name",
146 "dir": "asc",
277 "dir": "asc",
147 "records": watched_repos_data
278 "records": watched_repos_data
148 })
279 })
149 return render('journal/journal.html')
280 return render('journal/journal.html')
150
281
151 @LoginRequired(api_access=True)
282 @LoginRequired(api_access=True)
152 @NotAnonymous()
283 @NotAnonymous()
153 def journal_atom(self):
284 def journal_atom(self):
154 """
285 """
155 Produce an atom-1.0 feed via feedgenerator module
286 Produce an atom-1.0 feed via feedgenerator module
156 """
287 """
157 following = self.sa.query(UserFollowing)\
288 following = self.sa.query(UserFollowing)\
158 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
289 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
159 .options(joinedload(UserFollowing.follows_repository))\
290 .options(joinedload(UserFollowing.follows_repository))\
160 .all()
291 .all()
161 return self._atom_feed(following, public=False)
292 return self._atom_feed(following, public=False)
162
293
163 @LoginRequired(api_access=True)
294 @LoginRequired(api_access=True)
164 @NotAnonymous()
295 @NotAnonymous()
165 def journal_rss(self):
296 def journal_rss(self):
166 """
297 """
167 Produce an rss feed via feedgenerator module
298 Produce an rss feed via feedgenerator module
168 """
299 """
169 following = self.sa.query(UserFollowing)\
300 following = self.sa.query(UserFollowing)\
170 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
301 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
171 .options(joinedload(UserFollowing.follows_repository))\
302 .options(joinedload(UserFollowing.follows_repository))\
172 .all()
303 .all()
173 return self._rss_feed(following, public=False)
304 return self._rss_feed(following, public=False)
174
305
175 def _get_daily_aggregate(self, journal):
176 groups = []
177 for k, g in groupby(journal, lambda x: x.action_as_day):
178 user_group = []
179 #groupby username if it's a present value, else fallback to journal username
180 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
181 l = list(g2)
182 user_group.append((l[0].user, l))
183
184 groups.append((k, user_group,))
185
186 return groups
187
188 def _get_journal_data(self, following_repos):
189 repo_ids = [x.follows_repository.repo_id for x in following_repos
190 if x.follows_repository is not None]
191 user_ids = [x.follows_user.user_id for x in following_repos
192 if x.follows_user is not None]
193
194 filtering_criterion = None
195
196 if repo_ids and user_ids:
197 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
198 UserLog.user_id.in_(user_ids))
199 if repo_ids and not user_ids:
200 filtering_criterion = UserLog.repository_id.in_(repo_ids)
201 if not repo_ids and user_ids:
202 filtering_criterion = UserLog.user_id.in_(user_ids)
203 if filtering_criterion is not None:
204 journal = self.sa.query(UserLog)\
205 .options(joinedload(UserLog.user))\
206 .options(joinedload(UserLog.repository))
207 #filter
208 try:
209 journal = _journal_filter(journal, c.search_term)
210 except Exception:
211 # we want this to crash for now
212 raise
213 journal = journal.filter(filtering_criterion)\
214 .order_by(UserLog.action_date.desc())
215 else:
216 journal = []
217
218 return journal
219
220 @LoginRequired()
306 @LoginRequired()
221 @NotAnonymous()
307 @NotAnonymous()
222 def toggle_following(self):
308 def toggle_following(self):
223 cur_token = request.POST.get('auth_token')
309 cur_token = request.POST.get('auth_token')
224 token = h.get_token()
310 token = h.get_token()
225 if cur_token == token:
311 if cur_token == token:
226
312
227 user_id = request.POST.get('follows_user_id')
313 user_id = request.POST.get('follows_user_id')
228 if user_id:
314 if user_id:
229 try:
315 try:
230 self.scm_model.toggle_following_user(user_id,
316 self.scm_model.toggle_following_user(user_id,
231 self.rhodecode_user.user_id)
317 self.rhodecode_user.user_id)
232 Session.commit()
318 Session.commit()
233 return 'ok'
319 return 'ok'
234 except Exception:
320 except Exception:
235 raise HTTPBadRequest()
321 raise HTTPBadRequest()
236
322
237 repo_id = request.POST.get('follows_repo_id')
323 repo_id = request.POST.get('follows_repo_id')
238 if repo_id:
324 if repo_id:
239 try:
325 try:
240 self.scm_model.toggle_following_repo(repo_id,
326 self.scm_model.toggle_following_repo(repo_id,
241 self.rhodecode_user.user_id)
327 self.rhodecode_user.user_id)
242 Session.commit()
328 Session.commit()
243 return 'ok'
329 return 'ok'
244 except Exception:
330 except Exception:
245 raise HTTPBadRequest()
331 raise HTTPBadRequest()
246
332
247 log.debug('token mismatch %s vs %s' % (cur_token, token))
333 log.debug('token mismatch %s vs %s' % (cur_token, token))
248 raise HTTPBadRequest()
334 raise HTTPBadRequest()
249
335
250 @LoginRequired()
336 @LoginRequired()
251 def public_journal(self):
337 def public_journal(self):
252 # Return a rendered template
338 # Return a rendered template
253 p = safe_int(request.GET.get('page', 1), 1)
339 p = safe_int(request.GET.get('page', 1), 1)
254
340
255 c.following = self.sa.query(UserFollowing)\
341 c.following = self.sa.query(UserFollowing)\
256 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
342 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
257 .options(joinedload(UserFollowing.follows_repository))\
343 .options(joinedload(UserFollowing.follows_repository))\
258 .all()
344 .all()
259
345
260 journal = self._get_journal_data(c.following)
346 journal = self._get_journal_data(c.following)
261
347
262 c.journal_pager = Page(journal, page=p, items_per_page=20)
348 c.journal_pager = Page(journal, page=p, items_per_page=20)
263
349
264 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
350 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
265
351
266 c.journal_data = render('journal/journal_data.html')
352 c.journal_data = render('journal/journal_data.html')
267 if request.environ.get('HTTP_X_PARTIAL_XHR'):
353 if request.environ.get('HTTP_X_PARTIAL_XHR'):
268 return c.journal_data
354 return c.journal_data
269 return render('journal/public_journal.html')
355 return render('journal/public_journal.html')
270
356
271 def _atom_feed(self, repos, public=True):
272 journal = self._get_journal_data(repos)
273 if public:
274 _link = url('public_journal_atom', qualified=True)
275 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
276 'atom feed')
277 else:
278 _link = url('journal_atom', qualified=True)
279 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
280
281 feed = Atom1Feed(title=_desc,
282 link=_link,
283 description=_desc,
284 language=self.language,
285 ttl=self.ttl)
286
287 for entry in journal[:self.feed_nr]:
288 user = entry.user
289 if user is None:
290 #fix deleted users
291 user = AttributeDict({'short_contact': entry.username,
292 'email': '',
293 'full_contact': ''})
294 action, action_extra, ico = h.action_parser(entry, feed=True)
295 title = "%s - %s %s" % (user.short_contact, action(),
296 entry.repository.repo_name)
297 desc = action_extra()
298 _url = None
299 if entry.repository is not None:
300 _url = url('changelog_home',
301 repo_name=entry.repository.repo_name,
302 qualified=True)
303
304 feed.add_item(title=title,
305 pubdate=entry.action_date,
306 link=_url or url('', qualified=True),
307 author_email=user.email,
308 author_name=user.full_contact,
309 description=desc)
310
311 response.content_type = feed.mime_type
312 return feed.writeString('utf-8')
313
314 def _rss_feed(self, repos, public=True):
315 journal = self._get_journal_data(repos)
316 if public:
317 _link = url('public_journal_atom', qualified=True)
318 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
319 'rss feed')
320 else:
321 _link = url('journal_atom', qualified=True)
322 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
323
324 feed = Rss201rev2Feed(title=_desc,
325 link=_link,
326 description=_desc,
327 language=self.language,
328 ttl=self.ttl)
329
330 for entry in journal[:self.feed_nr]:
331 user = entry.user
332 if user is None:
333 #fix deleted users
334 user = AttributeDict({'short_contact': entry.username,
335 'email': '',
336 'full_contact': ''})
337 action, action_extra, ico = h.action_parser(entry, feed=True)
338 title = "%s - %s %s" % (user.short_contact, action(),
339 entry.repository.repo_name)
340 desc = action_extra()
341 _url = None
342 if entry.repository is not None:
343 _url = url('changelog_home',
344 repo_name=entry.repository.repo_name,
345 qualified=True)
346
347 feed.add_item(title=title,
348 pubdate=entry.action_date,
349 link=_url or url('', qualified=True),
350 author_email=user.email,
351 author_name=user.full_contact,
352 description=desc)
353
354 response.content_type = feed.mime_type
355 return feed.writeString('utf-8')
356
357 @LoginRequired(api_access=True)
357 @LoginRequired(api_access=True)
358 def public_journal_atom(self):
358 def public_journal_atom(self):
359 """
359 """
360 Produce an atom-1.0 feed via feedgenerator module
360 Produce an atom-1.0 feed via feedgenerator module
361 """
361 """
362 c.following = self.sa.query(UserFollowing)\
362 c.following = self.sa.query(UserFollowing)\
363 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
363 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
364 .options(joinedload(UserFollowing.follows_repository))\
364 .options(joinedload(UserFollowing.follows_repository))\
365 .all()
365 .all()
366
366
367 return self._atom_feed(c.following)
367 return self._atom_feed(c.following)
368
368
369 @LoginRequired(api_access=True)
369 @LoginRequired(api_access=True)
370 def public_journal_rss(self):
370 def public_journal_rss(self):
371 """
371 """
372 Produce an rss2 feed via feedgenerator module
372 Produce an rss2 feed via feedgenerator module
373 """
373 """
374 c.following = self.sa.query(UserFollowing)\
374 c.following = self.sa.query(UserFollowing)\
375 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
375 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
376 .options(joinedload(UserFollowing.follows_repository))\
376 .options(joinedload(UserFollowing.follows_repository))\
377 .all()
377 .all()
378
378
379 return self._rss_feed(c.following)
379 return self._rss_feed(c.following)
@@ -1,521 +1,542 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
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 import logging
25 import logging
26 import traceback
26 import traceback
27 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 NotAnonymous
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.utils import action_logger, jsonify
45 from rhodecode.lib.vcs.utils import safe_str
45 from rhodecode.lib.vcs.utils import safe_str
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.lib.diffs import LimitedDiffContainer
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 ChangesetComment
50 ChangesetComment
51 from rhodecode.model.pull_request import PullRequestModel
51 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.comment import ChangesetCommentsModel
54 from rhodecode.model.comment import ChangesetCommentsModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 from rhodecode.model.forms import PullRequestForm
56 from rhodecode.model.forms import PullRequestForm
57 from mercurial import scmutil
57 from mercurial import scmutil
58 from rhodecode.lib.utils2 import safe_int
58 from rhodecode.lib.utils2 import safe_int
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class PullrequestsController(BaseRepoController):
63 class PullrequestsController(BaseRepoController):
64
64
65 @LoginRequired()
66 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
67 'repository.admin')
68 def __before__(self):
65 def __before__(self):
69 super(PullrequestsController, self).__before__()
66 super(PullrequestsController, self).__before__()
70 repo_model = RepoModel()
67 repo_model = RepoModel()
71 c.users_array = repo_model.get_users_js()
68 c.users_array = repo_model.get_users_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
73
70
74 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
71 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
75 """return a structure with repo's interesting changesets, suitable for
72 """return a structure with repo's interesting changesets, suitable for
76 the selectors in pullrequest.html"""
73 the selectors in pullrequest.html"""
77 # list named branches that has been merged to this named branch - it should probably merge back
74 # list named branches that has been merged to this named branch - it should probably merge back
78 peers = []
75 peers = []
79
76
80 if rev:
77 if rev:
81 rev = safe_str(rev)
78 rev = safe_str(rev)
82
79
83 if branch_rev:
80 if branch_rev:
84 branch_rev = safe_str(branch_rev)
81 branch_rev = safe_str(branch_rev)
85 # not restricting to merge() would also get branch point and be better
82 # not restricting to merge() would also get branch point and be better
86 # (especially because it would get the branch point) ... but is currently too expensive
83 # (especially because it would get the branch point) ... but is currently too expensive
87 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
84 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
88 (branch_rev, branch_rev)]
85 (branch_rev, branch_rev)]
89 otherbranches = {}
86 otherbranches = {}
90 for i in scmutil.revrange(repo._repo, revs):
87 for i in scmutil.revrange(repo._repo, revs):
91 cs = repo.get_changeset(i)
88 cs = repo.get_changeset(i)
92 otherbranches[cs.branch] = cs.raw_id
89 otherbranches[cs.branch] = cs.raw_id
93 for branch, node in otherbranches.iteritems():
90 for branch, node in otherbranches.iteritems():
94 selected = 'branch:%s:%s' % (branch, node)
91 selected = 'branch:%s:%s' % (branch, node)
95 peers.append((selected, branch))
92 peers.append((selected, branch))
96
93
97 selected = None
94 selected = None
98 branches = []
95 branches = []
99 for branch, branchrev in repo.branches.iteritems():
96 for branch, branchrev in repo.branches.iteritems():
100 n = 'branch:%s:%s' % (branch, branchrev)
97 n = 'branch:%s:%s' % (branch, branchrev)
101 branches.append((n, branch))
98 branches.append((n, branch))
102 if rev == branchrev:
99 if rev == branchrev:
103 selected = n
100 selected = n
104 bookmarks = []
101 bookmarks = []
105 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
102 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
106 n = 'book:%s:%s' % (bookmark, bookmarkrev)
103 n = 'book:%s:%s' % (bookmark, bookmarkrev)
107 bookmarks.append((n, bookmark))
104 bookmarks.append((n, bookmark))
108 if rev == bookmarkrev:
105 if rev == bookmarkrev:
109 selected = n
106 selected = n
110 tags = []
107 tags = []
111 for tag, tagrev in repo.tags.iteritems():
108 for tag, tagrev in repo.tags.iteritems():
112 n = 'tag:%s:%s' % (tag, tagrev)
109 n = 'tag:%s:%s' % (tag, tagrev)
113 tags.append((n, tag))
110 tags.append((n, tag))
114 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
111 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
115 selected = n
112 selected = n
116
113
117 # prio 1: rev was selected as existing entry above
114 # prio 1: rev was selected as existing entry above
118
115
119 # prio 2: create special entry for rev; rev _must_ be used
116 # prio 2: create special entry for rev; rev _must_ be used
120 specials = []
117 specials = []
121 if rev and selected is None:
118 if rev and selected is None:
122 selected = 'rev:%s:%s' % (rev, rev)
119 selected = 'rev:%s:%s' % (rev, rev)
123 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
120 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
124
121
125 # prio 3: most recent peer branch
122 # prio 3: most recent peer branch
126 if peers and not selected:
123 if peers and not selected:
127 selected = peers[0][0][0]
124 selected = peers[0][0][0]
128
125
129 # prio 4: tip revision
126 # prio 4: tip revision
130 if not selected:
127 if not selected:
131 selected = 'tag:tip:%s' % repo.tags['tip']
128 selected = 'tag:tip:%s' % repo.tags['tip']
132
129
133 groups = [(specials, _("Special")),
130 groups = [(specials, _("Special")),
134 (peers, _("Peer branches")),
131 (peers, _("Peer branches")),
135 (bookmarks, _("Bookmarks")),
132 (bookmarks, _("Bookmarks")),
136 (branches, _("Branches")),
133 (branches, _("Branches")),
137 (tags, _("Tags")),
134 (tags, _("Tags")),
138 ]
135 ]
139 return [g for g in groups if g[0]], selected
136 return [g for g in groups if g[0]], selected
140
137
141 def _get_is_allowed_change_status(self, pull_request):
138 def _get_is_allowed_change_status(self, pull_request):
142 owner = self.rhodecode_user.user_id == pull_request.user_id
139 owner = self.rhodecode_user.user_id == pull_request.user_id
143 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
140 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
144 pull_request.reviewers]
141 pull_request.reviewers]
145 return (self.rhodecode_user.admin or owner or reviewer)
142 return (self.rhodecode_user.admin or owner or reviewer)
146
143
147 def show_all(self, repo_name):
148 c.pull_requests = PullRequestModel().get_all(repo_name)
149 c.repo_name = repo_name
150 p = safe_int(request.GET.get('page', 1), 1)
151
152 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
153
154 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
155
156 if request.environ.get('HTTP_X_PARTIAL_XHR'):
157 return c.pullrequest_data
158
159 return render('/pullrequests/pullrequest_show_all.html')
160
161 @NotAnonymous()
162 def index(self):
163 org_repo = c.rhodecode_db_repo
164
165 if org_repo.scm_instance.alias != 'hg':
166 log.error('Review not available for GIT REPOS')
167 raise HTTPNotFound
168
169 try:
170 org_repo.scm_instance.get_changeset()
171 except EmptyRepositoryError, e:
172 h.flash(h.literal(_('There are no changesets yet')),
173 category='warning')
174 redirect(url('summary_home', repo_name=org_repo.repo_name))
175
176 org_rev = request.GET.get('rev_end')
177 # rev_start is not directly useful - its parent could however be used
178 # as default for other and thus give a simple compare view
179 #other_rev = request.POST.get('rev_start')
180
181 c.org_repos = []
182 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
183 c.default_org_repo = org_repo.repo_name
184 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
185
186 c.other_repos = []
187 other_repos_info = {}
188
189 def add_other_repo(repo, branch_rev=None):
190 if repo.repo_name in other_repos_info: # shouldn't happen
191 return
192 c.other_repos.append((repo.repo_name, repo.repo_name))
193 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
194 other_repos_info[repo.repo_name] = {
195 'user': dict(user_id=repo.user.user_id,
196 username=repo.user.username,
197 firstname=repo.user.firstname,
198 lastname=repo.user.lastname,
199 gravatar_link=h.gravatar_url(repo.user.email, 14)),
200 'description': repo.description.split('\n', 1)[0],
201 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
202 }
203
204 # add org repo to other so we can open pull request against peer branches on itself
205 add_other_repo(org_repo, branch_rev=org_rev)
206 c.default_other_repo = org_repo.repo_name
207
208 # gather forks and add to this list ... even though it is rare to
209 # request forks to pull from their parent
210 for fork in org_repo.forks:
211 add_other_repo(fork)
212
213 # add parents of this fork also, but only if it's not empty
214 if org_repo.parent and org_repo.parent.scm_instance.revisions:
215 add_other_repo(org_repo.parent)
216 c.default_other_repo = org_repo.parent.repo_name
217
218 c.default_other_repo_info = other_repos_info[c.default_other_repo]
219 c.other_repos_info = json.dumps(other_repos_info)
220
221 return render('/pullrequests/pullrequest.html')
222
223 @NotAnonymous()
224 def create(self, repo_name):
225 repo = RepoModel()._get_repo(repo_name)
226 try:
227 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
228 except formencode.Invalid, errors:
229 log.error(traceback.format_exc())
230 if errors.error_dict.get('revisions'):
231 msg = 'Revisions: %s' % errors.error_dict['revisions']
232 elif errors.error_dict.get('pullrequest_title'):
233 msg = _('Pull request requires a title with min. 3 chars')
234 else:
235 msg = _('Error creating pull request')
236
237 h.flash(msg, 'error')
238 return redirect(url('pullrequest_home', repo_name=repo_name))
239
240 org_repo = _form['org_repo']
241 org_ref = 'rev:merge:%s' % _form['merge_rev']
242 other_repo = _form['other_repo']
243 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
244 revisions = reversed(_form['revisions'])
245 reviewers = _form['review_members']
246
247 title = _form['pullrequest_title']
248 description = _form['pullrequest_desc']
249
250 try:
251 pull_request = PullRequestModel().create(
252 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
253 other_ref, revisions, reviewers, title, description
254 )
255 Session().commit()
256 h.flash(_('Successfully opened new pull request'),
257 category='success')
258 except Exception:
259 h.flash(_('Error occurred during sending pull request'),
260 category='error')
261 log.error(traceback.format_exc())
262 return redirect(url('pullrequest_home', repo_name=repo_name))
263
264 return redirect(url('pullrequest_show', repo_name=other_repo,
265 pull_request_id=pull_request.pull_request_id))
266
267 @NotAnonymous()
268 @jsonify
269 def update(self, repo_name, pull_request_id):
270 pull_request = PullRequest.get_or_404(pull_request_id)
271 if pull_request.is_closed():
272 raise HTTPForbidden()
273 #only owner or admin can update it
274 owner = pull_request.author.user_id == c.rhodecode_user.user_id
275 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
276 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
277 request.POST.get('reviewers_ids', '').split(',')))
278
279 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
280 Session().commit()
281 return True
282 raise HTTPForbidden()
283
284 @NotAnonymous()
285 @jsonify
286 def delete(self, repo_name, pull_request_id):
287 pull_request = PullRequest.get_or_404(pull_request_id)
288 #only owner can delete it !
289 if pull_request.author.user_id == c.rhodecode_user.user_id:
290 PullRequestModel().delete(pull_request)
291 Session().commit()
292 h.flash(_('Successfully deleted pull request'),
293 category='success')
294 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
295 raise HTTPForbidden()
296
297 def _load_compare_data(self, pull_request, enable_comments=True):
144 def _load_compare_data(self, pull_request, enable_comments=True):
298 """
145 """
299 Load context data needed for generating compare diff
146 Load context data needed for generating compare diff
300
147
301 :param pull_request:
148 :param pull_request:
302 :type pull_request:
149 :type pull_request:
303 """
150 """
304 org_repo = pull_request.org_repo
151 org_repo = pull_request.org_repo
305 (org_ref_type,
152 (org_ref_type,
306 org_ref_name,
153 org_ref_name,
307 org_ref_rev) = pull_request.org_ref.split(':')
154 org_ref_rev) = pull_request.org_ref.split(':')
308
155
309 other_repo = org_repo
156 other_repo = org_repo
310 (other_ref_type,
157 (other_ref_type,
311 other_ref_name,
158 other_ref_name,
312 other_ref_rev) = pull_request.other_ref.split(':')
159 other_ref_rev) = pull_request.other_ref.split(':')
313
160
314 # despite opening revisions for bookmarks/branches/tags, we always
161 # despite opening revisions for bookmarks/branches/tags, we always
315 # convert this to rev to prevent changes after bookmark or branch change
162 # convert this to rev to prevent changes after bookmark or branch change
316 org_ref = ('rev', org_ref_rev)
163 org_ref = ('rev', org_ref_rev)
317 other_ref = ('rev', other_ref_rev)
164 other_ref = ('rev', other_ref_rev)
318
165
319 c.org_repo = org_repo
166 c.org_repo = org_repo
320 c.other_repo = other_repo
167 c.other_repo = other_repo
321
168
322 c.fulldiff = fulldiff = request.GET.get('fulldiff')
169 c.fulldiff = fulldiff = request.GET.get('fulldiff')
323
170
324 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
171 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
325
172
326 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
173 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
327
174
328 c.org_ref = org_ref[1]
175 c.org_ref = org_ref[1]
329 c.org_ref_type = org_ref[0]
176 c.org_ref_type = org_ref[0]
330 c.other_ref = other_ref[1]
177 c.other_ref = other_ref[1]
331 c.other_ref_type = other_ref[0]
178 c.other_ref_type = other_ref[0]
332
179
333 diff_limit = self.cut_off_limit if not fulldiff else None
180 diff_limit = self.cut_off_limit if not fulldiff else None
334
181
335 # we swap org/other ref since we run a simple diff on one repo
182 # we swap org/other ref since we run a simple diff on one repo
336 log.debug('running diff between %s@%s and %s@%s'
183 log.debug('running diff between %s@%s and %s@%s'
337 % (org_repo.scm_instance.path, org_ref,
184 % (org_repo.scm_instance.path, org_ref,
338 other_repo.scm_instance.path, other_ref))
185 other_repo.scm_instance.path, other_ref))
339 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
186 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
340
187
341 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
188 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
342 diff_limit=diff_limit)
189 diff_limit=diff_limit)
343 _parsed = diff_processor.prepare()
190 _parsed = diff_processor.prepare()
344
191
345 c.limited_diff = False
192 c.limited_diff = False
346 if isinstance(_parsed, LimitedDiffContainer):
193 if isinstance(_parsed, LimitedDiffContainer):
347 c.limited_diff = True
194 c.limited_diff = True
348
195
349 c.files = []
196 c.files = []
350 c.changes = {}
197 c.changes = {}
351 c.lines_added = 0
198 c.lines_added = 0
352 c.lines_deleted = 0
199 c.lines_deleted = 0
353 for f in _parsed:
200 for f in _parsed:
354 st = f['stats']
201 st = f['stats']
355 if st[0] != 'b':
202 if st[0] != 'b':
356 c.lines_added += st[0]
203 c.lines_added += st[0]
357 c.lines_deleted += st[1]
204 c.lines_deleted += st[1]
358 fid = h.FID('', f['filename'])
205 fid = h.FID('', f['filename'])
359 c.files.append([fid, f['operation'], f['filename'], f['stats']])
206 c.files.append([fid, f['operation'], f['filename'], f['stats']])
360 diff = diff_processor.as_html(enable_comments=enable_comments,
207 diff = diff_processor.as_html(enable_comments=enable_comments,
361 parsed_lines=[f])
208 parsed_lines=[f])
362 c.changes[fid] = [f['operation'], f['filename'], diff]
209 c.changes[fid] = [f['operation'], f['filename'], diff]
363
210
211 @LoginRequired()
212 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
213 'repository.admin')
214 def show_all(self, repo_name):
215 c.pull_requests = PullRequestModel().get_all(repo_name)
216 c.repo_name = repo_name
217 p = safe_int(request.GET.get('page', 1), 1)
218
219 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
220
221 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
222
223 if request.environ.get('HTTP_X_PARTIAL_XHR'):
224 return c.pullrequest_data
225
226 return render('/pullrequests/pullrequest_show_all.html')
227
228 @LoginRequired()
229 @NotAnonymous()
230 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
231 'repository.admin')
232 def index(self):
233 org_repo = c.rhodecode_db_repo
234
235 if org_repo.scm_instance.alias != 'hg':
236 log.error('Review not available for GIT REPOS')
237 raise HTTPNotFound
238
239 try:
240 org_repo.scm_instance.get_changeset()
241 except EmptyRepositoryError, e:
242 h.flash(h.literal(_('There are no changesets yet')),
243 category='warning')
244 redirect(url('summary_home', repo_name=org_repo.repo_name))
245
246 org_rev = request.GET.get('rev_end')
247 # rev_start is not directly useful - its parent could however be used
248 # as default for other and thus give a simple compare view
249 #other_rev = request.POST.get('rev_start')
250
251 c.org_repos = []
252 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
253 c.default_org_repo = org_repo.repo_name
254 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
255
256 c.other_repos = []
257 other_repos_info = {}
258
259 def add_other_repo(repo, branch_rev=None):
260 if repo.repo_name in other_repos_info: # shouldn't happen
261 return
262 c.other_repos.append((repo.repo_name, repo.repo_name))
263 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
264 other_repos_info[repo.repo_name] = {
265 'user': dict(user_id=repo.user.user_id,
266 username=repo.user.username,
267 firstname=repo.user.firstname,
268 lastname=repo.user.lastname,
269 gravatar_link=h.gravatar_url(repo.user.email, 14)),
270 'description': repo.description.split('\n', 1)[0],
271 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
272 }
273
274 # add org repo to other so we can open pull request against peer branches on itself
275 add_other_repo(org_repo, branch_rev=org_rev)
276 c.default_other_repo = org_repo.repo_name
277
278 # gather forks and add to this list ... even though it is rare to
279 # request forks to pull from their parent
280 for fork in org_repo.forks:
281 add_other_repo(fork)
282
283 # add parents of this fork also, but only if it's not empty
284 if org_repo.parent and org_repo.parent.scm_instance.revisions:
285 add_other_repo(org_repo.parent)
286 c.default_other_repo = org_repo.parent.repo_name
287
288 c.default_other_repo_info = other_repos_info[c.default_other_repo]
289 c.other_repos_info = json.dumps(other_repos_info)
290
291 return render('/pullrequests/pullrequest.html')
292
293 @LoginRequired()
294 @NotAnonymous()
295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
296 'repository.admin')
297 def create(self, repo_name):
298 repo = RepoModel()._get_repo(repo_name)
299 try:
300 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
301 except formencode.Invalid, errors:
302 log.error(traceback.format_exc())
303 if errors.error_dict.get('revisions'):
304 msg = 'Revisions: %s' % errors.error_dict['revisions']
305 elif errors.error_dict.get('pullrequest_title'):
306 msg = _('Pull request requires a title with min. 3 chars')
307 else:
308 msg = _('Error creating pull request')
309
310 h.flash(msg, 'error')
311 return redirect(url('pullrequest_home', repo_name=repo_name))
312
313 org_repo = _form['org_repo']
314 org_ref = 'rev:merge:%s' % _form['merge_rev']
315 other_repo = _form['other_repo']
316 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
317 revisions = reversed(_form['revisions'])
318 reviewers = _form['review_members']
319
320 title = _form['pullrequest_title']
321 description = _form['pullrequest_desc']
322
323 try:
324 pull_request = PullRequestModel().create(
325 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
326 other_ref, revisions, reviewers, title, description
327 )
328 Session().commit()
329 h.flash(_('Successfully opened new pull request'),
330 category='success')
331 except Exception:
332 h.flash(_('Error occurred during sending pull request'),
333 category='error')
334 log.error(traceback.format_exc())
335 return redirect(url('pullrequest_home', repo_name=repo_name))
336
337 return redirect(url('pullrequest_show', repo_name=other_repo,
338 pull_request_id=pull_request.pull_request_id))
339
340 @LoginRequired()
341 @NotAnonymous()
342 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
343 'repository.admin')
344 @jsonify
345 def update(self, repo_name, pull_request_id):
346 pull_request = PullRequest.get_or_404(pull_request_id)
347 if pull_request.is_closed():
348 raise HTTPForbidden()
349 #only owner or admin can update it
350 owner = pull_request.author.user_id == c.rhodecode_user.user_id
351 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
352 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
353 request.POST.get('reviewers_ids', '').split(',')))
354
355 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
356 Session().commit()
357 return True
358 raise HTTPForbidden()
359
360 @LoginRequired()
361 @NotAnonymous()
362 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
363 'repository.admin')
364 @jsonify
365 def delete(self, repo_name, pull_request_id):
366 pull_request = PullRequest.get_or_404(pull_request_id)
367 #only owner can delete it !
368 if pull_request.author.user_id == c.rhodecode_user.user_id:
369 PullRequestModel().delete(pull_request)
370 Session().commit()
371 h.flash(_('Successfully deleted pull request'),
372 category='success')
373 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
374 raise HTTPForbidden()
375
376 @LoginRequired()
377 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
378 'repository.admin')
364 def show(self, repo_name, pull_request_id):
379 def show(self, repo_name, pull_request_id):
365 repo_model = RepoModel()
380 repo_model = RepoModel()
366 c.users_array = repo_model.get_users_js()
381 c.users_array = repo_model.get_users_js()
367 c.users_groups_array = repo_model.get_users_groups_js()
382 c.users_groups_array = repo_model.get_users_groups_js()
368 c.pull_request = PullRequest.get_or_404(pull_request_id)
383 c.pull_request = PullRequest.get_or_404(pull_request_id)
369 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
384 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
370 cc_model = ChangesetCommentsModel()
385 cc_model = ChangesetCommentsModel()
371 cs_model = ChangesetStatusModel()
386 cs_model = ChangesetStatusModel()
372 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
387 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
373 pull_request=c.pull_request,
388 pull_request=c.pull_request,
374 with_revisions=True)
389 with_revisions=True)
375
390
376 cs_statuses = defaultdict(list)
391 cs_statuses = defaultdict(list)
377 for st in _cs_statuses:
392 for st in _cs_statuses:
378 cs_statuses[st.author.username] += [st]
393 cs_statuses[st.author.username] += [st]
379
394
380 c.pull_request_reviewers = []
395 c.pull_request_reviewers = []
381 c.pull_request_pending_reviewers = []
396 c.pull_request_pending_reviewers = []
382 for o in c.pull_request.reviewers:
397 for o in c.pull_request.reviewers:
383 st = cs_statuses.get(o.user.username, None)
398 st = cs_statuses.get(o.user.username, None)
384 if st:
399 if st:
385 sorter = lambda k: k.version
400 sorter = lambda k: k.version
386 st = [(x, list(y)[0])
401 st = [(x, list(y)[0])
387 for x, y in (groupby(sorted(st, key=sorter), sorter))]
402 for x, y in (groupby(sorted(st, key=sorter), sorter))]
388 else:
403 else:
389 c.pull_request_pending_reviewers.append(o.user)
404 c.pull_request_pending_reviewers.append(o.user)
390 c.pull_request_reviewers.append([o.user, st])
405 c.pull_request_reviewers.append([o.user, st])
391
406
392 # pull_requests repo_name we opened it against
407 # pull_requests repo_name we opened it against
393 # ie. other_repo must match
408 # ie. other_repo must match
394 if repo_name != c.pull_request.other_repo.repo_name:
409 if repo_name != c.pull_request.other_repo.repo_name:
395 raise HTTPNotFound
410 raise HTTPNotFound
396
411
397 # load compare data into template context
412 # load compare data into template context
398 enable_comments = not c.pull_request.is_closed()
413 enable_comments = not c.pull_request.is_closed()
399 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
414 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
400
415
401 # inline comments
416 # inline comments
402 c.inline_cnt = 0
417 c.inline_cnt = 0
403 c.inline_comments = cc_model.get_inline_comments(
418 c.inline_comments = cc_model.get_inline_comments(
404 c.rhodecode_db_repo.repo_id,
419 c.rhodecode_db_repo.repo_id,
405 pull_request=pull_request_id)
420 pull_request=pull_request_id)
406 # count inline comments
421 # count inline comments
407 for __, lines in c.inline_comments:
422 for __, lines in c.inline_comments:
408 for comments in lines.values():
423 for comments in lines.values():
409 c.inline_cnt += len(comments)
424 c.inline_cnt += len(comments)
410 # comments
425 # comments
411 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
426 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
412 pull_request=pull_request_id)
427 pull_request=pull_request_id)
413
428
414 try:
429 try:
415 cur_status = c.statuses[c.pull_request.revisions[0]][0]
430 cur_status = c.statuses[c.pull_request.revisions[0]][0]
416 except Exception:
431 except Exception:
417 log.error(traceback.format_exc())
432 log.error(traceback.format_exc())
418 cur_status = 'undefined'
433 cur_status = 'undefined'
419 if c.pull_request.is_closed() and 0:
434 if c.pull_request.is_closed() and 0:
420 c.current_changeset_status = cur_status
435 c.current_changeset_status = cur_status
421 else:
436 else:
422 # changeset(pull-request) status calulation based on reviewers
437 # changeset(pull-request) status calulation based on reviewers
423 c.current_changeset_status = cs_model.calculate_status(
438 c.current_changeset_status = cs_model.calculate_status(
424 c.pull_request_reviewers,
439 c.pull_request_reviewers,
425 )
440 )
426 c.changeset_statuses = ChangesetStatus.STATUSES
441 c.changeset_statuses = ChangesetStatus.STATUSES
427
442
428 c.as_form = False
443 c.as_form = False
429 c.ancestor = None # there is one - but right here we don't know which
444 c.ancestor = None # there is one - but right here we don't know which
430 return render('/pullrequests/pullrequest_show.html')
445 return render('/pullrequests/pullrequest_show.html')
431
446
447 @LoginRequired()
432 @NotAnonymous()
448 @NotAnonymous()
449 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
450 'repository.admin')
433 @jsonify
451 @jsonify
434 def comment(self, repo_name, pull_request_id):
452 def comment(self, repo_name, pull_request_id):
435 pull_request = PullRequest.get_or_404(pull_request_id)
453 pull_request = PullRequest.get_or_404(pull_request_id)
436 if pull_request.is_closed():
454 if pull_request.is_closed():
437 raise HTTPForbidden()
455 raise HTTPForbidden()
438
456
439 status = request.POST.get('changeset_status')
457 status = request.POST.get('changeset_status')
440 change_status = request.POST.get('change_changeset_status')
458 change_status = request.POST.get('change_changeset_status')
441 text = request.POST.get('text')
459 text = request.POST.get('text')
442 close_pr = request.POST.get('save_close')
460 close_pr = request.POST.get('save_close')
443
461
444 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
462 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
445 if status and change_status and allowed_to_change_status:
463 if status and change_status and allowed_to_change_status:
446 _def = (_('Status change -> %s')
464 _def = (_('Status change -> %s')
447 % ChangesetStatus.get_status_lbl(status))
465 % ChangesetStatus.get_status_lbl(status))
448 if close_pr:
466 if close_pr:
449 _def = _('Closing with') + ' ' + _def
467 _def = _('Closing with') + ' ' + _def
450 text = text or _def
468 text = text or _def
451 comm = ChangesetCommentsModel().create(
469 comm = ChangesetCommentsModel().create(
452 text=text,
470 text=text,
453 repo=c.rhodecode_db_repo.repo_id,
471 repo=c.rhodecode_db_repo.repo_id,
454 user=c.rhodecode_user.user_id,
472 user=c.rhodecode_user.user_id,
455 pull_request=pull_request_id,
473 pull_request=pull_request_id,
456 f_path=request.POST.get('f_path'),
474 f_path=request.POST.get('f_path'),
457 line_no=request.POST.get('line'),
475 line_no=request.POST.get('line'),
458 status_change=(ChangesetStatus.get_status_lbl(status)
476 status_change=(ChangesetStatus.get_status_lbl(status)
459 if status and change_status
477 if status and change_status
460 and allowed_to_change_status else None),
478 and allowed_to_change_status else None),
461 closing_pr=close_pr
479 closing_pr=close_pr
462 )
480 )
463
481
464 action_logger(self.rhodecode_user,
482 action_logger(self.rhodecode_user,
465 'user_commented_pull_request:%s' % pull_request_id,
483 'user_commented_pull_request:%s' % pull_request_id,
466 c.rhodecode_db_repo, self.ip_addr, self.sa)
484 c.rhodecode_db_repo, self.ip_addr, self.sa)
467
485
468 if allowed_to_change_status:
486 if allowed_to_change_status:
469 # get status if set !
487 # get status if set !
470 if status and change_status:
488 if status and change_status:
471 ChangesetStatusModel().set_status(
489 ChangesetStatusModel().set_status(
472 c.rhodecode_db_repo.repo_id,
490 c.rhodecode_db_repo.repo_id,
473 status,
491 status,
474 c.rhodecode_user.user_id,
492 c.rhodecode_user.user_id,
475 comm,
493 comm,
476 pull_request=pull_request_id
494 pull_request=pull_request_id
477 )
495 )
478
496
479 if close_pr:
497 if close_pr:
480 if status in ['rejected', 'approved']:
498 if status in ['rejected', 'approved']:
481 PullRequestModel().close_pull_request(pull_request_id)
499 PullRequestModel().close_pull_request(pull_request_id)
482 action_logger(self.rhodecode_user,
500 action_logger(self.rhodecode_user,
483 'user_closed_pull_request:%s' % pull_request_id,
501 'user_closed_pull_request:%s' % pull_request_id,
484 c.rhodecode_db_repo, self.ip_addr, self.sa)
502 c.rhodecode_db_repo, self.ip_addr, self.sa)
485 else:
503 else:
486 h.flash(_('Closing pull request on other statuses than '
504 h.flash(_('Closing pull request on other statuses than '
487 'rejected or approved forbidden'),
505 'rejected or approved forbidden'),
488 category='warning')
506 category='warning')
489
507
490 Session().commit()
508 Session().commit()
491
509
492 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
510 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
493 return redirect(h.url('pullrequest_show', repo_name=repo_name,
511 return redirect(h.url('pullrequest_show', repo_name=repo_name,
494 pull_request_id=pull_request_id))
512 pull_request_id=pull_request_id))
495
513
496 data = {
514 data = {
497 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
515 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
498 }
516 }
499 if comm:
517 if comm:
500 c.co = comm
518 c.co = comm
501 data.update(comm.get_dict())
519 data.update(comm.get_dict())
502 data.update({'rendered_text':
520 data.update({'rendered_text':
503 render('changeset/changeset_comment_block.html')})
521 render('changeset/changeset_comment_block.html')})
504
522
505 return data
523 return data
506
524
525 @LoginRequired()
507 @NotAnonymous()
526 @NotAnonymous()
527 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
528 'repository.admin')
508 @jsonify
529 @jsonify
509 def delete_comment(self, repo_name, comment_id):
530 def delete_comment(self, repo_name, comment_id):
510 co = ChangesetComment.get(comment_id)
531 co = ChangesetComment.get(comment_id)
511 if co.pull_request.is_closed():
532 if co.pull_request.is_closed():
512 #don't allow deleting comments on closed pull request
533 #don't allow deleting comments on closed pull request
513 raise HTTPForbidden()
534 raise HTTPForbidden()
514
535
515 owner = co.author.user_id == c.rhodecode_user.user_id
536 owner = co.author.user_id == c.rhodecode_user.user_id
516 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
537 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
517 ChangesetCommentsModel().delete(comment=co)
538 ChangesetCommentsModel().delete(comment=co)
518 Session().commit()
539 Session().commit()
519 return True
540 return True
520 else:
541 else:
521 raise HTTPForbidden()
542 raise HTTPForbidden()
@@ -1,146 +1,146 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.search
3 rhodecode.controllers.search
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Search controller for RhodeCode
6 Search controller for RhodeCode
7
7
8 :created_on: Aug 7, 2010
8 :created_on: Aug 7, 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 import logging
25 import logging
26 import traceback
26 import traceback
27 import urllib
27 import urllib
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons import request, config, tmpl_context as c
29 from pylons import request, config, tmpl_context as c
30
30
31 from rhodecode.lib.auth import LoginRequired
31 from rhodecode.lib.auth import LoginRequired
32 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.lib.base import BaseRepoController, render
33 from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
33 from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
34 IDX_NAME, WhooshResultWrapper
34 IDX_NAME, WhooshResultWrapper
35
35
36 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
37 from webhelpers.util import update_params
37 from webhelpers.util import update_params
38
38
39 from whoosh.index import open_dir, EmptyIndexError
39 from whoosh.index import open_dir, EmptyIndexError
40 from whoosh.qparser import QueryParser, QueryParserError
40 from whoosh.qparser import QueryParser, QueryParserError
41 from whoosh.query import Phrase, Wildcard, Term, Prefix
41 from whoosh.query import Phrase, Wildcard, Term, Prefix
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.lib.utils2 import safe_str, safe_int
43 from rhodecode.lib.utils2 import safe_str, safe_int
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class SearchController(BaseRepoController):
49 class SearchController(BaseRepoController):
50
50
51 @LoginRequired()
52 def __before__(self):
51 def __before__(self):
53 super(SearchController, self).__before__()
52 super(SearchController, self).__before__()
54
53
54 @LoginRequired()
55 def index(self, repo_name=None):
55 def index(self, repo_name=None):
56 c.repo_name = repo_name
56 c.repo_name = repo_name
57 c.formated_results = []
57 c.formated_results = []
58 c.runtime = ''
58 c.runtime = ''
59 c.cur_query = request.GET.get('q', None)
59 c.cur_query = request.GET.get('q', None)
60 c.cur_type = request.GET.get('type', 'content')
60 c.cur_type = request.GET.get('type', 'content')
61 c.cur_search = search_type = {'content': 'content',
61 c.cur_search = search_type = {'content': 'content',
62 'commit': 'message',
62 'commit': 'message',
63 'path': 'path',
63 'path': 'path',
64 'repository': 'repository'
64 'repository': 'repository'
65 }.get(c.cur_type, 'content')
65 }.get(c.cur_type, 'content')
66
66
67 index_name = {
67 index_name = {
68 'content': IDX_NAME,
68 'content': IDX_NAME,
69 'commit': CHGSET_IDX_NAME,
69 'commit': CHGSET_IDX_NAME,
70 'path': IDX_NAME
70 'path': IDX_NAME
71 }.get(c.cur_type, IDX_NAME)
71 }.get(c.cur_type, IDX_NAME)
72
72
73 schema_defn = {
73 schema_defn = {
74 'content': SCHEMA,
74 'content': SCHEMA,
75 'commit': CHGSETS_SCHEMA,
75 'commit': CHGSETS_SCHEMA,
76 'path': SCHEMA
76 'path': SCHEMA
77 }.get(c.cur_type, SCHEMA)
77 }.get(c.cur_type, SCHEMA)
78
78
79 log.debug('IDX: %s' % index_name)
79 log.debug('IDX: %s' % index_name)
80 log.debug('SCHEMA: %s' % schema_defn)
80 log.debug('SCHEMA: %s' % schema_defn)
81
81
82 if c.cur_query:
82 if c.cur_query:
83 cur_query = c.cur_query.lower()
83 cur_query = c.cur_query.lower()
84 log.debug(cur_query)
84 log.debug(cur_query)
85
85
86 if c.cur_query:
86 if c.cur_query:
87 p = safe_int(request.GET.get('page', 1), 1)
87 p = safe_int(request.GET.get('page', 1), 1)
88 highlight_items = set()
88 highlight_items = set()
89 try:
89 try:
90 idx = open_dir(config['app_conf']['index_dir'],
90 idx = open_dir(config['app_conf']['index_dir'],
91 indexname=index_name)
91 indexname=index_name)
92 searcher = idx.searcher()
92 searcher = idx.searcher()
93
93
94 qp = QueryParser(search_type, schema=schema_defn)
94 qp = QueryParser(search_type, schema=schema_defn)
95 if c.repo_name:
95 if c.repo_name:
96 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
96 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
97 try:
97 try:
98 query = qp.parse(unicode(cur_query))
98 query = qp.parse(unicode(cur_query))
99 # extract words for highlight
99 # extract words for highlight
100 if isinstance(query, Phrase):
100 if isinstance(query, Phrase):
101 highlight_items.update(query.words)
101 highlight_items.update(query.words)
102 elif isinstance(query, Prefix):
102 elif isinstance(query, Prefix):
103 highlight_items.add(query.text)
103 highlight_items.add(query.text)
104 else:
104 else:
105 for i in query.all_terms():
105 for i in query.all_terms():
106 if i[0] in ['content', 'message']:
106 if i[0] in ['content', 'message']:
107 highlight_items.add(i[1])
107 highlight_items.add(i[1])
108
108
109 matcher = query.matcher(searcher)
109 matcher = query.matcher(searcher)
110
110
111 log.debug('query: %s' % query)
111 log.debug('query: %s' % query)
112 log.debug('hl terms: %s' % highlight_items)
112 log.debug('hl terms: %s' % highlight_items)
113 results = searcher.search(query)
113 results = searcher.search(query)
114 res_ln = len(results)
114 res_ln = len(results)
115 c.runtime = '%s results (%.3f seconds)' % (
115 c.runtime = '%s results (%.3f seconds)' % (
116 res_ln, results.runtime
116 res_ln, results.runtime
117 )
117 )
118
118
119 def url_generator(**kw):
119 def url_generator(**kw):
120 q = urllib.quote(safe_str(c.cur_query))
120 q = urllib.quote(safe_str(c.cur_query))
121 return update_params("?q=%s&type=%s" \
121 return update_params("?q=%s&type=%s" \
122 % (q, safe_str(c.cur_type)), **kw)
122 % (q, safe_str(c.cur_type)), **kw)
123 repo_location = RepoModel().repos_path
123 repo_location = RepoModel().repos_path
124 c.formated_results = Page(
124 c.formated_results = Page(
125 WhooshResultWrapper(search_type, searcher, matcher,
125 WhooshResultWrapper(search_type, searcher, matcher,
126 highlight_items, repo_location),
126 highlight_items, repo_location),
127 page=p,
127 page=p,
128 item_count=res_ln,
128 item_count=res_ln,
129 items_per_page=10,
129 items_per_page=10,
130 url=url_generator
130 url=url_generator
131 )
131 )
132
132
133 except QueryParserError:
133 except QueryParserError:
134 c.runtime = _('Invalid search query. Try quoting it.')
134 c.runtime = _('Invalid search query. Try quoting it.')
135 searcher.close()
135 searcher.close()
136 except (EmptyIndexError, IOError):
136 except (EmptyIndexError, IOError):
137 log.error(traceback.format_exc())
137 log.error(traceback.format_exc())
138 log.error('Empty Index data')
138 log.error('Empty Index data')
139 c.runtime = _('There is no index to search in. '
139 c.runtime = _('There is no index to search in. '
140 'Please run whoosh indexer')
140 'Please run whoosh indexer')
141 except (Exception):
141 except (Exception):
142 log.error(traceback.format_exc())
142 log.error(traceback.format_exc())
143 c.runtime = _('An error occurred during this search operation')
143 c.runtime = _('An error occurred during this search operation')
144
144
145 # Return a rendered template
145 # Return a rendered template
146 return render('/search/search.html')
146 return render('/search/search.html')
@@ -1,107 +1,107 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.shortlog
3 rhodecode.controllers.shortlog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Shortlog controller for rhodecode
6 Shortlog controller for rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 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
27
28 from pylons import tmpl_context as c, request, url
28 from pylons import tmpl_context as c, request, url
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.base import BaseRepoController, render
33 from rhodecode.lib.base import BaseRepoController, render
34 from rhodecode.lib.helpers import RepoPage
34 from rhodecode.lib.helpers import RepoPage
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36 from rhodecode.lib.utils2 import safe_int
36 from rhodecode.lib.utils2 import safe_int
37 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError, ChangesetError,\
37 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError, ChangesetError,\
38 RepositoryError
38 RepositoryError
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class ShortlogController(BaseRepoController):
43 class ShortlogController(BaseRepoController):
44
44
45 @LoginRequired()
46 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
47 'repository.admin')
48 def __before__(self):
45 def __before__(self):
49 super(ShortlogController, self).__before__()
46 super(ShortlogController, self).__before__()
50
47
51 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
48 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
52 """
49 """
53 Safe way to get changeset if error occur it redirects to tip with
50 Safe way to get changeset if error occur it redirects to tip with
54 proper message
51 proper message
55
52
56 :param rev: revision to fetch
53 :param rev: revision to fetch
57 :param repo_name: repo name to redirect after
54 :param repo_name: repo name to redirect after
58 """
55 """
59
56
60 try:
57 try:
61 return c.rhodecode_repo.get_changeset(rev)
58 return c.rhodecode_repo.get_changeset(rev)
62 except RepositoryError, e:
59 except RepositoryError, e:
63 h.flash(str(e), category='warning')
60 h.flash(str(e), category='warning')
64 redirect(h.url('shortlog_home', repo_name=repo_name))
61 redirect(h.url('shortlog_home', repo_name=repo_name))
65
62
63 @LoginRequired()
64 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
65 'repository.admin')
66 def index(self, repo_name, revision=None, f_path=None):
66 def index(self, repo_name, revision=None, f_path=None):
67 p = safe_int(request.GET.get('page', 1), 1)
67 p = safe_int(request.GET.get('page', 1), 1)
68 size = safe_int(request.GET.get('size', 20), 20)
68 size = safe_int(request.GET.get('size', 20), 20)
69 collection = c.rhodecode_repo
69 collection = c.rhodecode_repo
70 c.file_history = f_path
70 c.file_history = f_path
71
71
72 def url_generator(**kw):
72 def url_generator(**kw):
73 if f_path:
73 if f_path:
74 return url('shortlog_file_home', repo_name=repo_name,
74 return url('shortlog_file_home', repo_name=repo_name,
75 revision=revision, f_path=f_path, size=size, **kw)
75 revision=revision, f_path=f_path, size=size, **kw)
76 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
76 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
77
77
78 if f_path:
78 if f_path:
79 log.debug('generating shortlog for path %s' % f_path)
79 log.debug('generating shortlog for path %s' % f_path)
80 # get the history for the file !
80 # get the history for the file !
81 tip_cs = c.rhodecode_repo.get_changeset()
81 tip_cs = c.rhodecode_repo.get_changeset()
82 try:
82 try:
83 collection = tip_cs.get_file_history(f_path)
83 collection = tip_cs.get_file_history(f_path)
84 except (NodeDoesNotExistError, ChangesetError):
84 except (NodeDoesNotExistError, ChangesetError):
85 #this node is not present at tip !
85 #this node is not present at tip !
86 try:
86 try:
87 cs = self.__get_cs_or_redirect(revision, repo_name)
87 cs = self.__get_cs_or_redirect(revision, repo_name)
88 collection = cs.get_file_history(f_path)
88 collection = cs.get_file_history(f_path)
89 except RepositoryError, e:
89 except RepositoryError, e:
90 h.flash(str(e), category='warning')
90 h.flash(str(e), category='warning')
91 redirect(h.url('shortlog_home', repo_name=repo_name))
91 redirect(h.url('shortlog_home', repo_name=repo_name))
92 collection = list(reversed(collection))
92 collection = list(reversed(collection))
93
93
94 c.repo_changesets = RepoPage(collection, page=p,
94 c.repo_changesets = RepoPage(collection, page=p,
95 items_per_page=size, url=url_generator)
95 items_per_page=size, url=url_generator)
96 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
96 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
97 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
97 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
98
98
99 if not c.repo_changesets:
99 if not c.repo_changesets:
100 h.flash(_('There are no changesets yet'), category='warning')
100 h.flash(_('There are no changesets yet'), category='warning')
101 return redirect(url('summary_home', repo_name=repo_name))
101 return redirect(url('summary_home', repo_name=repo_name))
102
102
103 c.shortlog_data = render('shortlog/shortlog_data.html')
103 c.shortlog_data = render('shortlog/shortlog_data.html')
104 if request.environ.get('HTTP_X_PARTIAL_XHR'):
104 if request.environ.get('HTTP_X_PARTIAL_XHR'):
105 return c.shortlog_data
105 return c.shortlog_data
106 r = render('shortlog/shortlog.html')
106 r = render('shortlog/shortlog.html')
107 return r
107 return r
@@ -1,260 +1,264 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.summary
3 rhodecode.controllers.summary
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Summary controller for Rhodecode
6 Summary controller for Rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 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 traceback
26 import traceback
27 import calendar
27 import calendar
28 import logging
28 import logging
29 import urllib
29 import urllib
30 from time import mktime
30 from time import mktime
31 from datetime import timedelta, date
31 from datetime import timedelta, date
32 from urlparse import urlparse
32 from urlparse import urlparse
33
33
34 from pylons import tmpl_context as c, request, url, config
34 from pylons import tmpl_context as c, request, url, config
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36 from webob.exc import HTTPBadRequest
36 from webob.exc import HTTPBadRequest
37
37
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39
39
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.lib.compat import product
41 from rhodecode.lib.compat import product
42 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
42 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
43 NodeDoesNotExistError
43 NodeDoesNotExistError
44 from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
44 from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
45 from rhodecode.model.db import Statistics, CacheInvalidation
45 from rhodecode.model.db import Statistics, CacheInvalidation
46 from rhodecode.lib.utils import jsonify
46 from rhodecode.lib.utils import jsonify
47 from rhodecode.lib.utils2 import safe_unicode, safe_str
47 from rhodecode.lib.utils2 import safe_unicode, safe_str
48 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
48 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
49 NotAnonymous
49 NotAnonymous
50 from rhodecode.lib.base import BaseRepoController, render
50 from rhodecode.lib.base import BaseRepoController, render
51 from rhodecode.lib.vcs.backends.base import EmptyChangeset
51 from rhodecode.lib.vcs.backends.base import EmptyChangeset
52 from rhodecode.lib.markup_renderer import MarkupRenderer
52 from rhodecode.lib.markup_renderer import MarkupRenderer
53 from rhodecode.lib.celerylib import run_task
53 from rhodecode.lib.celerylib import run_task
54 from rhodecode.lib.celerylib.tasks import get_commits_stats
54 from rhodecode.lib.celerylib.tasks import get_commits_stats
55 from rhodecode.lib.helpers import RepoPage
55 from rhodecode.lib.helpers import RepoPage
56 from rhodecode.lib.compat import json, OrderedDict
56 from rhodecode.lib.compat import json, OrderedDict
57 from rhodecode.lib.vcs.nodes import FileNode
57 from rhodecode.lib.vcs.nodes import FileNode
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
61 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
62 sorted(list(product(ALL_READMES, ALL_EXTS)),
62 sorted(list(product(ALL_READMES, ALL_EXTS)),
63 key=lambda y:y[0][1] + y[1][1])]
63 key=lambda y:y[0][1] + y[1][1])]
64
64
65
65
66 class SummaryController(BaseRepoController):
66 class SummaryController(BaseRepoController):
67
67
68 def __before__(self):
69 super(SummaryController, self).__before__()
70
71 def _get_download_links(self, repo):
72
73 download_l = []
74
75 branches_group = ([], _("Branches"))
76 tags_group = ([], _("Tags"))
77
78 for name, chs in c.rhodecode_repo.branches.items():
79 #chs = chs.split(':')[-1]
80 branches_group[0].append((chs, name),)
81 download_l.append(branches_group)
82
83 for name, chs in c.rhodecode_repo.tags.items():
84 #chs = chs.split(':')[-1]
85 tags_group[0].append((chs, name),)
86 download_l.append(tags_group)
87
88 return download_l
89
90
91 def __get_readme_data(self, db_repo):
92 repo_name = db_repo.repo_name
93
94 @cache_region('long_term')
95 def _get_readme_from_cache(key):
96 readme_data = None
97 readme_file = None
98 log.debug('Looking for README file')
99 try:
100 # get's the landing revision! or tip if fails
101 cs = db_repo.get_landing_changeset()
102 if isinstance(cs, EmptyChangeset):
103 raise EmptyRepositoryError()
104 renderer = MarkupRenderer()
105 for f in README_FILES:
106 try:
107 readme = cs.get_node(f)
108 if not isinstance(readme, FileNode):
109 continue
110 readme_file = f
111 log.debug('Found README file `%s` rendering...' %
112 readme_file)
113 readme_data = renderer.render(readme.content, f)
114 break
115 except NodeDoesNotExistError:
116 continue
117 except ChangesetError:
118 log.error(traceback.format_exc())
119 pass
120 except EmptyRepositoryError:
121 pass
122 except Exception:
123 log.error(traceback.format_exc())
124
125 return readme_data, readme_file
126
127 key = repo_name + '_README'
128 inv = CacheInvalidation.invalidate(key)
129 if inv is not None:
130 region_invalidate(_get_readme_from_cache, None, key)
131 CacheInvalidation.set_valid(inv.cache_key)
132 return _get_readme_from_cache(key)
133
68 @LoginRequired()
134 @LoginRequired()
69 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
135 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
70 'repository.admin')
136 'repository.admin')
71 def __before__(self):
72 super(SummaryController, self).__before__()
73
74 def index(self, repo_name):
137 def index(self, repo_name):
75 c.dbrepo = dbrepo = c.rhodecode_db_repo
138 c.dbrepo = dbrepo = c.rhodecode_db_repo
76
139
77 def url_generator(**kw):
140 def url_generator(**kw):
78 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
141 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
79
142
80 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
143 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
81 items_per_page=10, url=url_generator)
144 items_per_page=10, url=url_generator)
82 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
145 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
83 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
146 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
84
147
85 if self.rhodecode_user.username == 'default':
148 if self.rhodecode_user.username == 'default':
86 # for default(anonymous) user we don't need to pass credentials
149 # for default(anonymous) user we don't need to pass credentials
87 username = ''
150 username = ''
88 password = ''
151 password = ''
89 else:
152 else:
90 username = str(self.rhodecode_user.username)
153 username = str(self.rhodecode_user.username)
91 password = '@'
154 password = '@'
92
155
93 parsed_url = urlparse(url.current(qualified=True))
156 parsed_url = urlparse(url.current(qualified=True))
94
157
95 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
158 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
96
159
97 uri_tmpl = config.get('clone_uri', default_clone_uri)
160 uri_tmpl = config.get('clone_uri', default_clone_uri)
98 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
161 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
99 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
162 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
100 uri_dict = {
163 uri_dict = {
101 'user': urllib.quote(username),
164 'user': urllib.quote(username),
102 'pass': password,
165 'pass': password,
103 'scheme': parsed_url.scheme,
166 'scheme': parsed_url.scheme,
104 'netloc': parsed_url.netloc,
167 'netloc': parsed_url.netloc,
105 'path': urllib.quote(safe_str(decoded_path))
168 'path': urllib.quote(safe_str(decoded_path))
106 }
169 }
107
170
108 uri = (uri_tmpl % uri_dict)
171 uri = (uri_tmpl % uri_dict)
109 # generate another clone url by id
172 # generate another clone url by id
110 uri_dict.update(
173 uri_dict.update(
111 {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
174 {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
112 )
175 )
113 uri_id = uri_tmpl % uri_dict
176 uri_id = uri_tmpl % uri_dict
114
177
115 c.clone_repo_url = uri
178 c.clone_repo_url = uri
116 c.clone_repo_url_id = uri_id
179 c.clone_repo_url_id = uri_id
117 c.repo_tags = OrderedDict()
180 c.repo_tags = OrderedDict()
118 for name, hash_ in c.rhodecode_repo.tags.items()[:10]:
181 for name, hash_ in c.rhodecode_repo.tags.items()[:10]:
119 try:
182 try:
120 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash_)
183 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash_)
121 except ChangesetError:
184 except ChangesetError:
122 c.repo_tags[name] = EmptyChangeset(hash_)
185 c.repo_tags[name] = EmptyChangeset(hash_)
123
186
124 c.repo_branches = OrderedDict()
187 c.repo_branches = OrderedDict()
125 for name, hash_ in c.rhodecode_repo.branches.items()[:10]:
188 for name, hash_ in c.rhodecode_repo.branches.items()[:10]:
126 try:
189 try:
127 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_)
190 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_)
128 except ChangesetError:
191 except ChangesetError:
129 c.repo_branches[name] = EmptyChangeset(hash_)
192 c.repo_branches[name] = EmptyChangeset(hash_)
130
193
131 td = date.today() + timedelta(days=1)
194 td = date.today() + timedelta(days=1)
132 td_1m = td - timedelta(days=calendar.mdays[td.month])
195 td_1m = td - timedelta(days=calendar.mdays[td.month])
133 td_1y = td - timedelta(days=365)
196 td_1y = td - timedelta(days=365)
134
197
135 ts_min_m = mktime(td_1m.timetuple())
198 ts_min_m = mktime(td_1m.timetuple())
136 ts_min_y = mktime(td_1y.timetuple())
199 ts_min_y = mktime(td_1y.timetuple())
137 ts_max_y = mktime(td.timetuple())
200 ts_max_y = mktime(td.timetuple())
138
201
139 if dbrepo.enable_statistics:
202 if dbrepo.enable_statistics:
140 c.show_stats = True
203 c.show_stats = True
141 c.no_data_msg = _('No data loaded yet')
204 c.no_data_msg = _('No data loaded yet')
142 recurse_limit = 500 # don't recurse more than 500 times when parsing
205 recurse_limit = 500 # don't recurse more than 500 times when parsing
143 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y,
206 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y,
144 ts_max_y, recurse_limit)
207 ts_max_y, recurse_limit)
145 else:
208 else:
146 c.show_stats = False
209 c.show_stats = False
147 c.no_data_msg = _('Statistics are disabled for this repository')
210 c.no_data_msg = _('Statistics are disabled for this repository')
148 c.ts_min = ts_min_m
211 c.ts_min = ts_min_m
149 c.ts_max = ts_max_y
212 c.ts_max = ts_max_y
150
213
151 stats = self.sa.query(Statistics)\
214 stats = self.sa.query(Statistics)\
152 .filter(Statistics.repository == dbrepo)\
215 .filter(Statistics.repository == dbrepo)\
153 .scalar()
216 .scalar()
154
217
155 c.stats_percentage = 0
218 c.stats_percentage = 0
156
219
157 if stats and stats.languages:
220 if stats and stats.languages:
158 c.no_data = False is dbrepo.enable_statistics
221 c.no_data = False is dbrepo.enable_statistics
159 lang_stats_d = json.loads(stats.languages)
222 lang_stats_d = json.loads(stats.languages)
160 c.commit_data = stats.commit_activity
223 c.commit_data = stats.commit_activity
161 c.overview_data = stats.commit_activity_combined
224 c.overview_data = stats.commit_activity_combined
162
225
163 lang_stats = ((x, {"count": y,
226 lang_stats = ((x, {"count": y,
164 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
227 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
165 for x, y in lang_stats_d.items())
228 for x, y in lang_stats_d.items())
166
229
167 c.trending_languages = json.dumps(
230 c.trending_languages = json.dumps(
168 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
231 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
169 )
232 )
170 last_rev = stats.stat_on_revision + 1
233 last_rev = stats.stat_on_revision + 1
171 c.repo_last_rev = c.rhodecode_repo.count()\
234 c.repo_last_rev = c.rhodecode_repo.count()\
172 if c.rhodecode_repo.revisions else 0
235 if c.rhodecode_repo.revisions else 0
173 if last_rev == 0 or c.repo_last_rev == 0:
236 if last_rev == 0 or c.repo_last_rev == 0:
174 pass
237 pass
175 else:
238 else:
176 c.stats_percentage = '%.2f' % ((float((last_rev)) /
239 c.stats_percentage = '%.2f' % ((float((last_rev)) /
177 c.repo_last_rev) * 100)
240 c.repo_last_rev) * 100)
178 else:
241 else:
179 c.commit_data = json.dumps({})
242 c.commit_data = json.dumps({})
180 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
243 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
181 c.trending_languages = json.dumps({})
244 c.trending_languages = json.dumps({})
182 c.no_data = True
245 c.no_data = True
183
246
184 c.enable_downloads = dbrepo.enable_downloads
247 c.enable_downloads = dbrepo.enable_downloads
185 if c.enable_downloads:
248 if c.enable_downloads:
186 c.download_options = self._get_download_links(c.rhodecode_repo)
249 c.download_options = self._get_download_links(c.rhodecode_repo)
187
250
188 c.readme_data, c.readme_file = \
251 c.readme_data, c.readme_file = \
189 self.__get_readme_data(c.rhodecode_db_repo)
252 self.__get_readme_data(c.rhodecode_db_repo)
190 return render('summary/summary.html')
253 return render('summary/summary.html')
191
254
255 @LoginRequired()
192 @NotAnonymous()
256 @NotAnonymous()
257 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
258 'repository.admin')
193 @jsonify
259 @jsonify
194 def repo_size(self, repo_name):
260 def repo_size(self, repo_name):
195 if request.is_xhr:
261 if request.is_xhr:
196 return c.rhodecode_db_repo._repo_size()
262 return c.rhodecode_db_repo._repo_size()
197 else:
263 else:
198 raise HTTPBadRequest()
264 raise HTTPBadRequest()
199
200 def __get_readme_data(self, db_repo):
201 repo_name = db_repo.repo_name
202
203 @cache_region('long_term')
204 def _get_readme_from_cache(key):
205 readme_data = None
206 readme_file = None
207 log.debug('Looking for README file')
208 try:
209 # get's the landing revision! or tip if fails
210 cs = db_repo.get_landing_changeset()
211 if isinstance(cs, EmptyChangeset):
212 raise EmptyRepositoryError()
213 renderer = MarkupRenderer()
214 for f in README_FILES:
215 try:
216 readme = cs.get_node(f)
217 if not isinstance(readme, FileNode):
218 continue
219 readme_file = f
220 log.debug('Found README file `%s` rendering...' %
221 readme_file)
222 readme_data = renderer.render(readme.content, f)
223 break
224 except NodeDoesNotExistError:
225 continue
226 except ChangesetError:
227 log.error(traceback.format_exc())
228 pass
229 except EmptyRepositoryError:
230 pass
231 except Exception:
232 log.error(traceback.format_exc())
233
234 return readme_data, readme_file
235
236 key = repo_name + '_README'
237 inv = CacheInvalidation.invalidate(key)
238 if inv is not None:
239 region_invalidate(_get_readme_from_cache, None, key)
240 CacheInvalidation.set_valid(inv.cache_key)
241 return _get_readme_from_cache(key)
242
243 def _get_download_links(self, repo):
244
245 download_l = []
246
247 branches_group = ([], _("Branches"))
248 tags_group = ([], _("Tags"))
249
250 for name, chs in c.rhodecode_repo.branches.items():
251 #chs = chs.split(':')[-1]
252 branches_group[0].append((chs, name),)
253 download_l.append(branches_group)
254
255 for name, chs in c.rhodecode_repo.tags.items():
256 #chs = chs.split(':')[-1]
257 tags_group[0].append((chs, name),)
258 download_l.append(tags_group)
259
260 return download_l
@@ -1,53 +1,53 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.tags
3 rhodecode.controllers.tags
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Tags controller for rhodecode
6 Tags 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 import logging
25 import logging
26
26
27 from pylons import tmpl_context as c
27 from pylons import tmpl_context as c
28
28
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.base import BaseRepoController, render
30 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.compat import OrderedDict
31 from rhodecode.lib.compat import OrderedDict
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class TagsController(BaseRepoController):
36 class TagsController(BaseRepoController):
37
37
38 def __before__(self):
39 super(TagsController, self).__before__()
40
38 @LoginRequired()
41 @LoginRequired()
39 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
40 'repository.admin')
43 'repository.admin')
41 def __before__(self):
42 super(TagsController, self).__before__()
43
44 def index(self):
44 def index(self):
45 c.repo_tags = OrderedDict()
45 c.repo_tags = OrderedDict()
46
46
47 tags = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
47 tags = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
48 name, hash_ in c.rhodecode_repo.tags.items()]
48 name, hash_ in c.rhodecode_repo.tags.items()]
49 ordered_tags = sorted(tags, key=lambda x: x[1].date, reverse=True)
49 ordered_tags = sorted(tags, key=lambda x: x[1].date, reverse=True)
50 for name, cs_tag in ordered_tags:
50 for name, cs_tag in ordered_tags:
51 c.repo_tags[name] = cs_tag
51 c.repo_tags[name] = cs_tag
52
52
53 return render('tags/tags.html')
53 return render('tags/tags.html')
General Comments 0
You need to be logged in to leave comments. Login now