##// END OF EJS Templates
Enabled compare engine for tags...
marcink -
r3010:bf96fd19 beta
parent child Browse files
Show More
@@ -1,148 +1,153 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 showoing differences between two
6 compare controller for pylons showoing 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 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from webob.exc import HTTPNotFound
29 from webob.exc import HTTPNotFound
30 from pylons import request, response, session, tmpl_context as c, url
30 from pylons import request, response, session, tmpl_context as c, url
31 from pylons.controllers.util import abort, redirect
31 from pylons.controllers.util import abort, redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib import diffs
38 from rhodecode.lib import diffs
39
39
40 from rhodecode.model.db import Repository
40 from rhodecode.model.db import Repository
41 from rhodecode.model.pull_request import PullRequestModel
41 from rhodecode.model.pull_request import PullRequestModel
42 from webob.exc import HTTPBadRequest
42 from webob.exc import HTTPBadRequest
43 from rhodecode.lib.utils2 import str2bool
43 from rhodecode.lib.utils2 import str2bool
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class CompareController(BaseRepoController):
48 class CompareController(BaseRepoController):
49
49
50 @LoginRequired()
50 @LoginRequired()
51 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
51 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 'repository.admin')
52 'repository.admin')
53 def __before__(self):
53 def __before__(self):
54 super(CompareController, self).__before__()
54 super(CompareController, self).__before__()
55
55
56 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
56 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
57 partial=False):
57 partial=False):
58 """
58 """
59 Safe way to get changeset if error occur it redirects to changeset with
59 Safe way to get changeset if error occur it redirects to changeset with
60 proper message. If partial is set then don't do redirect raise Exception
60 proper message. If partial is set then don't do redirect raise Exception
61 instead
61 instead
62
62
63 :param rev: revision to fetch
63 :param rev: revision to fetch
64 :param repo: repo instance
64 :param repo: repo instance
65 """
65 """
66
66
67 try:
67 try:
68 type_, rev = rev
68 type_, rev = rev
69 return repo.scm_instance.get_changeset(rev)
69 return repo.scm_instance.get_changeset(rev)
70 except EmptyRepositoryError, e:
70 except EmptyRepositoryError, e:
71 if not redirect_after:
71 if not redirect_after:
72 return None
72 return None
73 h.flash(h.literal(_('There are no changesets yet')),
73 h.flash(h.literal(_('There are no changesets yet')),
74 category='warning')
74 category='warning')
75 redirect(url('summary_home', repo_name=repo.repo_name))
75 redirect(url('summary_home', repo_name=repo.repo_name))
76
76
77 except RepositoryError, e:
77 except RepositoryError, e:
78 log.error(traceback.format_exc())
78 log.error(traceback.format_exc())
79 h.flash(str(e), category='warning')
79 h.flash(str(e), category='warning')
80 if not partial:
80 if not partial:
81 redirect(h.url('summary_home', repo_name=repo.repo_name))
81 redirect(h.url('summary_home', repo_name=repo.repo_name))
82 raise HTTPBadRequest()
82 raise HTTPBadRequest()
83
83
84 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
84 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
85
85
86 org_repo = c.rhodecode_db_repo.repo_name
86 org_repo = c.rhodecode_db_repo.repo_name
87 org_ref = (org_ref_type, org_ref)
87 org_ref = (org_ref_type, org_ref)
88 other_ref = (other_ref_type, other_ref)
88 other_ref = (other_ref_type, other_ref)
89 other_repo = request.GET.get('repo', org_repo)
89 other_repo = request.GET.get('repo', org_repo)
90 bundle_compare = str2bool(request.GET.get('bundle', True))
90 bundle_compare = str2bool(request.GET.get('bundle', True))
91
91
92 c.swap_url = h.url('compare_url', repo_name=other_repo,
92 c.swap_url = h.url('compare_url', repo_name=other_repo,
93 org_ref_type=other_ref[0], org_ref=other_ref[1],
93 org_ref_type=other_ref[0], org_ref=other_ref[1],
94 other_ref_type=org_ref[0], other_ref=org_ref[1],
94 other_ref_type=org_ref[0], other_ref=org_ref[1],
95 repo=org_repo, as_form=request.GET.get('as_form'),
95 repo=org_repo, as_form=request.GET.get('as_form'),
96 bundle=bundle_compare)
96 bundle=bundle_compare)
97
97
98 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
98 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
99 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
99 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
100
100
101 if c.org_repo is None or c.other_repo is None:
101 if c.org_repo is None or c.other_repo is None:
102 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
102 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
103 raise HTTPNotFound
103 raise HTTPNotFound
104
104
105 if c.org_repo.scm_instance.alias != 'hg':
105 if c.org_repo != c.other_repo and h.is_git(c.rhodecode_repo):
106 log.error('Review not available for GIT REPOS')
106 log.error('compare of two remote repos not available for GIT REPOS')
107 raise HTTPNotFound
107 raise HTTPNotFound
108
109 if c.org_repo.scm_instance.alias != c.other_repo.scm_instance.alias:
110 log.error('compare of two different kind of remote repos not available')
111 raise HTTPNotFound
112
108 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
113 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
109 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
114 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
110 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
115 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
111
116
112 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
117 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
113 org_repo, org_ref, other_repo, other_ref
118 org_repo, org_ref, other_repo, other_ref
114 )
119 )
115
120
116 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
121 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
117 c.cs_ranges])
122 c.cs_ranges])
118 c.target_repo = c.repo_name
123 c.target_repo = c.repo_name
119 # defines that we need hidden inputs with changesets
124 # defines that we need hidden inputs with changesets
120 c.as_form = request.GET.get('as_form', False)
125 c.as_form = request.GET.get('as_form', False)
121 if partial:
126 if partial:
122 return render('compare/compare_cs.html')
127 return render('compare/compare_cs.html')
123
128
124 if not bundle_compare and c.cs_ranges:
129 if not bundle_compare and c.cs_ranges:
125 # case we want a simple diff without incoming changesets, just
130 # case we want a simple diff without incoming changesets, just
126 # for review purposes. Make the diff on the forked repo, with
131 # for review purposes. Make the diff on the forked repo, with
127 # revision that is common ancestor
132 # revision that is common ancestor
128 other_ref = ('rev', c.cs_ranges[-1].parents[0].raw_id)
133 other_ref = ('rev', c.cs_ranges[-1].parents[0].raw_id)
129 other_repo = org_repo
134 other_repo = org_repo
130
135
131 c.org_ref = org_ref[1]
136 c.org_ref = org_ref[1]
132 c.other_ref = other_ref[1]
137 c.other_ref = other_ref[1]
133
138
134 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
139 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
135 discovery_data, bundle_compare=bundle_compare)
140 discovery_data, bundle_compare=bundle_compare)
136 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
141 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
137 _parsed = diff_processor.prepare()
142 _parsed = diff_processor.prepare()
138
143
139 c.files = []
144 c.files = []
140 c.changes = {}
145 c.changes = {}
141
146
142 for f in _parsed:
147 for f in _parsed:
143 fid = h.FID('', f['filename'])
148 fid = h.FID('', f['filename'])
144 c.files.append([fid, f['operation'], f['filename'], f['stats']])
149 c.files.append([fid, f['operation'], f['filename'], f['stats']])
145 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
150 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
146 c.changes[fid] = [f['operation'], f['filename'], diff]
151 c.changes[fid] = [f['operation'], f['filename'], diff]
147
152
148 return render('compare/compare_diff.html')
153 return render('compare/compare_diff.html')
@@ -1,185 +1,185 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 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.forms import RepoForkForm
43 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.scm import ScmModel
45 from rhodecode.lib.utils2 import safe_int
45 from rhodecode.lib.utils2 import safe_int
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class ForksController(BaseRepoController):
50 class ForksController(BaseRepoController):
51
51
52 @LoginRequired()
52 @LoginRequired()
53 def __before__(self):
53 def __before__(self):
54 super(ForksController, self).__before__()
54 super(ForksController, self).__before__()
55
55
56 def __load_defaults(self):
56 def __load_defaults(self):
57 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
57 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
59 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
59 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
60 c.landing_revs_choices = choices
60 c.landing_revs_choices = choices
61
61
62 def __load_data(self, repo_name=None):
62 def __load_data(self, repo_name=None):
63 """
63 """
64 Load defaults settings for edit, and update
64 Load defaults settings for edit, and update
65
65
66 :param repo_name:
66 :param repo_name:
67 """
67 """
68 self.__load_defaults()
68 self.__load_defaults()
69
69
70 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
70 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
71 repo = db_repo.scm_instance
71 repo = db_repo.scm_instance
72
72
73 if c.repo_info is None:
73 if c.repo_info is None:
74 h.flash(_('%s repository is not mapped to db perhaps'
74 h.flash(_('%s repository is not mapped to db perhaps'
75 ' it was created or renamed from the filesystem'
75 ' it was created or renamed from the filesystem'
76 ' please run the application again'
76 ' please run the application again'
77 ' in order to rescan repositories') % repo_name,
77 ' in order to rescan repositories') % repo_name,
78 category='error')
78 category='error')
79
79
80 return redirect(url('repos'))
80 return redirect(url('repos'))
81
81
82 c.default_user_id = User.get_by_username('default').user_id
82 c.default_user_id = User.get_by_username('default').user_id
83 c.in_public_journal = UserFollowing.query()\
83 c.in_public_journal = UserFollowing.query()\
84 .filter(UserFollowing.user_id == c.default_user_id)\
84 .filter(UserFollowing.user_id == c.default_user_id)\
85 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
85 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
86
86
87 if c.repo_info.stats:
87 if c.repo_info.stats:
88 last_rev = c.repo_info.stats.stat_on_revision+1
88 last_rev = c.repo_info.stats.stat_on_revision+1
89 else:
89 else:
90 last_rev = 0
90 last_rev = 0
91 c.stats_revision = last_rev
91 c.stats_revision = last_rev
92
92
93 c.repo_last_rev = repo.count() if repo.revisions else 0
93 c.repo_last_rev = repo.count() if repo.revisions else 0
94
94
95 if last_rev == 0 or c.repo_last_rev == 0:
95 if last_rev == 0 or c.repo_last_rev == 0:
96 c.stats_percentage = 0
96 c.stats_percentage = 0
97 else:
97 else:
98 c.stats_percentage = '%.2f' % ((float((last_rev)) /
98 c.stats_percentage = '%.2f' % ((float((last_rev)) /
99 c.repo_last_rev) * 100)
99 c.repo_last_rev) * 100)
100
100
101 defaults = RepoModel()._get_defaults(repo_name)
101 defaults = RepoModel()._get_defaults(repo_name)
102 # add prefix to fork
102 # add suffix to fork
103 defaults['repo_name'] = 'fork-' + defaults['repo_name']
103 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
104 return defaults
104 return defaults
105
105
106 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
106 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
107 'repository.admin')
107 'repository.admin')
108 def forks(self, repo_name):
108 def forks(self, repo_name):
109 p = safe_int(request.params.get('page', 1), 1)
109 p = safe_int(request.params.get('page', 1), 1)
110 repo_id = c.rhodecode_db_repo.repo_id
110 repo_id = c.rhodecode_db_repo.repo_id
111 d = []
111 d = []
112 for r in Repository.get_repo_forks(repo_id):
112 for r in Repository.get_repo_forks(repo_id):
113 if not HasRepoPermissionAny(
113 if not HasRepoPermissionAny(
114 'repository.read', 'repository.write', 'repository.admin'
114 'repository.read', 'repository.write', 'repository.admin'
115 )(r.repo_name, 'get forks check'):
115 )(r.repo_name, 'get forks check'):
116 continue
116 continue
117 d.append(r)
117 d.append(r)
118 c.forks_pager = Page(d, page=p, items_per_page=20)
118 c.forks_pager = Page(d, page=p, items_per_page=20)
119
119
120 c.forks_data = render('/forks/forks_data.html')
120 c.forks_data = render('/forks/forks_data.html')
121
121
122 if request.environ.get('HTTP_X_PARTIAL_XHR'):
122 if request.environ.get('HTTP_X_PARTIAL_XHR'):
123 return c.forks_data
123 return c.forks_data
124
124
125 return render('/forks/forks.html')
125 return render('/forks/forks.html')
126
126
127 @NotAnonymous()
127 @NotAnonymous()
128 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
128 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
129 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
129 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
130 'repository.admin')
130 'repository.admin')
131 def fork(self, repo_name):
131 def fork(self, repo_name):
132 c.repo_info = Repository.get_by_repo_name(repo_name)
132 c.repo_info = Repository.get_by_repo_name(repo_name)
133 if not c.repo_info:
133 if not c.repo_info:
134 h.flash(_('%s repository is not mapped to db perhaps'
134 h.flash(_('%s repository is not mapped to db perhaps'
135 ' it was created or renamed from the file system'
135 ' it was created or renamed from the file system'
136 ' please run the application again'
136 ' please run the application again'
137 ' in order to rescan repositories') % repo_name,
137 ' in order to rescan repositories') % repo_name,
138 category='error')
138 category='error')
139
139
140 return redirect(url('home'))
140 return redirect(url('home'))
141
141
142 defaults = self.__load_data(repo_name)
142 defaults = self.__load_data(repo_name)
143
143
144 return htmlfill.render(
144 return htmlfill.render(
145 render('forks/fork.html'),
145 render('forks/fork.html'),
146 defaults=defaults,
146 defaults=defaults,
147 encoding="UTF-8",
147 encoding="UTF-8",
148 force_defaults=False
148 force_defaults=False
149 )
149 )
150
150
151 @NotAnonymous()
151 @NotAnonymous()
152 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
152 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
153 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
154 'repository.admin')
154 'repository.admin')
155 def fork_create(self, repo_name):
155 def fork_create(self, repo_name):
156 self.__load_defaults()
156 self.__load_defaults()
157 c.repo_info = Repository.get_by_repo_name(repo_name)
157 c.repo_info = Repository.get_by_repo_name(repo_name)
158 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
158 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
159 repo_groups=c.repo_groups_choices,
159 repo_groups=c.repo_groups_choices,
160 landing_revs=c.landing_revs_choices)()
160 landing_revs=c.landing_revs_choices)()
161 form_result = {}
161 form_result = {}
162 try:
162 try:
163 form_result = _form.to_python(dict(request.POST))
163 form_result = _form.to_python(dict(request.POST))
164
164
165 # create fork is done sometimes async on celery, db transaction
165 # create fork is done sometimes async on celery, db transaction
166 # management is handled there.
166 # management is handled there.
167 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
167 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
168 h.flash(_('forked %s repository as %s') \
168 h.flash(_('forked %s repository as %s') \
169 % (repo_name, form_result['repo_name']),
169 % (repo_name, form_result['repo_name']),
170 category='success')
170 category='success')
171 except formencode.Invalid, errors:
171 except formencode.Invalid, errors:
172 c.new_repo = errors.value['repo_name']
172 c.new_repo = errors.value['repo_name']
173
173
174 return htmlfill.render(
174 return htmlfill.render(
175 render('forks/fork.html'),
175 render('forks/fork.html'),
176 defaults=errors.value,
176 defaults=errors.value,
177 errors=errors.error_dict or {},
177 errors=errors.error_dict or {},
178 prefix_error=False,
178 prefix_error=False,
179 encoding="UTF-8")
179 encoding="UTF-8")
180 except Exception:
180 except Exception:
181 log.error(traceback.format_exc())
181 log.error(traceback.format_exc())
182 h.flash(_('An error occurred during repository forking %s') %
182 h.flash(_('An error occurred during repository forking %s') %
183 repo_name, category='error')
183 repo_name, category='error')
184
184
185 return redirect(url('home'))
185 return redirect(url('home'))
@@ -1,767 +1,771 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.diffs
3 rhodecode.lib.diffs
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Set of diffing helpers, previously part of vcs
6 Set of diffing helpers, previously part of vcs
7
7
8
8
9 :created_on: Dec 4, 2011
9 :created_on: Dec 4, 2011
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 :original copyright: 2007-2008 by Armin Ronacher
12 :original copyright: 2007-2008 by Armin Ronacher
13 :license: GPLv3, see COPYING for more details.
13 :license: GPLv3, see COPYING for more details.
14 """
14 """
15 # This program is free software: you can redistribute it and/or modify
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
18 # (at your option) any later version.
19 #
19 #
20 # This program is distributed in the hope that it will be useful,
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
23 # GNU General Public License for more details.
24 #
24 #
25 # You should have received a copy of the GNU General Public License
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
27
28 import re
28 import re
29 import difflib
29 import difflib
30 import logging
30 import logging
31 import traceback
31 import traceback
32
32
33 from itertools import tee, imap
33 from itertools import tee, imap
34
34
35 from mercurial import patch
35 from mercurial import patch
36 from mercurial.mdiff import diffopts
36 from mercurial.mdiff import diffopts
37 from mercurial.bundlerepo import bundlerepository
37 from mercurial.bundlerepo import bundlerepository
38
38
39 from pylons.i18n.translation import _
39 from pylons.i18n.translation import _
40
40
41 from rhodecode.lib.compat import BytesIO
41 from rhodecode.lib.compat import BytesIO
42 from rhodecode.lib.vcs.utils.hgcompat import localrepo
42 from rhodecode.lib.vcs.utils.hgcompat import localrepo
43 from rhodecode.lib.vcs.exceptions import VCSError
43 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
44 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.helpers import escape
46 from rhodecode.lib.helpers import escape
47 from rhodecode.lib.utils import make_ui
47 from rhodecode.lib.utils import make_ui
48 from rhodecode.lib.utils2 import safe_unicode
48 from rhodecode.lib.utils2 import safe_unicode
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 def wrap_to_table(str_):
53 def wrap_to_table(str_):
54 return '''<table class="code-difftable">
54 return '''<table class="code-difftable">
55 <tr class="line no-comment">
55 <tr class="line no-comment">
56 <td class="lineno new"></td>
56 <td class="lineno new"></td>
57 <td class="code no-comment"><pre>%s</pre></td>
57 <td class="code no-comment"><pre>%s</pre></td>
58 </tr>
58 </tr>
59 </table>''' % str_
59 </table>''' % str_
60
60
61
61
62 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
62 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
63 ignore_whitespace=True, line_context=3,
63 ignore_whitespace=True, line_context=3,
64 enable_comments=False):
64 enable_comments=False):
65 """
65 """
66 returns a wrapped diff into a table, checks for cut_off_limit and presents
66 returns a wrapped diff into a table, checks for cut_off_limit and presents
67 proper message
67 proper message
68 """
68 """
69
69
70 if filenode_old is None:
70 if filenode_old is None:
71 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
71 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
72
72
73 if filenode_old.is_binary or filenode_new.is_binary:
73 if filenode_old.is_binary or filenode_new.is_binary:
74 diff = wrap_to_table(_('binary file'))
74 diff = wrap_to_table(_('binary file'))
75 stats = (0, 0)
75 stats = (0, 0)
76 size = 0
76 size = 0
77
77
78 elif cut_off_limit != -1 and (cut_off_limit is None or
78 elif cut_off_limit != -1 and (cut_off_limit is None or
79 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
79 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
80
80
81 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
81 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
82 ignore_whitespace=ignore_whitespace,
82 ignore_whitespace=ignore_whitespace,
83 context=line_context)
83 context=line_context)
84 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
84 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
85
85
86 diff = diff_processor.as_html(enable_comments=enable_comments)
86 diff = diff_processor.as_html(enable_comments=enable_comments)
87 stats = diff_processor.stat()
87 stats = diff_processor.stat()
88 size = len(diff or '')
88 size = len(diff or '')
89 else:
89 else:
90 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
90 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
91 'diff menu to display this diff'))
91 'diff menu to display this diff'))
92 stats = (0, 0)
92 stats = (0, 0)
93 size = 0
93 size = 0
94 if not diff:
94 if not diff:
95 submodules = filter(lambda o: isinstance(o, SubModuleNode),
95 submodules = filter(lambda o: isinstance(o, SubModuleNode),
96 [filenode_new, filenode_old])
96 [filenode_new, filenode_old])
97 if submodules:
97 if submodules:
98 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
98 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
99 else:
99 else:
100 diff = wrap_to_table(_('No changes detected'))
100 diff = wrap_to_table(_('No changes detected'))
101
101
102 cs1 = filenode_old.changeset.raw_id
102 cs1 = filenode_old.changeset.raw_id
103 cs2 = filenode_new.changeset.raw_id
103 cs2 = filenode_new.changeset.raw_id
104
104
105 return size, cs1, cs2, diff, stats
105 return size, cs1, cs2, diff, stats
106
106
107
107
108 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
108 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
109 """
109 """
110 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
110 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
111
111
112 :param ignore_whitespace: ignore whitespaces in diff
112 :param ignore_whitespace: ignore whitespaces in diff
113 """
113 """
114 # make sure we pass in default context
114 # make sure we pass in default context
115 context = context or 3
115 context = context or 3
116 submodules = filter(lambda o: isinstance(o, SubModuleNode),
116 submodules = filter(lambda o: isinstance(o, SubModuleNode),
117 [filenode_new, filenode_old])
117 [filenode_new, filenode_old])
118 if submodules:
118 if submodules:
119 return ''
119 return ''
120
120
121 for filenode in (filenode_old, filenode_new):
121 for filenode in (filenode_old, filenode_new):
122 if not isinstance(filenode, FileNode):
122 if not isinstance(filenode, FileNode):
123 raise VCSError("Given object should be FileNode object, not %s"
123 raise VCSError("Given object should be FileNode object, not %s"
124 % filenode.__class__)
124 % filenode.__class__)
125
125
126 repo = filenode_new.changeset.repository
126 repo = filenode_new.changeset.repository
127 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
127 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
128 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
128 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
129
129
130 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
130 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
131 ignore_whitespace, context)
131 ignore_whitespace, context)
132 return vcs_gitdiff
132 return vcs_gitdiff
133
133
134 NEW_FILENODE = 1
134 NEW_FILENODE = 1
135 DEL_FILENODE = 2
135 DEL_FILENODE = 2
136 MOD_FILENODE = 3
136 MOD_FILENODE = 3
137 RENAMED_FILENODE = 4
137 RENAMED_FILENODE = 4
138 CHMOD_FILENODE = 5
138 CHMOD_FILENODE = 5
139
139
140
140
141 class DiffLimitExceeded(Exception):
141 class DiffLimitExceeded(Exception):
142 pass
142 pass
143
143
144
144
145 class LimitedDiffContainer(object):
145 class LimitedDiffContainer(object):
146
146
147 def __init__(self, diff_limit, cur_diff_size, diff):
147 def __init__(self, diff_limit, cur_diff_size, diff):
148 self.diff = diff
148 self.diff = diff
149 self.diff_limit = diff_limit
149 self.diff_limit = diff_limit
150 self.cur_diff_size = cur_diff_size
150 self.cur_diff_size = cur_diff_size
151
151
152 def __iter__(self):
152 def __iter__(self):
153 for l in self.diff:
153 for l in self.diff:
154 yield l
154 yield l
155
155
156
156
157 class DiffProcessor(object):
157 class DiffProcessor(object):
158 """
158 """
159 Give it a unified or git diff and it returns a list of the files that were
159 Give it a unified or git diff and it returns a list of the files that were
160 mentioned in the diff together with a dict of meta information that
160 mentioned in the diff together with a dict of meta information that
161 can be used to render it in a HTML template.
161 can be used to render it in a HTML template.
162 """
162 """
163 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
163 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
164 _newline_marker = '\\ No newline at end of file\n'
164 _newline_marker = '\\ No newline at end of file\n'
165 _git_header_re = re.compile(r"""
165 _git_header_re = re.compile(r"""
166 #^diff[ ]--git
166 #^diff[ ]--git
167 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
167 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
168 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
168 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
169 ^rename[ ]from[ ](?P<rename_from>\S+)\n
169 ^rename[ ]from[ ](?P<rename_from>\S+)\n
170 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
170 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
171 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
171 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
172 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
172 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
173 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
173 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
174 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
174 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
175 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
175 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
176 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
176 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
177 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
177 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
178 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
178 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
179 """, re.VERBOSE | re.MULTILINE)
179 """, re.VERBOSE | re.MULTILINE)
180 _hg_header_re = re.compile(r"""
180 _hg_header_re = re.compile(r"""
181 #^diff[ ]--git
181 #^diff[ ]--git
182 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
182 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
183 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
183 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
184 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
184 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
185 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
185 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
186 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
186 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
187 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
187 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
188 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
188 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
189 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
189 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
190 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
190 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
191 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
191 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
192 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
192 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
193 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
193 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
194 """, re.VERBOSE | re.MULTILINE)
194 """, re.VERBOSE | re.MULTILINE)
195
195
196 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
196 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
197 """
197 """
198 :param diff: a text in diff format
198 :param diff: a text in diff format
199 :param vcs: type of version controll hg or git
199 :param vcs: type of version controll hg or git
200 :param format: format of diff passed, `udiff` or `gitdiff`
200 :param format: format of diff passed, `udiff` or `gitdiff`
201 :param diff_limit: define the size of diff that is considered "big"
201 :param diff_limit: define the size of diff that is considered "big"
202 based on that parameter cut off will be triggered, set to None
202 based on that parameter cut off will be triggered, set to None
203 to show full diff
203 to show full diff
204 """
204 """
205 if not isinstance(diff, basestring):
205 if not isinstance(diff, basestring):
206 raise Exception('Diff must be a basestring got %s instead' % type(diff))
206 raise Exception('Diff must be a basestring got %s instead' % type(diff))
207
207
208 self._diff = diff
208 self._diff = diff
209 self._format = format
209 self._format = format
210 self.adds = 0
210 self.adds = 0
211 self.removes = 0
211 self.removes = 0
212 # calculate diff size
212 # calculate diff size
213 self.diff_size = len(diff)
213 self.diff_size = len(diff)
214 self.diff_limit = diff_limit
214 self.diff_limit = diff_limit
215 self.cur_diff_size = 0
215 self.cur_diff_size = 0
216 self.parsed = False
216 self.parsed = False
217 self.parsed_diff = []
217 self.parsed_diff = []
218 self.vcs = vcs
218 self.vcs = vcs
219
219
220 if format == 'gitdiff':
220 if format == 'gitdiff':
221 self.differ = self._highlight_line_difflib
221 self.differ = self._highlight_line_difflib
222 self._parser = self._parse_gitdiff
222 self._parser = self._parse_gitdiff
223 else:
223 else:
224 self.differ = self._highlight_line_udiff
224 self.differ = self._highlight_line_udiff
225 self._parser = self._parse_udiff
225 self._parser = self._parse_udiff
226
226
227 def _copy_iterator(self):
227 def _copy_iterator(self):
228 """
228 """
229 make a fresh copy of generator, we should not iterate thru
229 make a fresh copy of generator, we should not iterate thru
230 an original as it's needed for repeating operations on
230 an original as it's needed for repeating operations on
231 this instance of DiffProcessor
231 this instance of DiffProcessor
232 """
232 """
233 self.__udiff, iterator_copy = tee(self.__udiff)
233 self.__udiff, iterator_copy = tee(self.__udiff)
234 return iterator_copy
234 return iterator_copy
235
235
236 def _escaper(self, string):
236 def _escaper(self, string):
237 """
237 """
238 Escaper for diff escapes special chars and checks the diff limit
238 Escaper for diff escapes special chars and checks the diff limit
239
239
240 :param string:
240 :param string:
241 :type string:
241 :type string:
242 """
242 """
243
243
244 self.cur_diff_size += len(string)
244 self.cur_diff_size += len(string)
245
245
246 # escaper get's iterated on each .next() call and it checks if each
246 # escaper get's iterated on each .next() call and it checks if each
247 # parsed line doesn't exceed the diff limit
247 # parsed line doesn't exceed the diff limit
248 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
248 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
249 raise DiffLimitExceeded('Diff Limit Exceeded')
249 raise DiffLimitExceeded('Diff Limit Exceeded')
250
250
251 return safe_unicode(string).replace('&', '&amp;')\
251 return safe_unicode(string).replace('&', '&amp;')\
252 .replace('<', '&lt;')\
252 .replace('<', '&lt;')\
253 .replace('>', '&gt;')
253 .replace('>', '&gt;')
254
254
255 def _line_counter(self, l):
255 def _line_counter(self, l):
256 """
256 """
257 Checks each line and bumps total adds/removes for this diff
257 Checks each line and bumps total adds/removes for this diff
258
258
259 :param l:
259 :param l:
260 """
260 """
261 if l.startswith('+') and not l.startswith('+++'):
261 if l.startswith('+') and not l.startswith('+++'):
262 self.adds += 1
262 self.adds += 1
263 elif l.startswith('-') and not l.startswith('---'):
263 elif l.startswith('-') and not l.startswith('---'):
264 self.removes += 1
264 self.removes += 1
265 return safe_unicode(l)
265 return safe_unicode(l)
266
266
267 def _highlight_line_difflib(self, line, next_):
267 def _highlight_line_difflib(self, line, next_):
268 """
268 """
269 Highlight inline changes in both lines.
269 Highlight inline changes in both lines.
270 """
270 """
271
271
272 if line['action'] == 'del':
272 if line['action'] == 'del':
273 old, new = line, next_
273 old, new = line, next_
274 else:
274 else:
275 old, new = next_, line
275 old, new = next_, line
276
276
277 oldwords = re.split(r'(\W)', old['line'])
277 oldwords = re.split(r'(\W)', old['line'])
278 newwords = re.split(r'(\W)', new['line'])
278 newwords = re.split(r'(\W)', new['line'])
279
279
280 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
280 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
281
281
282 oldfragments, newfragments = [], []
282 oldfragments, newfragments = [], []
283 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
283 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
284 oldfrag = ''.join(oldwords[i1:i2])
284 oldfrag = ''.join(oldwords[i1:i2])
285 newfrag = ''.join(newwords[j1:j2])
285 newfrag = ''.join(newwords[j1:j2])
286 if tag != 'equal':
286 if tag != 'equal':
287 if oldfrag:
287 if oldfrag:
288 oldfrag = '<del>%s</del>' % oldfrag
288 oldfrag = '<del>%s</del>' % oldfrag
289 if newfrag:
289 if newfrag:
290 newfrag = '<ins>%s</ins>' % newfrag
290 newfrag = '<ins>%s</ins>' % newfrag
291 oldfragments.append(oldfrag)
291 oldfragments.append(oldfrag)
292 newfragments.append(newfrag)
292 newfragments.append(newfrag)
293
293
294 old['line'] = "".join(oldfragments)
294 old['line'] = "".join(oldfragments)
295 new['line'] = "".join(newfragments)
295 new['line'] = "".join(newfragments)
296
296
297 def _highlight_line_udiff(self, line, next_):
297 def _highlight_line_udiff(self, line, next_):
298 """
298 """
299 Highlight inline changes in both lines.
299 Highlight inline changes in both lines.
300 """
300 """
301 start = 0
301 start = 0
302 limit = min(len(line['line']), len(next_['line']))
302 limit = min(len(line['line']), len(next_['line']))
303 while start < limit and line['line'][start] == next_['line'][start]:
303 while start < limit and line['line'][start] == next_['line'][start]:
304 start += 1
304 start += 1
305 end = -1
305 end = -1
306 limit -= start
306 limit -= start
307 while -end <= limit and line['line'][end] == next_['line'][end]:
307 while -end <= limit and line['line'][end] == next_['line'][end]:
308 end -= 1
308 end -= 1
309 end += 1
309 end += 1
310 if start or end:
310 if start or end:
311 def do(l):
311 def do(l):
312 last = end + len(l['line'])
312 last = end + len(l['line'])
313 if l['action'] == 'add':
313 if l['action'] == 'add':
314 tag = 'ins'
314 tag = 'ins'
315 else:
315 else:
316 tag = 'del'
316 tag = 'del'
317 l['line'] = '%s<%s>%s</%s>%s' % (
317 l['line'] = '%s<%s>%s</%s>%s' % (
318 l['line'][:start],
318 l['line'][:start],
319 tag,
319 tag,
320 l['line'][start:last],
320 l['line'][start:last],
321 tag,
321 tag,
322 l['line'][last:]
322 l['line'][last:]
323 )
323 )
324 do(line)
324 do(line)
325 do(next_)
325 do(next_)
326
326
327 def _get_header(self, diff_chunk):
327 def _get_header(self, diff_chunk):
328 """
328 """
329 parses the diff header, and returns parts, and leftover diff
329 parses the diff header, and returns parts, and leftover diff
330 parts consists of 14 elements::
330 parts consists of 14 elements::
331
331
332 a_path, b_path, similarity_index, rename_from, rename_to,
332 a_path, b_path, similarity_index, rename_from, rename_to,
333 old_mode, new_mode, new_file_mode, deleted_file_mode,
333 old_mode, new_mode, new_file_mode, deleted_file_mode,
334 a_blob_id, b_blob_id, b_mode, a_file, b_file
334 a_blob_id, b_blob_id, b_mode, a_file, b_file
335
335
336 :param diff_chunk:
336 :param diff_chunk:
337 :type diff_chunk:
337 :type diff_chunk:
338 """
338 """
339
339
340 if self.vcs == 'git':
340 if self.vcs == 'git':
341 match = self._git_header_re.match(diff_chunk)
341 match = self._git_header_re.match(diff_chunk)
342 diff = diff_chunk[match.end():]
342 diff = diff_chunk[match.end():]
343 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
343 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
344 elif self.vcs == 'hg':
344 elif self.vcs == 'hg':
345 match = self._hg_header_re.match(diff_chunk)
345 match = self._hg_header_re.match(diff_chunk)
346 diff = diff_chunk[match.end():]
346 diff = diff_chunk[match.end():]
347 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
347 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
348 else:
348 else:
349 raise Exception('VCS type %s is not supported' % self.vcs)
349 raise Exception('VCS type %s is not supported' % self.vcs)
350
350
351 def _parse_gitdiff(self, inline_diff=True):
351 def _parse_gitdiff(self, inline_diff=True):
352 _files = []
352 _files = []
353 diff_container = lambda arg: arg
353 diff_container = lambda arg: arg
354
354
355 ##split the diff in chunks of separate --git a/file b/file chunks
355 ##split the diff in chunks of separate --git a/file b/file chunks
356 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
356 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
357 binary = False
357 binary = False
358 binary_msg = 'unknown binary'
358 binary_msg = 'unknown binary'
359 head, diff = self._get_header(raw_diff)
359 head, diff = self._get_header(raw_diff)
360
360
361 if not head['a_file'] and head['b_file']:
361 if not head['a_file'] and head['b_file']:
362 op = 'A'
362 op = 'A'
363 elif head['a_file'] and head['b_file']:
363 elif head['a_file'] and head['b_file']:
364 op = 'M'
364 op = 'M'
365 elif head['a_file'] and not head['b_file']:
365 elif head['a_file'] and not head['b_file']:
366 op = 'D'
366 op = 'D'
367 else:
367 else:
368 #probably we're dealing with a binary file 1
368 #probably we're dealing with a binary file 1
369 binary = True
369 binary = True
370 if head['deleted_file_mode']:
370 if head['deleted_file_mode']:
371 op = 'D'
371 op = 'D'
372 stats = ['b', DEL_FILENODE]
372 stats = ['b', DEL_FILENODE]
373 binary_msg = 'deleted binary file'
373 binary_msg = 'deleted binary file'
374 elif head['new_file_mode']:
374 elif head['new_file_mode']:
375 op = 'A'
375 op = 'A'
376 stats = ['b', NEW_FILENODE]
376 stats = ['b', NEW_FILENODE]
377 binary_msg = 'new binary file %s' % head['new_file_mode']
377 binary_msg = 'new binary file %s' % head['new_file_mode']
378 else:
378 else:
379 if head['new_mode'] and head['old_mode']:
379 if head['new_mode'] and head['old_mode']:
380 stats = ['b', CHMOD_FILENODE]
380 stats = ['b', CHMOD_FILENODE]
381 op = 'M'
381 op = 'M'
382 binary_msg = ('modified binary file chmod %s => %s'
382 binary_msg = ('modified binary file chmod %s => %s'
383 % (head['old_mode'], head['new_mode']))
383 % (head['old_mode'], head['new_mode']))
384 elif (head['rename_from'] and head['rename_to']
384 elif (head['rename_from'] and head['rename_to']
385 and head['rename_from'] != head['rename_to']):
385 and head['rename_from'] != head['rename_to']):
386 stats = ['b', RENAMED_FILENODE]
386 stats = ['b', RENAMED_FILENODE]
387 op = 'M'
387 op = 'M'
388 binary_msg = ('file renamed from %s to %s'
388 binary_msg = ('file renamed from %s to %s'
389 % (head['rename_from'], head['rename_to']))
389 % (head['rename_from'], head['rename_to']))
390 else:
390 else:
391 stats = ['b', MOD_FILENODE]
391 stats = ['b', MOD_FILENODE]
392 op = 'M'
392 op = 'M'
393 binary_msg = 'modified binary file'
393 binary_msg = 'modified binary file'
394
394
395 if not binary:
395 if not binary:
396 try:
396 try:
397 chunks, stats = self._parse_lines(diff)
397 chunks, stats = self._parse_lines(diff)
398 except DiffLimitExceeded:
398 except DiffLimitExceeded:
399 diff_container = lambda _diff: LimitedDiffContainer(
399 diff_container = lambda _diff: LimitedDiffContainer(
400 self.diff_limit,
400 self.diff_limit,
401 self.cur_diff_size,
401 self.cur_diff_size,
402 _diff)
402 _diff)
403 break
403 break
404 else:
404 else:
405 chunks = []
405 chunks = []
406 chunks.append([{
406 chunks.append([{
407 'old_lineno': '',
407 'old_lineno': '',
408 'new_lineno': '',
408 'new_lineno': '',
409 'action': 'binary',
409 'action': 'binary',
410 'line': binary_msg,
410 'line': binary_msg,
411 }])
411 }])
412
412
413 _files.append({
413 _files.append({
414 'filename': head['b_path'],
414 'filename': head['b_path'],
415 'old_revision': head['a_blob_id'],
415 'old_revision': head['a_blob_id'],
416 'new_revision': head['b_blob_id'],
416 'new_revision': head['b_blob_id'],
417 'chunks': chunks,
417 'chunks': chunks,
418 'operation': op,
418 'operation': op,
419 'stats': stats,
419 'stats': stats,
420 })
420 })
421
421
422 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
422 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
423
423
424 if inline_diff is False:
424 if inline_diff is False:
425 return diff_container(sorted(_files, key=sorter))
425 return diff_container(sorted(_files, key=sorter))
426
426
427 # highlight inline changes
427 # highlight inline changes
428 for diff_data in _files:
428 for diff_data in _files:
429 for chunk in diff_data['chunks']:
429 for chunk in diff_data['chunks']:
430 lineiter = iter(chunk)
430 lineiter = iter(chunk)
431 try:
431 try:
432 while 1:
432 while 1:
433 line = lineiter.next()
433 line = lineiter.next()
434 if line['action'] not in ['unmod', 'context']:
434 if line['action'] not in ['unmod', 'context']:
435 nextline = lineiter.next()
435 nextline = lineiter.next()
436 if nextline['action'] in ['unmod', 'context'] or \
436 if nextline['action'] in ['unmod', 'context'] or \
437 nextline['action'] == line['action']:
437 nextline['action'] == line['action']:
438 continue
438 continue
439 self.differ(line, nextline)
439 self.differ(line, nextline)
440 except StopIteration:
440 except StopIteration:
441 pass
441 pass
442
442
443 return diff_container(sorted(_files, key=sorter))
443 return diff_container(sorted(_files, key=sorter))
444
444
445 def _parse_udiff(self, inline_diff=True):
445 def _parse_udiff(self, inline_diff=True):
446 raise NotImplementedError()
446 raise NotImplementedError()
447
447
448 def _parse_lines(self, diff):
448 def _parse_lines(self, diff):
449 """
449 """
450 Parse the diff an return data for the template.
450 Parse the diff an return data for the template.
451 """
451 """
452
452
453 lineiter = iter(diff)
453 lineiter = iter(diff)
454 stats = [0, 0]
454 stats = [0, 0]
455
455
456 try:
456 try:
457 chunks = []
457 chunks = []
458 line = lineiter.next()
458 line = lineiter.next()
459
459
460 while line:
460 while line:
461 lines = []
461 lines = []
462 chunks.append(lines)
462 chunks.append(lines)
463
463
464 match = self._chunk_re.match(line)
464 match = self._chunk_re.match(line)
465
465
466 if not match:
466 if not match:
467 break
467 break
468
468
469 gr = match.groups()
469 gr = match.groups()
470 (old_line, old_end,
470 (old_line, old_end,
471 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
471 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
472 old_line -= 1
472 old_line -= 1
473 new_line -= 1
473 new_line -= 1
474
474
475 context = len(gr) == 5
475 context = len(gr) == 5
476 old_end += old_line
476 old_end += old_line
477 new_end += new_line
477 new_end += new_line
478
478
479 if context:
479 if context:
480 # skip context only if it's first line
480 # skip context only if it's first line
481 if int(gr[0]) > 1:
481 if int(gr[0]) > 1:
482 lines.append({
482 lines.append({
483 'old_lineno': '...',
483 'old_lineno': '...',
484 'new_lineno': '...',
484 'new_lineno': '...',
485 'action': 'context',
485 'action': 'context',
486 'line': line,
486 'line': line,
487 })
487 })
488
488
489 line = lineiter.next()
489 line = lineiter.next()
490
490
491 while old_line < old_end or new_line < new_end:
491 while old_line < old_end or new_line < new_end:
492 if line:
492 if line:
493 command = line[0]
493 command = line[0]
494 if command in ['+', '-', ' ']:
494 if command in ['+', '-', ' ']:
495 #only modify the line if it's actually a diff
495 #only modify the line if it's actually a diff
496 # thing
496 # thing
497 line = line[1:]
497 line = line[1:]
498 else:
498 else:
499 command = ' '
499 command = ' '
500
500
501 affects_old = affects_new = False
501 affects_old = affects_new = False
502
502
503 # ignore those if we don't expect them
503 # ignore those if we don't expect them
504 if command in '#@':
504 if command in '#@':
505 continue
505 continue
506 elif command == '+':
506 elif command == '+':
507 affects_new = True
507 affects_new = True
508 action = 'add'
508 action = 'add'
509 stats[0] += 1
509 stats[0] += 1
510 elif command == '-':
510 elif command == '-':
511 affects_old = True
511 affects_old = True
512 action = 'del'
512 action = 'del'
513 stats[1] += 1
513 stats[1] += 1
514 else:
514 else:
515 affects_old = affects_new = True
515 affects_old = affects_new = True
516 action = 'unmod'
516 action = 'unmod'
517
517
518 if line != self._newline_marker:
518 if line != self._newline_marker:
519 old_line += affects_old
519 old_line += affects_old
520 new_line += affects_new
520 new_line += affects_new
521 lines.append({
521 lines.append({
522 'old_lineno': affects_old and old_line or '',
522 'old_lineno': affects_old and old_line or '',
523 'new_lineno': affects_new and new_line or '',
523 'new_lineno': affects_new and new_line or '',
524 'action': action,
524 'action': action,
525 'line': line
525 'line': line
526 })
526 })
527
527
528 line = lineiter.next()
528 line = lineiter.next()
529
529
530 if line == self._newline_marker:
530 if line == self._newline_marker:
531 # we need to append to lines, since this is not
531 # we need to append to lines, since this is not
532 # counted in the line specs of diff
532 # counted in the line specs of diff
533 lines.append({
533 lines.append({
534 'old_lineno': '...',
534 'old_lineno': '...',
535 'new_lineno': '...',
535 'new_lineno': '...',
536 'action': 'context',
536 'action': 'context',
537 'line': line
537 'line': line
538 })
538 })
539
539
540 except StopIteration:
540 except StopIteration:
541 pass
541 pass
542 return chunks, stats
542 return chunks, stats
543
543
544 def _safe_id(self, idstring):
544 def _safe_id(self, idstring):
545 """Make a string safe for including in an id attribute.
545 """Make a string safe for including in an id attribute.
546
546
547 The HTML spec says that id attributes 'must begin with
547 The HTML spec says that id attributes 'must begin with
548 a letter ([A-Za-z]) and may be followed by any number
548 a letter ([A-Za-z]) and may be followed by any number
549 of letters, digits ([0-9]), hyphens ("-"), underscores
549 of letters, digits ([0-9]), hyphens ("-"), underscores
550 ("_"), colons (":"), and periods (".")'. These regexps
550 ("_"), colons (":"), and periods (".")'. These regexps
551 are slightly over-zealous, in that they remove colons
551 are slightly over-zealous, in that they remove colons
552 and periods unnecessarily.
552 and periods unnecessarily.
553
553
554 Whitespace is transformed into underscores, and then
554 Whitespace is transformed into underscores, and then
555 anything which is not a hyphen or a character that
555 anything which is not a hyphen or a character that
556 matches \w (alphanumerics and underscore) is removed.
556 matches \w (alphanumerics and underscore) is removed.
557
557
558 """
558 """
559 # Transform all whitespace to underscore
559 # Transform all whitespace to underscore
560 idstring = re.sub(r'\s', "_", '%s' % idstring)
560 idstring = re.sub(r'\s', "_", '%s' % idstring)
561 # Remove everything that is not a hyphen or a member of \w
561 # Remove everything that is not a hyphen or a member of \w
562 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
562 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
563 return idstring
563 return idstring
564
564
565 def prepare(self, inline_diff=True):
565 def prepare(self, inline_diff=True):
566 """
566 """
567 Prepare the passed udiff for HTML rendering. It'l return a list
567 Prepare the passed udiff for HTML rendering. It'l return a list
568 of dicts with diff information
568 of dicts with diff information
569 """
569 """
570 parsed = self._parser(inline_diff=inline_diff)
570 parsed = self._parser(inline_diff=inline_diff)
571 self.parsed = True
571 self.parsed = True
572 self.parsed_diff = parsed
572 self.parsed_diff = parsed
573 return parsed
573 return parsed
574
574
575 def as_raw(self, diff_lines=None):
575 def as_raw(self, diff_lines=None):
576 """
576 """
577 Returns raw string diff
577 Returns raw string diff
578 """
578 """
579 return self._diff
579 return self._diff
580 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
580 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
581
581
582 def as_html(self, table_class='code-difftable', line_class='line',
582 def as_html(self, table_class='code-difftable', line_class='line',
583 new_lineno_class='lineno old', old_lineno_class='lineno new',
583 new_lineno_class='lineno old', old_lineno_class='lineno new',
584 code_class='code', enable_comments=False, parsed_lines=None):
584 code_class='code', enable_comments=False, parsed_lines=None):
585 """
585 """
586 Return given diff as html table with customized css classes
586 Return given diff as html table with customized css classes
587 """
587 """
588 def _link_to_if(condition, label, url):
588 def _link_to_if(condition, label, url):
589 """
589 """
590 Generates a link if condition is meet or just the label if not.
590 Generates a link if condition is meet or just the label if not.
591 """
591 """
592
592
593 if condition:
593 if condition:
594 return '''<a href="%(url)s">%(label)s</a>''' % {
594 return '''<a href="%(url)s">%(label)s</a>''' % {
595 'url': url,
595 'url': url,
596 'label': label
596 'label': label
597 }
597 }
598 else:
598 else:
599 return label
599 return label
600 if not self.parsed:
600 if not self.parsed:
601 self.prepare()
601 self.prepare()
602
602
603 diff_lines = self.parsed_diff
603 diff_lines = self.parsed_diff
604 if parsed_lines:
604 if parsed_lines:
605 diff_lines = parsed_lines
605 diff_lines = parsed_lines
606
606
607 _html_empty = True
607 _html_empty = True
608 _html = []
608 _html = []
609 _html.append('''<table class="%(table_class)s">\n''' % {
609 _html.append('''<table class="%(table_class)s">\n''' % {
610 'table_class': table_class
610 'table_class': table_class
611 })
611 })
612
612
613 for diff in diff_lines:
613 for diff in diff_lines:
614 for line in diff['chunks']:
614 for line in diff['chunks']:
615 _html_empty = False
615 _html_empty = False
616 for change in line:
616 for change in line:
617 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
617 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
618 'lc': line_class,
618 'lc': line_class,
619 'action': change['action']
619 'action': change['action']
620 })
620 })
621 anchor_old_id = ''
621 anchor_old_id = ''
622 anchor_new_id = ''
622 anchor_new_id = ''
623 anchor_old = "%(filename)s_o%(oldline_no)s" % {
623 anchor_old = "%(filename)s_o%(oldline_no)s" % {
624 'filename': self._safe_id(diff['filename']),
624 'filename': self._safe_id(diff['filename']),
625 'oldline_no': change['old_lineno']
625 'oldline_no': change['old_lineno']
626 }
626 }
627 anchor_new = "%(filename)s_n%(oldline_no)s" % {
627 anchor_new = "%(filename)s_n%(oldline_no)s" % {
628 'filename': self._safe_id(diff['filename']),
628 'filename': self._safe_id(diff['filename']),
629 'oldline_no': change['new_lineno']
629 'oldline_no': change['new_lineno']
630 }
630 }
631 cond_old = (change['old_lineno'] != '...' and
631 cond_old = (change['old_lineno'] != '...' and
632 change['old_lineno'])
632 change['old_lineno'])
633 cond_new = (change['new_lineno'] != '...' and
633 cond_new = (change['new_lineno'] != '...' and
634 change['new_lineno'])
634 change['new_lineno'])
635 if cond_old:
635 if cond_old:
636 anchor_old_id = 'id="%s"' % anchor_old
636 anchor_old_id = 'id="%s"' % anchor_old
637 if cond_new:
637 if cond_new:
638 anchor_new_id = 'id="%s"' % anchor_new
638 anchor_new_id = 'id="%s"' % anchor_new
639 ###########################################################
639 ###########################################################
640 # OLD LINE NUMBER
640 # OLD LINE NUMBER
641 ###########################################################
641 ###########################################################
642 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
642 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
643 'a_id': anchor_old_id,
643 'a_id': anchor_old_id,
644 'olc': old_lineno_class
644 'olc': old_lineno_class
645 })
645 })
646
646
647 _html.append('''%(link)s''' % {
647 _html.append('''%(link)s''' % {
648 'link': _link_to_if(True, change['old_lineno'],
648 'link': _link_to_if(True, change['old_lineno'],
649 '#%s' % anchor_old)
649 '#%s' % anchor_old)
650 })
650 })
651 _html.append('''</td>\n''')
651 _html.append('''</td>\n''')
652 ###########################################################
652 ###########################################################
653 # NEW LINE NUMBER
653 # NEW LINE NUMBER
654 ###########################################################
654 ###########################################################
655
655
656 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
656 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
657 'a_id': anchor_new_id,
657 'a_id': anchor_new_id,
658 'nlc': new_lineno_class
658 'nlc': new_lineno_class
659 })
659 })
660
660
661 _html.append('''%(link)s''' % {
661 _html.append('''%(link)s''' % {
662 'link': _link_to_if(True, change['new_lineno'],
662 'link': _link_to_if(True, change['new_lineno'],
663 '#%s' % anchor_new)
663 '#%s' % anchor_new)
664 })
664 })
665 _html.append('''</td>\n''')
665 _html.append('''</td>\n''')
666 ###########################################################
666 ###########################################################
667 # CODE
667 # CODE
668 ###########################################################
668 ###########################################################
669 comments = '' if enable_comments else 'no-comment'
669 comments = '' if enable_comments else 'no-comment'
670 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
670 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
671 'cc': code_class,
671 'cc': code_class,
672 'inc': comments
672 'inc': comments
673 })
673 })
674 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
674 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
675 'code': change['line']
675 'code': change['line']
676 })
676 })
677
677
678 _html.append('''\t</td>''')
678 _html.append('''\t</td>''')
679 _html.append('''\n</tr>\n''')
679 _html.append('''\n</tr>\n''')
680 _html.append('''</table>''')
680 _html.append('''</table>''')
681 if _html_empty:
681 if _html_empty:
682 return None
682 return None
683 return ''.join(_html)
683 return ''.join(_html)
684
684
685 def stat(self):
685 def stat(self):
686 """
686 """
687 Returns tuple of added, and removed lines for this instance
687 Returns tuple of added, and removed lines for this instance
688 """
688 """
689 return self.adds, self.removes
689 return self.adds, self.removes
690
690
691
691
692 class InMemoryBundleRepo(bundlerepository):
692 class InMemoryBundleRepo(bundlerepository):
693 def __init__(self, ui, path, bundlestream):
693 def __init__(self, ui, path, bundlestream):
694 self._tempparent = None
694 self._tempparent = None
695 localrepo.localrepository.__init__(self, ui, path)
695 localrepo.localrepository.__init__(self, ui, path)
696 self.ui.setconfig('phases', 'publish', False)
696 self.ui.setconfig('phases', 'publish', False)
697
697
698 self.bundle = bundlestream
698 self.bundle = bundlestream
699
699
700 # dict with the mapping 'filename' -> position in the bundle
700 # dict with the mapping 'filename' -> position in the bundle
701 self.bundlefilespos = {}
701 self.bundlefilespos = {}
702
702
703
703
704 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None,
704 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None,
705 bundle_compare=False, context=3, ignore_whitespace=False):
705 bundle_compare=False, context=3, ignore_whitespace=False):
706 """
706 """
707 General differ between branches, bookmarks, revisions of two remote related
707 General differ between branches, bookmarks, revisions of two remote related
708 repositories
708 repositories
709
709
710 :param org_repo:
710 :param org_repo:
711 :type org_repo:
711 :type org_repo:
712 :param org_ref:
712 :param org_ref:
713 :type org_ref:
713 :type org_ref:
714 :param other_repo:
714 :param other_repo:
715 :type other_repo:
715 :type other_repo:
716 :param other_ref:
716 :param other_ref:
717 :type other_ref:
717 :type other_ref:
718 """
718 """
719
719
720 bundlerepo = None
720 bundlerepo = None
721 ignore_whitespace = ignore_whitespace
721 ignore_whitespace = ignore_whitespace
722 context = context
722 context = context
723 org_repo = org_repo.scm_instance._repo
723 org_repo_scm = org_repo.scm_instance
724 org_repo = org_repo_scm._repo
724 other_repo = other_repo.scm_instance._repo
725 other_repo = other_repo.scm_instance._repo
725 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
726 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
726 org_ref = org_ref[1]
727 org_ref = org_ref[1]
727 other_ref = other_ref[1]
728 other_ref = other_ref[1]
728
729
729 if org_repo != other_repo and bundle_compare:
730 if org_repo == other_repo:
731 log.debug('running diff between %s@%s and %s@%s'
732 % (org_repo, org_ref, other_repo, other_ref))
733 _diff = org_repo_scm.get_diff(rev1=other_ref, rev2=org_ref,
734 ignore_whitespace=ignore_whitespace, context=context)
735 return _diff
736
737 elif bundle_compare:
730
738
731 common, incoming, rheads = discovery_data
739 common, incoming, rheads = discovery_data
732 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
740 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
733 # create a bundle (uncompressed if other repo is not local)
741 # create a bundle (uncompressed if other repo is not local)
734 if other_repo_peer.capable('getbundle') and incoming:
742 if other_repo_peer.capable('getbundle') and incoming:
735 # disable repo hooks here since it's just bundle !
743 # disable repo hooks here since it's just bundle !
736 # patch and reset hooks section of UI config to not run any
744 # patch and reset hooks section of UI config to not run any
737 # hooks on fetching archives with subrepos
745 # hooks on fetching archives with subrepos
738 for k, _ in other_repo.ui.configitems('hooks'):
746 for k, _ in other_repo.ui.configitems('hooks'):
739 other_repo.ui.setconfig('hooks', k, None)
747 other_repo.ui.setconfig('hooks', k, None)
740
748
741 unbundle = other_repo.getbundle('incoming', common=common,
749 unbundle = other_repo.getbundle('incoming', common=common,
742 heads=None)
750 heads=None)
743
751
744 buf = BytesIO()
752 buf = BytesIO()
745 while True:
753 while True:
746 chunk = unbundle._stream.read(1024 * 4)
754 chunk = unbundle._stream.read(1024 * 4)
747 if not chunk:
755 if not chunk:
748 break
756 break
749 buf.write(chunk)
757 buf.write(chunk)
750
758
751 buf.seek(0)
759 buf.seek(0)
752 # replace chunked _stream with data that can do tell() and seek()
760 # replace chunked _stream with data that can do tell() and seek()
753 unbundle._stream = buf
761 unbundle._stream = buf
754
762
755 ui = make_ui('db')
763 ui = make_ui('db')
756 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
764 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
757 bundlestream=unbundle)
765 bundlestream=unbundle)
758
766
759 return ''.join(patch.diff(bundlerepo or org_repo,
767 return ''.join(patch.diff(bundlerepo or org_repo,
760 node1=org_repo[org_ref].node(),
768 node1=org_repo[org_ref].node(),
761 node2=other_repo[other_ref].node(),
769 node2=other_repo[other_ref].node(),
762 opts=opts))
770 opts=opts))
763 else:
771
764 log.debug('running diff between %s@%s and %s@%s'
765 % (org_repo, org_ref, other_repo, other_ref))
766 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
767 opts=opts))
@@ -1,262 +1,277 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.pull_request
3 rhodecode.model.pull_request
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull request model for RhodeCode
6 pull request model for RhodeCode
7
7
8 :created_on: Jun 6, 2012
8 :created_on: Jun 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2012-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 import datetime
28 import datetime
29 import re
29
30
30 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
31
32
32 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
36 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
36 from rhodecode.model.notification import NotificationModel
37 from rhodecode.model.notification import NotificationModel
37 from rhodecode.lib.utils2 import safe_unicode
38 from rhodecode.lib.utils2 import safe_unicode
38
39
39 from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil, \
40 from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil, \
40 findcommonoutgoing
41 findcommonoutgoing
41
42
42 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
43
44
44
45
45 class PullRequestModel(BaseModel):
46 class PullRequestModel(BaseModel):
46
47
47 cls = PullRequest
48 cls = PullRequest
48
49
49 def __get_pull_request(self, pull_request):
50 def __get_pull_request(self, pull_request):
50 return self._get_instance(PullRequest, pull_request)
51 return self._get_instance(PullRequest, pull_request)
51
52
52 def get_all(self, repo):
53 def get_all(self, repo):
53 repo = self._get_repo(repo)
54 repo = self._get_repo(repo)
54 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
55 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
55
56
56 def create(self, created_by, org_repo, org_ref, other_repo,
57 def create(self, created_by, org_repo, org_ref, other_repo,
57 other_ref, revisions, reviewers, title, description=None):
58 other_ref, revisions, reviewers, title, description=None):
58
59
59 created_by_user = self._get_user(created_by)
60 created_by_user = self._get_user(created_by)
60 org_repo = self._get_repo(org_repo)
61 org_repo = self._get_repo(org_repo)
61 other_repo = self._get_repo(other_repo)
62 other_repo = self._get_repo(other_repo)
62
63
63 new = PullRequest()
64 new = PullRequest()
64 new.org_repo = org_repo
65 new.org_repo = org_repo
65 new.org_ref = org_ref
66 new.org_ref = org_ref
66 new.other_repo = other_repo
67 new.other_repo = other_repo
67 new.other_ref = other_ref
68 new.other_ref = other_ref
68 new.revisions = revisions
69 new.revisions = revisions
69 new.title = title
70 new.title = title
70 new.description = description
71 new.description = description
71 new.author = created_by_user
72 new.author = created_by_user
72 self.sa.add(new)
73 self.sa.add(new)
73 Session().flush()
74 Session().flush()
74 #members
75 #members
75 for member in reviewers:
76 for member in reviewers:
76 _usr = self._get_user(member)
77 _usr = self._get_user(member)
77 reviewer = PullRequestReviewers(_usr, new)
78 reviewer = PullRequestReviewers(_usr, new)
78 self.sa.add(reviewer)
79 self.sa.add(reviewer)
79
80
80 #notification to reviewers
81 #notification to reviewers
81 notif = NotificationModel()
82 notif = NotificationModel()
82
83
83 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
84 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
84 pull_request_id=new.pull_request_id,
85 pull_request_id=new.pull_request_id,
85 qualified=True,
86 qualified=True,
86 )
87 )
87 subject = safe_unicode(
88 subject = safe_unicode(
88 h.link_to(
89 h.link_to(
89 _('%(user)s wants you to review pull request #%(pr_id)s') % \
90 _('%(user)s wants you to review pull request #%(pr_id)s') % \
90 {'user': created_by_user.username,
91 {'user': created_by_user.username,
91 'pr_id': new.pull_request_id},
92 'pr_id': new.pull_request_id},
92 pr_url
93 pr_url
93 )
94 )
94 )
95 )
95 body = description
96 body = description
96 kwargs = {
97 kwargs = {
97 'pr_title': title,
98 'pr_title': title,
98 'pr_user_created': h.person(created_by_user.email),
99 'pr_user_created': h.person(created_by_user.email),
99 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
100 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
100 qualified=True,),
101 qualified=True,),
101 'pr_url': pr_url,
102 'pr_url': pr_url,
102 'pr_revisions': revisions
103 'pr_revisions': revisions
103 }
104 }
104 notif.create(created_by=created_by_user, subject=subject, body=body,
105 notif.create(created_by=created_by_user, subject=subject, body=body,
105 recipients=reviewers,
106 recipients=reviewers,
106 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
107 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
107 return new
108 return new
108
109
109 def update_reviewers(self, pull_request, reviewers_ids):
110 def update_reviewers(self, pull_request, reviewers_ids):
110 reviewers_ids = set(reviewers_ids)
111 reviewers_ids = set(reviewers_ids)
111 pull_request = self.__get_pull_request(pull_request)
112 pull_request = self.__get_pull_request(pull_request)
112 current_reviewers = PullRequestReviewers.query()\
113 current_reviewers = PullRequestReviewers.query()\
113 .filter(PullRequestReviewers.pull_request==
114 .filter(PullRequestReviewers.pull_request==
114 pull_request)\
115 pull_request)\
115 .all()
116 .all()
116 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
117 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
117
118
118 to_add = reviewers_ids.difference(current_reviewers_ids)
119 to_add = reviewers_ids.difference(current_reviewers_ids)
119 to_remove = current_reviewers_ids.difference(reviewers_ids)
120 to_remove = current_reviewers_ids.difference(reviewers_ids)
120
121
121 log.debug("Adding %s reviewers" % to_add)
122 log.debug("Adding %s reviewers" % to_add)
122 log.debug("Removing %s reviewers" % to_remove)
123 log.debug("Removing %s reviewers" % to_remove)
123
124
124 for uid in to_add:
125 for uid in to_add:
125 _usr = self._get_user(uid)
126 _usr = self._get_user(uid)
126 reviewer = PullRequestReviewers(_usr, pull_request)
127 reviewer = PullRequestReviewers(_usr, pull_request)
127 self.sa.add(reviewer)
128 self.sa.add(reviewer)
128
129
129 for uid in to_remove:
130 for uid in to_remove:
130 reviewer = PullRequestReviewers.query()\
131 reviewer = PullRequestReviewers.query()\
131 .filter(PullRequestReviewers.user_id==uid,
132 .filter(PullRequestReviewers.user_id==uid,
132 PullRequestReviewers.pull_request==pull_request)\
133 PullRequestReviewers.pull_request==pull_request)\
133 .scalar()
134 .scalar()
134 if reviewer:
135 if reviewer:
135 self.sa.delete(reviewer)
136 self.sa.delete(reviewer)
136
137
137 def delete(self, pull_request):
138 def delete(self, pull_request):
138 pull_request = self.__get_pull_request(pull_request)
139 pull_request = self.__get_pull_request(pull_request)
139 Session().delete(pull_request)
140 Session().delete(pull_request)
140
141
141 def close_pull_request(self, pull_request):
142 def close_pull_request(self, pull_request):
142 pull_request = self.__get_pull_request(pull_request)
143 pull_request = self.__get_pull_request(pull_request)
143 pull_request.status = PullRequest.STATUS_CLOSED
144 pull_request.status = PullRequest.STATUS_CLOSED
144 pull_request.updated_on = datetime.datetime.now()
145 pull_request.updated_on = datetime.datetime.now()
145 self.sa.add(pull_request)
146 self.sa.add(pull_request)
146
147
147 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
148 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref,
148 discovery_data):
149 discovery_data):
149 """
150 """
150 Returns a list of changesets that are incoming from org_repo@org_ref
151 Returns a list of changesets that are incoming from org_repo@org_ref
151 to other_repo@other_ref
152 to other_repo@other_ref
152
153
153 :param org_repo:
154 :param org_repo:
154 :type org_repo:
155 :type org_repo:
155 :param org_ref:
156 :param org_ref:
156 :type org_ref:
157 :type org_ref:
157 :param other_repo:
158 :param other_repo:
158 :type other_repo:
159 :type other_repo:
159 :param other_ref:
160 :param other_ref:
160 :type other_ref:
161 :type other_ref:
161 :param tmp:
162 :param tmp:
162 :type tmp:
163 :type tmp:
163 """
164 """
164 changesets = []
165 changesets = []
165 #case two independent repos
166 #case two independent repos
166 common, incoming, rheads = discovery_data
167 common, incoming, rheads = discovery_data
167 if org_repo != other_repo and incoming:
168 if org_repo != other_repo and incoming:
168 obj = findcommonoutgoing(org_repo._repo,
169 obj = findcommonoutgoing(org_repo._repo,
169 localrepo.locallegacypeer(other_repo._repo.local()),
170 localrepo.locallegacypeer(other_repo._repo.local()),
170 force=True)
171 force=True)
171 revs = obj.missing
172 revs = obj.missing
172
173
173 for cs in reversed(map(binascii.hexlify, revs)):
174 for cs in reversed(map(binascii.hexlify, revs)):
174 changesets.append(org_repo.get_changeset(cs))
175 changesets.append(org_repo.get_changeset(cs))
175 else:
176 else:
177 #no remote compare do it on the same repository
178 if alias == 'hg':
176 _revset_predicates = {
179 _revset_predicates = {
177 'branch': 'branch',
180 'branch': 'branch',
178 'book': 'bookmark',
181 'book': 'bookmark',
179 'tag': 'tag',
182 'tag': 'tag',
180 'rev': 'id',
183 'rev': 'id',
181 }
184 }
182
185
183 revs = [
186 revs = [
184 "ancestors(%s('%s')) and not ancestors(%s('%s'))" % (
187 "ancestors(%s('%s')) and not ancestors(%s('%s'))" % (
185 _revset_predicates[org_ref[0]], org_ref[1],
188 _revset_predicates[org_ref[0]], org_ref[1],
186 _revset_predicates[other_ref[0]], other_ref[1]
189 _revset_predicates[other_ref[0]], other_ref[1]
187 )
190 )
188 ]
191 ]
189
192
190 out = scmutil.revrange(org_repo._repo, revs)
193 out = scmutil.revrange(org_repo._repo, revs)
191 for cs in reversed(out):
194 for cs in reversed(out):
192 changesets.append(org_repo.get_changeset(cs))
195 changesets.append(org_repo.get_changeset(cs))
196 elif alias == 'git':
197 so, se = org_repo.run_git_command(
198 'log --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
199 other_ref[1])
200 )
201 ids = re.findall(r'[0-9a-fA-F]{40}', so)
202 for cs in reversed(ids):
203 changesets.append(org_repo.get_changeset(cs))
193
204
194 return changesets
205 return changesets
195
206
196 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
207 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
197 """
208 """
198 Get's mercurial discovery data used to calculate difference between
209 Get's mercurial discovery data used to calculate difference between
199 repos and refs
210 repos and refs
200
211
201 :param org_repo:
212 :param org_repo:
202 :type org_repo:
213 :type org_repo:
203 :param org_ref:
214 :param org_ref:
204 :type org_ref:
215 :type org_ref:
205 :param other_repo:
216 :param other_repo:
206 :type other_repo:
217 :type other_repo:
207 :param other_ref:
218 :param other_ref:
208 :type other_ref:
219 :type other_ref:
209 """
220 """
210
221
211 _org_repo = org_repo._repo
222 _org_repo = org_repo._repo
212 org_rev_type, org_rev = org_ref
223 org_rev_type, org_rev = org_ref
213
224
214 _other_repo = other_repo._repo
225 _other_repo = other_repo._repo
215 other_rev_type, other_rev = other_ref
226 other_rev_type, other_rev = other_ref
216
227
217 log.debug('Doing discovery for %s@%s vs %s@%s' % (
228 log.debug('Doing discovery for %s@%s vs %s@%s' % (
218 org_repo, org_ref, other_repo, other_ref)
229 org_repo, org_ref, other_repo, other_ref)
219 )
230 )
220
231
221 #log.debug('Filter heads are %s[%s]' % ('', org_ref[1]))
232 #log.debug('Filter heads are %s[%s]' % ('', org_ref[1]))
222 org_peer = localrepo.locallegacypeer(_org_repo.local())
233 org_peer = localrepo.locallegacypeer(_org_repo.local())
223 tmp = discovery.findcommonincoming(
234 tmp = discovery.findcommonincoming(
224 repo=_other_repo, # other_repo we check for incoming
235 repo=_other_repo, # other_repo we check for incoming
225 remote=org_peer, # org_repo source for incoming
236 remote=org_peer, # org_repo source for incoming
226 heads=[_other_repo[other_rev].node(),
237 heads=[_other_repo[other_rev].node(),
227 _org_repo[org_rev].node()],
238 _org_repo[org_rev].node()],
228 force=True
239 force=True
229 )
240 )
230 return tmp
241 return tmp
231
242
232 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
243 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
233 """
244 """
234 Returns a tuple of incomming changesets, and discoverydata cache
245 Returns a tuple of incomming changesets, and discoverydata cache for
246 mercurial repositories
235
247
236 :param org_repo:
248 :param org_repo:
237 :type org_repo:
249 :type org_repo:
238 :param org_ref:
250 :param org_ref:
239 :type org_ref:
251 :type org_ref:
240 :param other_repo:
252 :param other_repo:
241 :type other_repo:
253 :type other_repo:
242 :param other_ref:
254 :param other_ref:
243 :type other_ref:
255 :type other_ref:
244 """
256 """
245
257
246 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
258 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
247 raise Exception('org_ref must be a two element list/tuple')
259 raise Exception('org_ref must be a two element list/tuple')
248
260
249 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
261 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
250 raise Exception('other_ref must be a two element list/tuple')
262 raise Exception('other_ref must be a two element list/tuple')
251
263
252 discovery_data = self._get_discovery(org_repo.scm_instance,
264 org_repo_scm = org_repo.scm_instance
253 org_ref,
265 other_repo_scm = other_repo.scm_instance
254 other_repo.scm_instance,
266
255 other_ref)
267 alias = org_repo.scm_instance.alias
256 cs_ranges = self._get_changesets(org_repo.scm_instance,
268 discovery_data = [None, None, None]
257 org_ref,
269 if alias == 'hg':
258 other_repo.scm_instance,
270 discovery_data = self._get_discovery(org_repo_scm, org_ref,
259 other_ref,
271 other_repo_scm, other_ref)
272 cs_ranges = self._get_changesets(alias,
273 org_repo_scm, org_ref,
274 other_repo_scm, other_ref,
260 discovery_data)
275 discovery_data)
261
276
262 return cs_ranges, discovery_data
277 return cs_ranges, discovery_data
@@ -1,94 +1,93 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Branches') % c.repo_name} - ${c.rhodecode_name}
5 ${_('%s Branches') % c.repo_name} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 <input class="q_filter_box" id="q_filter_branches" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
9 <input class="q_filter_box" id="q_filter_branches" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
10 ${h.link_to(_(u'Home'),h.url('/'))}
10 ${h.link_to(_(u'Home'),h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 ${_('branches')}
14 ${_('branches')}
15 </%def>
15 </%def>
16
16
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('branches')}
18 ${self.menu('branches')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <!-- end box / title -->
27 <!-- end box / title -->
28 %if c.repo_branches:
28 %if c.repo_branches:
29 <div class="info_box" id="compare_branches" style="clear: both;padding: 10px 19px;vertical-align: right;text-align: right;"><a href="#" class="ui-btn small">${_('Compare branches')}</a></div>
29 <div class="info_box" id="compare_branches" style="clear: both;padding: 10px 19px;vertical-align: right;text-align: right;"><a href="#" class="ui-btn small">${_('Compare branches')}</a></div>
30 %endif
30 %endif
31 <div class="table">
31 <div class="table">
32 <%include file='branches_data.html'/>
32 <%include file='branches_data.html'/>
33 </div>
33 </div>
34 </div>
34 </div>
35 <script type="text/javascript">
35 <script type="text/javascript">
36 YUE.on('compare_branches','click',function(e){
36 YUE.on('compare_branches','click',function(e){
37 YUE.preventDefault(e);
37 YUE.preventDefault(e);
38 var org = YUQ('input[name=compare_org]:checked')[0];
38 var org = YUQ('input[name=compare_org]:checked')[0];
39 var other = YUQ('input[name=compare_other]:checked')[0];
39 var other = YUQ('input[name=compare_other]:checked')[0];
40
40
41 if(org && other){
41 if(org && other){
42 var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref='__ORG__',other_ref_type='branch',other_ref='__OTHER__')}";
42 var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref='__ORG__',other_ref_type='branch',other_ref='__OTHER__')}";
43 var u = compare_url.replace('__ORG__',org.value)
43 var u = compare_url.replace('__ORG__',org.value)
44 .replace('__OTHER__',other.value);
44 .replace('__OTHER__',other.value);
45 window.location=u;
45 window.location=u;
46 }
46 }
47
47 });
48 })
49 // main table sorting
48 // main table sorting
50 var myColumnDefs = [
49 var myColumnDefs = [
51 {key:"name",label:"${_('Name')}",sortable:true},
50 {key:"name",label:"${_('Name')}",sortable:true},
52 {key:"date",label:"${_('Date')}",sortable:true,
51 {key:"date",label:"${_('Date')}",sortable:true,
53 sortOptions: { sortFunction: dateSort }},
52 sortOptions: { sortFunction: dateSort }},
54 {key:"author",label:"${_('Author')}",sortable:true},
53 {key:"author",label:"${_('Author')}",sortable:true},
55 {key:"revision",label:"${_('Revision')}",sortable:true,
54 {key:"revision",label:"${_('Revision')}",sortable:true,
56 sortOptions: { sortFunction: revisionSort }},
55 sortOptions: { sortFunction: revisionSort }},
57 {key:"compare",label:"${_('Compare')}",sortable:false,},
56 {key:"compare",label:"${_('Compare')}",sortable:false,},
58 ];
57 ];
59
58
60 var myDataSource = new YAHOO.util.DataSource(YUD.get("branches_data"));
59 var myDataSource = new YAHOO.util.DataSource(YUD.get("branches_data"));
61
60
62 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
61 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
63
62
64 myDataSource.responseSchema = {
63 myDataSource.responseSchema = {
65 fields: [
64 fields: [
66 {key:"name"},
65 {key:"name"},
67 {key:"date"},
66 {key:"date"},
68 {key:"author"},
67 {key:"author"},
69 {key:"revision"},
68 {key:"revision"},
70 {key:"compare"},
69 {key:"compare"},
71 ]
70 ]
72 };
71 };
73
72
74 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
73 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
75 {
74 {
76 sortedBy:{key:"name",dir:"asc"},
75 sortedBy:{key:"name",dir:"asc"},
77 MSG_SORTASC:"${_('Click to sort ascending')}",
76 MSG_SORTASC:"${_('Click to sort ascending')}",
78 MSG_SORTDESC:"${_('Click to sort descending')}",
77 MSG_SORTDESC:"${_('Click to sort descending')}",
79 MSG_EMPTY:"${_('No records found.')}",
78 MSG_EMPTY:"${_('No records found.')}",
80 MSG_ERROR:"${_('Data error.')}",
79 MSG_ERROR:"${_('Data error.')}",
81 MSG_LOADING:"${_('Loading...')}",
80 MSG_LOADING:"${_('Loading...')}",
82 }
81 }
83 );
82 );
84 myDataTable.subscribe('postRenderEvent',function(oArgs) {
83 myDataTable.subscribe('postRenderEvent',function(oArgs) {
85 tooltip_activate();
84 tooltip_activate();
86 var func = function(node){
85 var func = function(node){
87 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
86 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
88 }
87 }
89 q_filter('q_filter_branches',YUQ('div.table tr td .logtags .branchtag a'),func);
88 q_filter('q_filter_branches',YUQ('div.table tr td .logtags .branchtag a'),func);
90 });
89 });
91
90
92 </script>
91 </script>
93
92
94 </%def>
93 </%def>
@@ -1,58 +1,58 b''
1 %if c.repo_branches:
1 %if c.repo_branches:
2 <div id="table_wrap" class="yui-skin-sam">
2 <div id="table_wrap" class="yui-skin-sam">
3 <table id="branches_data">
3 <table id="branches_data">
4 <thead>
4 <thead>
5 <tr>
5 <tr>
6 <th class="left">${_('name')}</th>
6 <th class="left">${_('Name')}</th>
7 <th class="left">${_('date')}</th>
7 <th class="left">${_('Date')}</th>
8 <th class="left">${_('author')}</th>
8 <th class="left">${_('Author')}</th>
9 <th class="left">${_('revision')}</th>
9 <th class="left">${_('Revision')}</th>
10 <th class="left">${_('compare')}</th>
10 <th class="left">${_('Compare')}</th>
11 </tr>
11 </tr>
12 </thead>
12 </thead>
13 %for cnt,branch in enumerate(c.repo_branches.items()):
13 %for cnt,branch in enumerate(c.repo_branches.items()):
14 <tr class="parity${cnt%2}">
14 <tr class="parity${cnt%2}">
15 <td>
15 <td>
16 <span class="logtags">
16 <span class="logtags">
17 <span class="branchtag">${h.link_to(branch[0],
17 <span class="branchtag">${h.link_to(branch[0],
18 h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
18 h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
19 </span>
19 </span>
20 </td>
20 </td>
21 <td><span class="tooltip" title="${h.tooltip(h.age(branch[1].date))}">${h.fmt_date(branch[1].date)}</span></td>
21 <td><span class="tooltip" title="${h.tooltip(h.age(branch[1].date))}">${h.fmt_date(branch[1].date)}</span></td>
22 <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
22 <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
23 <td>
23 <td>
24 <div>
24 <div>
25 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
25 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
26 </div>
26 </div>
27 </td>
27 </td>
28 <td>
28 <td>
29 <input class="branch-compare" type="radio" name="compare_org" value="${branch[0]}"/>
29 <input class="branch-compare" type="radio" name="compare_org" value="${branch[0]}"/>
30 <input class="branch-compare" type="radio" name="compare_other" value="${branch[0]}"/>
30 <input class="branch-compare" type="radio" name="compare_other" value="${branch[0]}"/>
31 </td>
31 </td>
32 </tr>
32 </tr>
33 %endfor
33 %endfor
34 % if hasattr(c,'repo_closed_branches') and c.repo_closed_branches:
34 % if hasattr(c,'repo_closed_branches') and c.repo_closed_branches:
35 %for cnt,branch in enumerate(c.repo_closed_branches.items()):
35 %for cnt,branch in enumerate(c.repo_closed_branches.items()):
36 <tr class="parity${cnt%2}">
36 <tr class="parity${cnt%2}">
37 <td>
37 <td>
38 <span class="logtags">
38 <span class="logtags">
39 <span class="branchtag">${h.link_to(branch[0]+' [closed]',
39 <span class="branchtag">${h.link_to(branch[0]+' [closed]',
40 h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
40 h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
41 </span>
41 </span>
42 </td>
42 </td>
43 <td><span class="tooltip" title="${h.tooltip(h.age(branch[1].date))}">${h.fmt_date(branch[1].date)}</span></td>
43 <td><span class="tooltip" title="${h.tooltip(h.age(branch[1].date))}">${h.fmt_date(branch[1].date)}</span></td>
44 <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
44 <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
45 <td>
45 <td>
46 <div>
46 <div>
47 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
47 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
48 </div>
48 </div>
49 </td>
49 </td>
50 <td></td>
50 <td></td>
51 </tr>
51 </tr>
52 %endfor
52 %endfor
53 %endif
53 %endif
54 </table>
54 </table>
55 </div>
55 </div>
56 %else:
56 %else:
57 ${_('There are no branches yet')}
57 ${_('There are no branches yet')}
58 %endif
58 %endif
@@ -1,303 +1,303 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_(u'Home'),h.url('/'))}
10 ${h.link_to(_(u'Home'),h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
15 ${_('Changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
15 ${_('Changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
16 </%def>
16 </%def>
17
17
18 <%def name="page_nav()">
18 <%def name="page_nav()">
19 ${self.menu('changelog')}
19 ${self.menu('changelog')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24 <!-- box / title -->
24 <!-- box / title -->
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class="table">
28 <div class="table">
29 % if c.pagination:
29 % if c.pagination:
30 <div id="graph">
30 <div id="graph">
31 <div id="graph_nodes">
31 <div id="graph_nodes">
32 <canvas id="graph_canvas"></canvas>
32 <canvas id="graph_canvas"></canvas>
33 </div>
33 </div>
34 <div id="graph_content">
34 <div id="graph_content">
35 <div class="info_box" style="clear: both;padding: 10px 6px;vertical-align: right;text-align: right;">
35 <div class="info_box" style="clear: both;padding: 10px 6px;vertical-align: right;text-align: right;">
36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
38
38
39 %if c.rhodecode_db_repo.fork:
39 %if c.rhodecode_db_repo.fork:
40 <a title="${_('compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref=request.GET.get('branch') or 'default',other_ref_type='branch',other_ref='default',repo=c.rhodecode_db_repo.fork.repo_name)}" class="ui-btn small">${_('Compare fork')}</a>
40 <a title="${_('compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref=request.GET.get('branch') or 'default',other_ref_type='branch',other_ref='default',repo=c.rhodecode_db_repo.fork.repo_name)}" class="ui-btn small">${_('Compare fork with parent')}</a>
41 %endif
41 %endif
42 %if h.is_hg(c.rhodecode_repo):
42 %if h.is_hg(c.rhodecode_repo):
43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
44 %endif
44 %endif
45 </div>
45 </div>
46 <div class="container_header">
46 <div class="container_header">
47 ${h.form(h.url.current(),method='get')}
47 ${h.form(h.url.current(),method='get')}
48 <div class="info_box" style="float:left">
48 <div class="info_box" style="float:left">
49 ${h.submit('set',_('Show'),class_="ui-btn")}
49 ${h.submit('set',_('Show'),class_="ui-btn")}
50 ${h.text('size',size=1,value=c.size)}
50 ${h.text('size',size=1,value=c.size)}
51 ${_('revisions')}
51 ${_('revisions')}
52 </div>
52 </div>
53 ${h.end_form()}
53 ${h.end_form()}
54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
55 </div>
55 </div>
56
56
57 %for cnt,cs in enumerate(c.pagination):
57 %for cnt,cs in enumerate(c.pagination):
58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
59 <div class="left">
59 <div class="left">
60 <div>
60 <div>
61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
63 </div>
63 </div>
64 <div class="author">
64 <div class="author">
65 <div class="gravatar">
65 <div class="gravatar">
66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
67 </div>
67 </div>
68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
69 </div>
69 </div>
70 <div class="date">${h.fmt_date(cs.date)}</div>
70 <div class="date">${h.fmt_date(cs.date)}</div>
71 </div>
71 </div>
72 <div class="mid">
72 <div class="mid">
73 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
73 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
74 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
74 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
75 </div>
75 </div>
76 <div class="right">
76 <div class="right">
77 <div class="changes">
77 <div class="changes">
78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
79 <div class="comments-container">
79 <div class="comments-container">
80 %if len(c.comments.get(cs.raw_id,[])) > 0:
80 %if len(c.comments.get(cs.raw_id,[])) > 0:
81 <div class="comments-cnt" title="${('comments')}">
81 <div class="comments-cnt" title="${('comments')}">
82 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
82 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
84 <img src="${h.url('/images/icons/comments.png')}">
84 <img src="${h.url('/images/icons/comments.png')}">
85 </a>
85 </a>
86 </div>
86 </div>
87 %endif
87 %endif
88 </div>
88 </div>
89 <div class="changeset-status-container">
89 <div class="changeset-status-container">
90 %if c.statuses.get(cs.raw_id):
90 %if c.statuses.get(cs.raw_id):
91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
92 <div class="changeset-status-ico">
92 <div class="changeset-status-ico">
93 %if c.statuses.get(cs.raw_id)[2]:
93 %if c.statuses.get(cs.raw_id)[2]:
94 <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
94 <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
95 %else:
95 %else:
96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
97 %endif
97 %endif
98 </div>
98 </div>
99 %endif
99 %endif
100 </div>
100 </div>
101 </div>
101 </div>
102 %if cs.parents:
102 %if cs.parents:
103 %for p_cs in reversed(cs.parents):
103 %for p_cs in reversed(cs.parents):
104 <div class="parent">${_('Parent')}
104 <div class="parent">${_('Parent')}
105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
107 </div>
107 </div>
108 %endfor
108 %endfor
109 %else:
109 %else:
110 <div class="parent">${_('No parents')}</div>
110 <div class="parent">${_('No parents')}</div>
111 %endif
111 %endif
112
112
113 <span class="logtags">
113 <span class="logtags">
114 %if len(cs.parents)>1:
114 %if len(cs.parents)>1:
115 <span class="merge">${_('merge')}</span>
115 <span class="merge">${_('merge')}</span>
116 %endif
116 %endif
117 %if cs.branch:
117 %if cs.branch:
118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
120 </span>
120 </span>
121 %endif
121 %endif
122 %if h.is_hg(c.rhodecode_repo):
122 %if h.is_hg(c.rhodecode_repo):
123 %for book in cs.bookmarks:
123 %for book in cs.bookmarks:
124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
126 </span>
126 </span>
127 %endfor
127 %endfor
128 %endif
128 %endif
129 %for tag in cs.tags:
129 %for tag in cs.tags:
130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
132 %endfor
132 %endfor
133 </span>
133 </span>
134 </div>
134 </div>
135 </div>
135 </div>
136
136
137 %endfor
137 %endfor
138 <div class="pagination-wh pagination-left">
138 <div class="pagination-wh pagination-left">
139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
140 </div>
140 </div>
141 </div>
141 </div>
142 </div>
142 </div>
143
143
144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
145 <script type="text/javascript">
145 <script type="text/javascript">
146 YAHOO.util.Event.onDOMReady(function(){
146 YAHOO.util.Event.onDOMReady(function(){
147
147
148 //Monitor range checkboxes and build a link to changesets
148 //Monitor range checkboxes and build a link to changesets
149 //ranges
149 //ranges
150 var checkboxes = YUD.getElementsByClassName('changeset_range');
150 var checkboxes = YUD.getElementsByClassName('changeset_range');
151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
152 YUE.on(checkboxes,'click',function(e){
152 YUE.on(checkboxes,'click',function(e){
153 var clicked_cb = e.currentTarget;
153 var clicked_cb = e.currentTarget;
154 var checked_checkboxes = [];
154 var checked_checkboxes = [];
155 for (pos in checkboxes){
155 for (pos in checkboxes){
156 if(checkboxes[pos].checked){
156 if(checkboxes[pos].checked){
157 checked_checkboxes.push(checkboxes[pos]);
157 checked_checkboxes.push(checkboxes[pos]);
158 }
158 }
159 }
159 }
160 if(YUD.get('open_new_pr')){
160 if(YUD.get('open_new_pr')){
161 if(checked_checkboxes.length>0){
161 if(checked_checkboxes.length>0){
162 // modify open pull request to show we have selected cs
162 // modify open pull request to show we have selected cs
163 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
163 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
164
164
165 }else{
165 }else{
166 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
166 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
167 }
167 }
168 }
168 }
169
169
170 if(checked_checkboxes.length>1){
170 if(checked_checkboxes.length>1){
171 var rev_end = checked_checkboxes[0].name;
171 var rev_end = checked_checkboxes[0].name;
172 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
172 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
173
173
174 // now select all checkboxes in the middle.
174 // now select all checkboxes in the middle.
175 var checked = false;
175 var checked = false;
176 for (var i=0; i<checkboxes.length; i++){
176 for (var i=0; i<checkboxes.length; i++){
177 var cb = checkboxes[i];
177 var cb = checkboxes[i];
178 var rev = cb.name;
178 var rev = cb.name;
179
179
180 if (rev == rev_end){
180 if (rev == rev_end){
181 checked = true;
181 checked = true;
182 }
182 }
183 if (checked){
183 if (checked){
184 cb.checked = true;
184 cb.checked = true;
185 }
185 }
186 else{
186 else{
187 cb.checked = false;
187 cb.checked = false;
188 }
188 }
189 if (rev == rev_start){
189 if (rev == rev_start){
190 checked = false;
190 checked = false;
191 }
191 }
192
192
193 }
193 }
194
194
195 var url = url_tmpl.replace('__REVRANGE__',
195 var url = url_tmpl.replace('__REVRANGE__',
196 rev_start+'...'+rev_end);
196 rev_start+'...'+rev_end);
197
197
198 var link = _TM['Show selected changes __S -> __E'];
198 var link = _TM['Show selected changes __S -> __E'];
199 link = link.replace('__S',rev_start.substr(0,6));
199 link = link.replace('__S',rev_start.substr(0,6));
200 link = link.replace('__E',rev_end.substr(0,6));
200 link = link.replace('__E',rev_end.substr(0,6));
201 YUD.get('rev_range_container').href = url;
201 YUD.get('rev_range_container').href = url;
202 YUD.get('rev_range_container').innerHTML = link;
202 YUD.get('rev_range_container').innerHTML = link;
203 YUD.setStyle('rev_range_container','display','');
203 YUD.setStyle('rev_range_container','display','');
204 YUD.setStyle('rev_range_clear','display','');
204 YUD.setStyle('rev_range_clear','display','');
205
205
206 }
206 }
207 else{
207 else{
208 YUD.setStyle('rev_range_container','display','none');
208 YUD.setStyle('rev_range_container','display','none');
209 YUD.setStyle('rev_range_clear','display','none');
209 YUD.setStyle('rev_range_clear','display','none');
210 }
210 }
211 });
211 });
212 YUE.on('rev_range_clear','click',function(e){
212 YUE.on('rev_range_clear','click',function(e){
213 for (var i=0; i<checkboxes.length; i++){
213 for (var i=0; i<checkboxes.length; i++){
214 var cb = checkboxes[i];
214 var cb = checkboxes[i];
215 cb.checked = false;
215 cb.checked = false;
216 }
216 }
217 YUE.preventDefault(e);
217 YUE.preventDefault(e);
218 })
218 })
219 var msgs = YUQ('.message');
219 var msgs = YUQ('.message');
220 // get first element height
220 // get first element height
221 var el = YUQ('#graph_content .container')[0];
221 var el = YUQ('#graph_content .container')[0];
222 var row_h = el.clientHeight;
222 var row_h = el.clientHeight;
223 for(var i=0;i<msgs.length;i++){
223 for(var i=0;i<msgs.length;i++){
224 var m = msgs[i];
224 var m = msgs[i];
225
225
226 var h = m.clientHeight;
226 var h = m.clientHeight;
227 var pad = YUD.getStyle(m,'padding');
227 var pad = YUD.getStyle(m,'padding');
228 if(h > row_h){
228 if(h > row_h){
229 var offset = row_h - (h+12);
229 var offset = row_h - (h+12);
230 YUD.setStyle(m.nextElementSibling,'display','block');
230 YUD.setStyle(m.nextElementSibling,'display','block');
231 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
231 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
232 };
232 };
233 }
233 }
234 YUE.on(YUQ('.expand'),'click',function(e){
234 YUE.on(YUQ('.expand'),'click',function(e){
235 var elem = e.currentTarget.parentNode.parentNode;
235 var elem = e.currentTarget.parentNode.parentNode;
236 YUD.setStyle(e.currentTarget,'display','none');
236 YUD.setStyle(e.currentTarget,'display','none');
237 YUD.setStyle(elem,'height','auto');
237 YUD.setStyle(elem,'height','auto');
238
238
239 //redraw the graph, line_count and jsdata are global vars
239 //redraw the graph, line_count and jsdata are global vars
240 set_canvas(100);
240 set_canvas(100);
241
241
242 var r = new BranchRenderer();
242 var r = new BranchRenderer();
243 r.render(jsdata,100,line_count);
243 r.render(jsdata,100,line_count);
244
244
245 })
245 })
246
246
247 // Fetch changeset details
247 // Fetch changeset details
248 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
248 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
249 var id = e.currentTarget.id;
249 var id = e.currentTarget.id;
250 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
250 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
251 var url = url.replace('__CS__',id.replace('changed_total_',''));
251 var url = url.replace('__CS__',id.replace('changed_total_',''));
252 ypjax(url,id,function(){tooltip_activate()});
252 ypjax(url,id,function(){tooltip_activate()});
253 });
253 });
254
254
255 // change branch filter
255 // change branch filter
256 YUE.on(YUD.get('branch_filter'),'change',function(e){
256 YUE.on(YUD.get('branch_filter'),'change',function(e){
257 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
257 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
258 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
258 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
259 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
259 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
260 var url = url.replace('__BRANCH__',selected_branch);
260 var url = url.replace('__BRANCH__',selected_branch);
261 if(selected_branch != ''){
261 if(selected_branch != ''){
262 window.location = url;
262 window.location = url;
263 }else{
263 }else{
264 window.location = url_main;
264 window.location = url_main;
265 }
265 }
266
266
267 });
267 });
268
268
269 function set_canvas(width) {
269 function set_canvas(width) {
270 var c = document.getElementById('graph_nodes');
270 var c = document.getElementById('graph_nodes');
271 var t = document.getElementById('graph_content');
271 var t = document.getElementById('graph_content');
272 canvas = document.getElementById('graph_canvas');
272 canvas = document.getElementById('graph_canvas');
273 var div_h = t.clientHeight;
273 var div_h = t.clientHeight;
274 c.style.height=div_h+'px';
274 c.style.height=div_h+'px';
275 canvas.setAttribute('height',div_h);
275 canvas.setAttribute('height',div_h);
276 c.style.height=width+'px';
276 c.style.height=width+'px';
277 canvas.setAttribute('width',width);
277 canvas.setAttribute('width',width);
278 };
278 };
279 var heads = 1;
279 var heads = 1;
280 var line_count = 0;
280 var line_count = 0;
281 var jsdata = ${c.jsdata|n};
281 var jsdata = ${c.jsdata|n};
282
282
283 for (var i=0;i<jsdata.length;i++) {
283 for (var i=0;i<jsdata.length;i++) {
284 var in_l = jsdata[i][2];
284 var in_l = jsdata[i][2];
285 for (var j in in_l) {
285 for (var j in in_l) {
286 var m = in_l[j][1];
286 var m = in_l[j][1];
287 if (m > line_count)
287 if (m > line_count)
288 line_count = m;
288 line_count = m;
289 }
289 }
290 }
290 }
291 set_canvas(100);
291 set_canvas(100);
292
292
293 var r = new BranchRenderer();
293 var r = new BranchRenderer();
294 r.render(jsdata,100,line_count);
294 r.render(jsdata,100,line_count);
295
295
296 });
296 });
297 </script>
297 </script>
298 %else:
298 %else:
299 ${_('There are no changes yet')}
299 ${_('There are no changes yet')}
300 %endif
300 %endif
301 </div>
301 </div>
302 </div>
302 </div>
303 </%def>
303 </%def>
@@ -1,76 +1,93 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Tags') % c.repo_name} - ${c.rhodecode_name}
5 ${_('%s Tags') % c.repo_name} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 <input class="q_filter_box" id="q_filter_tags" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
10 <input class="q_filter_box" id="q_filter_tags" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
11 ${h.link_to(_(u'Home'),h.url('/'))}
11 ${h.link_to(_(u'Home'),h.url('/'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
14 &raquo;
14 &raquo;
15 ${_('tags')}
15 ${_('tags')}
16 </%def>
16 </%def>
17
17
18 <%def name="page_nav()">
18 <%def name="page_nav()">
19 ${self.menu('tags')}
19 ${self.menu('tags')}
20 </%def>
20 </%def>
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <!-- end box / title -->
27 <!-- end box / title -->
28 %if c.repo_tags:
29 <div class="info_box" id="compare_tags" style="clear: both;padding: 10px 19px;vertical-align: right;text-align: right;"><a href="#" class="ui-btn small">${_('Compare tags')}</a></div>
30 %endif
28 <div class="table">
31 <div class="table">
29 <%include file='tags_data.html'/>
32 <%include file='tags_data.html'/>
30 </div>
33 </div>
31 </div>
34 </div>
32 <script type="text/javascript">
35 <script type="text/javascript">
36 YUE.on('compare_tags','click',function(e){
37 YUE.preventDefault(e);
38 var org = YUQ('input[name=compare_org]:checked')[0];
39 var other = YUQ('input[name=compare_other]:checked')[0];
40
41 if(org && other){
42 var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='tag',org_ref='__ORG__',other_ref_type='tag',other_ref='__OTHER__')}";
43 var u = compare_url.replace('__ORG__',org.value)
44 .replace('__OTHER__',other.value);
45 window.location=u;
46 }
47 });
33
48
34 // main table sorting
49 // main table sorting
35 var myColumnDefs = [
50 var myColumnDefs = [
36 {key:"name",label:"${_('Name')}",sortable:true},
51 {key:"name",label:"${_('Name')}",sortable:true},
37 {key:"date",label:"${_('Date')}",sortable:true,
52 {key:"date",label:"${_('Date')}",sortable:true,
38 sortOptions: { sortFunction: dateSort }},
53 sortOptions: { sortFunction: dateSort }},
39 {key:"author",label:"${_('Author')}",sortable:true},
54 {key:"author",label:"${_('Author')}",sortable:true},
40 {key:"revision",label:"${_('Revision')}",sortable:true,
55 {key:"revision",label:"${_('Revision')}",sortable:true,
41 sortOptions: { sortFunction: revisionSort }},
56 sortOptions: { sortFunction: revisionSort }},
57 {key:"compare",label:"${_('Compare')}",sortable:false,},
42 ];
58 ];
43
59
44 var myDataSource = new YAHOO.util.DataSource(YUD.get("tags_data"));
60 var myDataSource = new YAHOO.util.DataSource(YUD.get("tags_data"));
45
61
46 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
62 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
47
63
48 myDataSource.responseSchema = {
64 myDataSource.responseSchema = {
49 fields: [
65 fields: [
50 {key:"name"},
66 {key:"name"},
51 {key:"date"},
67 {key:"date"},
52 {key:"author"},
68 {key:"author"},
53 {key:"revision"},
69 {key:"revision"},
70 {key:"compare"},
54 ]
71 ]
55 };
72 };
56
73
57 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
74 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
58 {
75 {
59 sortedBy:{key:"name",dir:"asc"},
76 sortedBy:{key:"name",dir:"asc"},
60 MSG_SORTASC:"${_('Click to sort ascending')}",
77 MSG_SORTASC:"${_('Click to sort ascending')}",
61 MSG_SORTDESC:"${_('Click to sort descending')}",
78 MSG_SORTDESC:"${_('Click to sort descending')}",
62 MSG_EMPTY:"${_('No records found.')}",
79 MSG_EMPTY:"${_('No records found.')}",
63 MSG_ERROR:"${_('Data error.')}",
80 MSG_ERROR:"${_('Data error.')}",
64 MSG_LOADING:"${_('Loading...')}",
81 MSG_LOADING:"${_('Loading...')}",
65 }
82 }
66 );
83 );
67 myDataTable.subscribe('postRenderEvent',function(oArgs) {
84 myDataTable.subscribe('postRenderEvent',function(oArgs) {
68 tooltip_activate();
85 tooltip_activate();
69 var func = function(node){
86 var func = function(node){
70 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
87 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
71 }
88 }
72 q_filter('q_filter_tags',YUQ('div.table tr td .logtags .tagtag a'),func);
89 q_filter('q_filter_tags',YUQ('div.table tr td .logtags .tagtag a'),func);
73 });
90 });
74
91
75 </script>
92 </script>
76 </%def>
93 </%def>
@@ -1,34 +1,39 b''
1 %if c.repo_tags:
1 %if c.repo_tags:
2 <div id="table_wrap" class="yui-skin-sam">
2 <div id="table_wrap" class="yui-skin-sam">
3 <table id="tags_data">
3 <table id="tags_data">
4 <thead>
4 <thead>
5 <tr>
5 <tr>
6 <th class="left">${_('Name')}</th>
6 <th class="left">${_('Name')}</th>
7 <th class="left">${_('Date')}</th>
7 <th class="left">${_('Date')}</th>
8 <th class="left">${_('Author')}</th>
8 <th class="left">${_('Author')}</th>
9 <th class="left">${_('Revision')}</th>
9 <th class="left">${_('Revision')}</th>
10 <th class="left">${_('Compare')}</th>
10 </tr>
11 </tr>
11 </thead>
12 </thead>
12 %for cnt,tag in enumerate(c.repo_tags.items()):
13 %for cnt,tag in enumerate(c.repo_tags.items()):
13 <tr class="parity${cnt%2}">
14 <tr class="parity${cnt%2}">
14 <td>
15 <td>
15 <span class="logtags">
16 <span class="logtags">
16 <span class="tagtag">${h.link_to(tag[0],
17 <span class="tagtag">${h.link_to(tag[0],
17 h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
18 h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
18 </span>
19 </span>
19 </span>
20 </span>
20 </td>
21 </td>
21 <td><span class="tooltip" title="${h.tooltip(h.age(tag[1].date))}">${h.fmt_date(tag[1].date)}</span></td>
22 <td><span class="tooltip" title="${h.tooltip(h.age(tag[1].date))}">${h.fmt_date(tag[1].date)}</span></td>
22 <td title="${tag[1].author}">${h.person(tag[1].author)}</td>
23 <td title="${tag[1].author}">${h.person(tag[1].author)}</td>
23 <td>
24 <td>
24 <div>
25 <div>
25 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id)}">r${tag[1].revision}:${h.short_id(tag[1].raw_id)}</a></pre>
26 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id)}">r${tag[1].revision}:${h.short_id(tag[1].raw_id)}</a></pre>
26 </div>
27 </div>
27 </td>
28 </td>
29 <td>
30 <input class="branch-compare" type="radio" name="compare_org" value="${tag[0]}"/>
31 <input class="branch-compare" type="radio" name="compare_other" value="${tag[0]}"/>
32 </td>
28 </tr>
33 </tr>
29 %endfor
34 %endfor
30 </table>
35 </table>
31 </div>
36 </div>
32 %else:
37 %else:
33 ${_('There are no tags yet')}
38 ${_('There are no tags yet')}
34 %endif
39 %endif
General Comments 0
You need to be logged in to leave comments. Login now