##// END OF EJS Templates
tests: fix Git on Windows sometimes failing on ' or ` in file:/// URLs...
domruf -
r5758:078136fd default
parent child Browse files
Show More
@@ -1,298 +1,299 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.compare
15 kallithea.controllers.compare
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 compare controller for pylons showing differences between two
18 compare controller for pylons showing differences between two
19 repos, branches, bookmarks or tips
19 repos, branches, bookmarks or tips
20
20
21 This file was forked by the Kallithea project in July 2014.
21 This file was forked by the Kallithea project in July 2014.
22 Original author and date, and relevant copyright and licensing information is below:
22 Original author and date, and relevant copyright and licensing information is below:
23 :created_on: May 6, 2012
23 :created_on: May 6, 2012
24 :author: marcink
24 :author: marcink
25 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :copyright: (c) 2013 RhodeCode GmbH, and others.
26 :license: GPLv3, see LICENSE.md for more details.
26 :license: GPLv3, see LICENSE.md for more details.
27 """
27 """
28
28
29
29
30 import logging
30 import logging
31 import re
31 import re
32
32
33 from pylons import request, tmpl_context as c, url
33 from pylons import request, tmpl_context as c, url
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from webob.exc import HTTPFound, HTTPBadRequest
35 from webob.exc import HTTPFound, HTTPBadRequest
36
36
37 from kallithea.lib.utils2 import safe_str
37 from kallithea.lib.utils2 import safe_str
38 from kallithea.lib.vcs.utils.hgcompat import unionrepo
38 from kallithea.lib.vcs.utils.hgcompat import unionrepo
39 from kallithea.lib import helpers as h
39 from kallithea.lib import helpers as h
40 from kallithea.lib.base import BaseRepoController, render
40 from kallithea.lib.base import BaseRepoController, render
41 from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from kallithea.lib import diffs
42 from kallithea.lib import diffs
43 from kallithea.model.db import Repository
43 from kallithea.model.db import Repository
44 from kallithea.lib.diffs import LimitedDiffContainer
44 from kallithea.lib.diffs import LimitedDiffContainer
45 from kallithea.controllers.changeset import _ignorews_url, _context_url
45 from kallithea.controllers.changeset import _ignorews_url, _context_url
46 from kallithea.lib.graphmod import graph_data
46 from kallithea.lib.graphmod import graph_data
47 from kallithea.lib.compat import json
47 from kallithea.lib.compat import json
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class CompareController(BaseRepoController):
52 class CompareController(BaseRepoController):
53
53
54 def __before__(self):
54 def __before__(self):
55 super(CompareController, self).__before__()
55 super(CompareController, self).__before__()
56
56
57 @staticmethod
57 @staticmethod
58 def _get_changesets(alias, org_repo, org_rev, other_repo, other_rev):
58 def _get_changesets(alias, org_repo, org_rev, other_repo, other_rev):
59 """
59 """
60 Returns lists of changesets that can be merged from org_repo@org_rev
60 Returns lists of changesets that can be merged from org_repo@org_rev
61 to other_repo@other_rev
61 to other_repo@other_rev
62 ... and the other way
62 ... and the other way
63 ... and the ancestor that would be used for merge
63 ... and the ancestor that would be used for merge
64
64
65 :param org_repo: repo object, that is most likely the original repo we forked from
65 :param org_repo: repo object, that is most likely the original repo we forked from
66 :param org_rev: the revision we want our compare to be made
66 :param org_rev: the revision we want our compare to be made
67 :param other_repo: repo object, most likely the fork of org_repo. It has
67 :param other_repo: repo object, most likely the fork of org_repo. It has
68 all changesets that we need to obtain
68 all changesets that we need to obtain
69 :param other_rev: revision we want out compare to be made on other_repo
69 :param other_rev: revision we want out compare to be made on other_repo
70 """
70 """
71 ancestor = None
71 ancestor = None
72 if org_rev == other_rev:
72 if org_rev == other_rev:
73 org_changesets = []
73 org_changesets = []
74 other_changesets = []
74 other_changesets = []
75 ancestor = org_rev
75 ancestor = org_rev
76
76
77 elif alias == 'hg':
77 elif alias == 'hg':
78 #case two independent repos
78 #case two independent repos
79 if org_repo != other_repo:
79 if org_repo != other_repo:
80 hgrepo = unionrepo.unionrepository(other_repo.baseui,
80 hgrepo = unionrepo.unionrepository(other_repo.baseui,
81 other_repo.path,
81 other_repo.path,
82 org_repo.path)
82 org_repo.path)
83 # all ancestors of other_rev will be in other_repo and
83 # all ancestors of other_rev will be in other_repo and
84 # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot
84 # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot
85
85
86 #no remote compare do it on the same repository
86 #no remote compare do it on the same repository
87 else:
87 else:
88 hgrepo = other_repo._repo
88 hgrepo = other_repo._repo
89
89
90 if org_repo.EMPTY_CHANGESET in (org_rev, other_rev):
90 if org_repo.EMPTY_CHANGESET in (org_rev, other_rev):
91 # work around unexpected behaviour in Mercurial < 3.4
91 # work around unexpected behaviour in Mercurial < 3.4
92 ancestor = org_repo.EMPTY_CHANGESET
92 ancestor = org_repo.EMPTY_CHANGESET
93 else:
93 else:
94 ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
94 ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
95 if ancestors:
95 if ancestors:
96 # FIXME: picks arbitrary ancestor - but there is usually only one
96 # FIXME: picks arbitrary ancestor - but there is usually only one
97 try:
97 try:
98 ancestor = hgrepo[ancestors.first()].hex()
98 ancestor = hgrepo[ancestors.first()].hex()
99 except AttributeError:
99 except AttributeError:
100 # removed in hg 3.2
100 # removed in hg 3.2
101 ancestor = hgrepo[ancestors[0]].hex()
101 ancestor = hgrepo[ancestors[0]].hex()
102
102
103 other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
103 other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
104 other_rev, org_rev, org_rev)
104 other_rev, org_rev, org_rev)
105 other_changesets = [other_repo.get_changeset(rev) for rev in other_revs]
105 other_changesets = [other_repo.get_changeset(rev) for rev in other_revs]
106 org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
106 org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
107 org_rev, other_rev, other_rev)
107 org_rev, other_rev, other_rev)
108
108
109 org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs]
109 org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs]
110
110
111 elif alias == 'git':
111 elif alias == 'git':
112 if org_repo != other_repo:
112 if org_repo != other_repo:
113 from dulwich.repo import Repo
113 from dulwich.repo import Repo
114 from dulwich.client import SubprocessGitClient
114 from dulwich.client import SubprocessGitClient
115
115
116 gitrepo = Repo(org_repo.path)
116 gitrepo = Repo(org_repo.path)
117 SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo)
117 SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo)
118
118
119 gitrepo_remote = Repo(other_repo.path)
119 gitrepo_remote = Repo(other_repo.path)
120 SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote)
120 SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote)
121
121
122 revs = []
122 revs = []
123 for x in gitrepo_remote.get_walker(include=[other_rev],
123 for x in gitrepo_remote.get_walker(include=[other_rev],
124 exclude=[org_rev]):
124 exclude=[org_rev]):
125 revs.append(x.commit.id)
125 revs.append(x.commit.id)
126
126
127 other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
127 other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
128 if other_changesets:
128 if other_changesets:
129 ancestor = other_changesets[0].parents[0].raw_id
129 ancestor = other_changesets[0].parents[0].raw_id
130 else:
130 else:
131 # no changesets from other repo, ancestor is the other_rev
131 # no changesets from other repo, ancestor is the other_rev
132 ancestor = other_rev
132 ancestor = other_rev
133
133 # dulwich 0.9.9 doesn't have a Repo.close() so we have to mess with internals:
134 # dulwich 0.9.9 doesn't have a Repo.close() so we have to mess with internals:
134 gitrepo.object_store.close()
135 gitrepo.object_store.close()
135 gitrepo_remote.object_store.close()
136 gitrepo_remote.object_store.close()
136
137
137 else:
138 else:
138 so, se = org_repo.run_git_command(
139 so, se = org_repo.run_git_command(
139 ['log', '--reverse', '--pretty=format:%H',
140 ['log', '--reverse', '--pretty=format:%H',
140 '-s', '%s..%s' % (org_rev, other_rev)]
141 '-s', '%s..%s' % (org_rev, other_rev)]
141 )
142 )
142 other_changesets = [org_repo.get_changeset(cs)
143 other_changesets = [org_repo.get_changeset(cs)
143 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
144 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
144 so, se = org_repo.run_git_command(
145 so, se = org_repo.run_git_command(
145 ['merge-base', org_rev, other_rev]
146 ['merge-base', org_rev, other_rev]
146 )
147 )
147 ancestor = re.findall(r'[0-9a-fA-F]{40}', so)[0]
148 ancestor = re.findall(r'[0-9a-fA-F]{40}', so)[0]
148 org_changesets = []
149 org_changesets = []
149
150
150 else:
151 else:
151 raise Exception('Bad alias only git and hg is allowed')
152 raise Exception('Bad alias only git and hg is allowed')
152
153
153 return other_changesets, org_changesets, ancestor
154 return other_changesets, org_changesets, ancestor
154
155
155 @LoginRequired()
156 @LoginRequired()
156 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
157 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
157 'repository.admin')
158 'repository.admin')
158 def index(self, repo_name):
159 def index(self, repo_name):
159 c.compare_home = True
160 c.compare_home = True
160 org_repo = c.db_repo.repo_name
161 org_repo = c.db_repo.repo_name
161 other_repo = request.GET.get('other_repo', org_repo)
162 other_repo = request.GET.get('other_repo', org_repo)
162 c.a_repo = Repository.get_by_repo_name(org_repo)
163 c.a_repo = Repository.get_by_repo_name(org_repo)
163 c.cs_repo = Repository.get_by_repo_name(other_repo)
164 c.cs_repo = Repository.get_by_repo_name(other_repo)
164 c.a_ref_name = c.cs_ref_name = _('Select changeset')
165 c.a_ref_name = c.cs_ref_name = _('Select changeset')
165 return render('compare/compare_diff.html')
166 return render('compare/compare_diff.html')
166
167
167 @LoginRequired()
168 @LoginRequired()
168 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
169 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
169 'repository.admin')
170 'repository.admin')
170 def compare(self, repo_name, org_ref_type, org_ref_name, other_ref_type, other_ref_name):
171 def compare(self, repo_name, org_ref_type, org_ref_name, other_ref_type, other_ref_name):
171 org_ref_name = org_ref_name.strip()
172 org_ref_name = org_ref_name.strip()
172 other_ref_name = other_ref_name.strip()
173 other_ref_name = other_ref_name.strip()
173
174
174 org_repo = c.db_repo.repo_name
175 org_repo = c.db_repo.repo_name
175 other_repo = request.GET.get('other_repo', org_repo)
176 other_repo = request.GET.get('other_repo', org_repo)
176 # If merge is True:
177 # If merge is True:
177 # Show what org would get if merged with other:
178 # Show what org would get if merged with other:
178 # List changesets that are ancestors of other but not of org.
179 # List changesets that are ancestors of other but not of org.
179 # New changesets in org is thus ignored.
180 # New changesets in org is thus ignored.
180 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
181 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
181 # If merge is False:
182 # If merge is False:
182 # Make a raw diff from org to other, no matter if related or not.
183 # Make a raw diff from org to other, no matter if related or not.
183 # Changesets in one and not in the other will be ignored
184 # Changesets in one and not in the other will be ignored
184 merge = bool(request.GET.get('merge'))
185 merge = bool(request.GET.get('merge'))
185 # fulldiff disables cut_off_limit
186 # fulldiff disables cut_off_limit
186 c.fulldiff = request.GET.get('fulldiff')
187 c.fulldiff = request.GET.get('fulldiff')
187 # partial uses compare_cs.html template directly
188 # partial uses compare_cs.html template directly
188 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
189 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
189 # as_form puts hidden input field with changeset revisions
190 # as_form puts hidden input field with changeset revisions
190 c.as_form = partial and request.GET.get('as_form')
191 c.as_form = partial and request.GET.get('as_form')
191 # swap url for compare_diff page - never partial and never as_form
192 # swap url for compare_diff page - never partial and never as_form
192 c.swap_url = h.url('compare_url',
193 c.swap_url = h.url('compare_url',
193 repo_name=other_repo,
194 repo_name=other_repo,
194 org_ref_type=other_ref_type, org_ref_name=other_ref_name,
195 org_ref_type=other_ref_type, org_ref_name=other_ref_name,
195 other_repo=org_repo,
196 other_repo=org_repo,
196 other_ref_type=org_ref_type, other_ref_name=org_ref_name,
197 other_ref_type=org_ref_type, other_ref_name=org_ref_name,
197 merge=merge or '')
198 merge=merge or '')
198
199
199 # set callbacks for generating markup for icons
200 # set callbacks for generating markup for icons
200 c.ignorews_url = _ignorews_url
201 c.ignorews_url = _ignorews_url
201 c.context_url = _context_url
202 c.context_url = _context_url
202 ignore_whitespace = request.GET.get('ignorews') == '1'
203 ignore_whitespace = request.GET.get('ignorews') == '1'
203 line_context = request.GET.get('context', 3)
204 line_context = request.GET.get('context', 3)
204
205
205 org_repo = Repository.get_by_repo_name(org_repo)
206 org_repo = Repository.get_by_repo_name(org_repo)
206 other_repo = Repository.get_by_repo_name(other_repo)
207 other_repo = Repository.get_by_repo_name(other_repo)
207
208
208 if org_repo is None:
209 if org_repo is None:
209 msg = 'Could not find org repo %s' % org_repo
210 msg = 'Could not find org repo %s' % org_repo
210 log.error(msg)
211 log.error(msg)
211 h.flash(msg, category='error')
212 h.flash(msg, category='error')
212 raise HTTPFound(location=url('compare_home', repo_name=c.repo_name))
213 raise HTTPFound(location=url('compare_home', repo_name=c.repo_name))
213
214
214 if other_repo is None:
215 if other_repo is None:
215 msg = 'Could not find other repo %s' % other_repo
216 msg = 'Could not find other repo %s' % other_repo
216 log.error(msg)
217 log.error(msg)
217 h.flash(msg, category='error')
218 h.flash(msg, category='error')
218 raise HTTPFound(location=url('compare_home', repo_name=c.repo_name))
219 raise HTTPFound(location=url('compare_home', repo_name=c.repo_name))
219
220
220 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
221 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
221 msg = 'compare of two different kind of remote repos not available'
222 msg = 'compare of two different kind of remote repos not available'
222 log.error(msg)
223 log.error(msg)
223 h.flash(msg, category='error')
224 h.flash(msg, category='error')
224 raise HTTPFound(location=url('compare_home', repo_name=c.repo_name))
225 raise HTTPFound(location=url('compare_home', repo_name=c.repo_name))
225
226
226 c.a_rev = self._get_ref_rev(org_repo, org_ref_type, org_ref_name,
227 c.a_rev = self._get_ref_rev(org_repo, org_ref_type, org_ref_name,
227 returnempty=True)
228 returnempty=True)
228 c.cs_rev = self._get_ref_rev(other_repo, other_ref_type, other_ref_name)
229 c.cs_rev = self._get_ref_rev(other_repo, other_ref_type, other_ref_name)
229
230
230 c.compare_home = False
231 c.compare_home = False
231 c.a_repo = org_repo
232 c.a_repo = org_repo
232 c.a_ref_name = org_ref_name
233 c.a_ref_name = org_ref_name
233 c.a_ref_type = org_ref_type
234 c.a_ref_type = org_ref_type
234 c.cs_repo = other_repo
235 c.cs_repo = other_repo
235 c.cs_ref_name = other_ref_name
236 c.cs_ref_name = other_ref_name
236 c.cs_ref_type = other_ref_type
237 c.cs_ref_type = other_ref_type
237
238
238 c.cs_ranges, c.cs_ranges_org, c.ancestor = self._get_changesets(
239 c.cs_ranges, c.cs_ranges_org, c.ancestor = self._get_changesets(
239 org_repo.scm_instance.alias, org_repo.scm_instance, c.a_rev,
240 org_repo.scm_instance.alias, org_repo.scm_instance, c.a_rev,
240 other_repo.scm_instance, c.cs_rev)
241 other_repo.scm_instance, c.cs_rev)
241 raw_ids = [x.raw_id for x in c.cs_ranges]
242 raw_ids = [x.raw_id for x in c.cs_ranges]
242 c.cs_comments = other_repo.get_comments(raw_ids)
243 c.cs_comments = other_repo.get_comments(raw_ids)
243 c.statuses = other_repo.statuses(raw_ids)
244 c.statuses = other_repo.statuses(raw_ids)
244
245
245 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
246 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
246 c.jsdata = json.dumps(graph_data(c.cs_repo.scm_instance, revs))
247 c.jsdata = json.dumps(graph_data(c.cs_repo.scm_instance, revs))
247
248
248 if partial:
249 if partial:
249 return render('compare/compare_cs.html')
250 return render('compare/compare_cs.html')
250 if merge and c.ancestor:
251 if merge and c.ancestor:
251 # case we want a simple diff without incoming changesets,
252 # case we want a simple diff without incoming changesets,
252 # previewing what will be merged.
253 # previewing what will be merged.
253 # Make the diff on the other repo (which is known to have other_rev)
254 # Make the diff on the other repo (which is known to have other_rev)
254 log.debug('Using ancestor %s as rev1 instead of %s',
255 log.debug('Using ancestor %s as rev1 instead of %s',
255 c.ancestor, c.a_rev)
256 c.ancestor, c.a_rev)
256 rev1 = c.ancestor
257 rev1 = c.ancestor
257 org_repo = other_repo
258 org_repo = other_repo
258 else: # comparing tips, not necessarily linearly related
259 else: # comparing tips, not necessarily linearly related
259 if merge:
260 if merge:
260 log.error('Unable to find ancestor revision')
261 log.error('Unable to find ancestor revision')
261 if org_repo != other_repo:
262 if org_repo != other_repo:
262 # TODO: we could do this by using hg unionrepo
263 # TODO: we could do this by using hg unionrepo
263 log.error('cannot compare across repos %s and %s', org_repo, other_repo)
264 log.error('cannot compare across repos %s and %s', org_repo, other_repo)
264 h.flash(_('Cannot compare repositories without using common ancestor'), category='error')
265 h.flash(_('Cannot compare repositories without using common ancestor'), category='error')
265 raise HTTPBadRequest
266 raise HTTPBadRequest
266 rev1 = c.a_rev
267 rev1 = c.a_rev
267
268
268 diff_limit = self.cut_off_limit if not c.fulldiff else None
269 diff_limit = self.cut_off_limit if not c.fulldiff else None
269
270
270 log.debug('running diff between %s and %s in %s',
271 log.debug('running diff between %s and %s in %s',
271 rev1, c.cs_rev, org_repo.scm_instance.path)
272 rev1, c.cs_rev, org_repo.scm_instance.path)
272 txtdiff = org_repo.scm_instance.get_diff(rev1=rev1, rev2=c.cs_rev,
273 txtdiff = org_repo.scm_instance.get_diff(rev1=rev1, rev2=c.cs_rev,
273 ignore_whitespace=ignore_whitespace,
274 ignore_whitespace=ignore_whitespace,
274 context=line_context)
275 context=line_context)
275
276
276 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
277 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
277 diff_limit=diff_limit)
278 diff_limit=diff_limit)
278 _parsed = diff_processor.prepare()
279 _parsed = diff_processor.prepare()
279
280
280 c.limited_diff = False
281 c.limited_diff = False
281 if isinstance(_parsed, LimitedDiffContainer):
282 if isinstance(_parsed, LimitedDiffContainer):
282 c.limited_diff = True
283 c.limited_diff = True
283
284
284 c.files = []
285 c.files = []
285 c.changes = {}
286 c.changes = {}
286 c.lines_added = 0
287 c.lines_added = 0
287 c.lines_deleted = 0
288 c.lines_deleted = 0
288 for f in _parsed:
289 for f in _parsed:
289 st = f['stats']
290 st = f['stats']
290 if not st['binary']:
291 if not st['binary']:
291 c.lines_added += st['added']
292 c.lines_added += st['added']
292 c.lines_deleted += st['deleted']
293 c.lines_deleted += st['deleted']
293 fid = h.FID('', f['filename'])
294 fid = h.FID('', f['filename'])
294 c.files.append([fid, f['operation'], f['filename'], f['stats']])
295 c.files.append([fid, f['operation'], f['filename'], f['stats']])
295 htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
296 htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
296 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
297 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
297
298
298 return render('compare/compare_diff.html')
299 return render('compare/compare_diff.html')
@@ -1,838 +1,840 b''
1
1
2 import os
2 import os
3 import sys
3 import sys
4 import mock
4 import mock
5 import datetime
5 import datetime
6 import urllib2
6 import urllib2
7 import tempfile
7 import tempfile
8
8
9 import pytest
9 import pytest
10
10
11 from kallithea.lib.vcs.backends.git import GitRepository, GitChangeset
11 from kallithea.lib.vcs.backends.git import GitRepository, GitChangeset
12 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
12 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
13 from kallithea.lib.vcs.nodes import NodeKind, FileNode, DirNode, NodeState
13 from kallithea.lib.vcs.nodes import NodeKind, FileNode, DirNode, NodeState
14 from kallithea.lib.vcs.utils.compat import unittest
14 from kallithea.lib.vcs.utils.compat import unittest
15 from kallithea.model.scm import ScmModel
15 from kallithea.model.scm import ScmModel
16 from kallithea.tests.vcs.base import _BackendTestMixin
16 from kallithea.tests.vcs.base import _BackendTestMixin
17 from kallithea.tests.vcs.conf import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
17 from kallithea.tests.vcs.conf import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
18
18
19
19
20 class GitRepositoryTest(unittest.TestCase):
20 class GitRepositoryTest(unittest.TestCase):
21
21
22 def __check_for_existing_repo(self):
22 def __check_for_existing_repo(self):
23 if os.path.exists(TEST_GIT_REPO_CLONE):
23 if os.path.exists(TEST_GIT_REPO_CLONE):
24 pytest.fail('Cannot test git clone repo as location %s already '
24 pytest.fail('Cannot test git clone repo as location %s already '
25 'exists. You should manually remove it first.'
25 'exists. You should manually remove it first.'
26 % TEST_GIT_REPO_CLONE)
26 % TEST_GIT_REPO_CLONE)
27
27
28 def setUp(self):
28 def setUp(self):
29 self.repo = GitRepository(TEST_GIT_REPO)
29 self.repo = GitRepository(TEST_GIT_REPO)
30
30
31 def test_wrong_repo_path(self):
31 def test_wrong_repo_path(self):
32 wrong_repo_path = os.path.join(tempfile.gettempdir(), 'errorrepo')
32 wrong_repo_path = os.path.join(tempfile.gettempdir(), 'errorrepo')
33 self.assertRaises(RepositoryError, GitRepository, wrong_repo_path)
33 self.assertRaises(RepositoryError, GitRepository, wrong_repo_path)
34
34
35 def test_git_cmd_injection(self):
35 def test_git_cmd_injection(self):
36 repo_inject_path = TEST_GIT_REPO + '; echo "Cake";'
36 repo_inject_path = TEST_GIT_REPO + '; echo "Cake";'
37 with self.assertRaises(urllib2.URLError):
37 with self.assertRaises(urllib2.URLError):
38 # Should fail because URL will contain the parts after ; too
38 # Should fail because URL will contain the parts after ; too
39 urlerror_fail_repo = GitRepository(get_new_dir('injection-repo'), src_url=repo_inject_path, update_after_clone=True, create=True)
39 urlerror_fail_repo = GitRepository(get_new_dir('injection-repo'), src_url=repo_inject_path, update_after_clone=True, create=True)
40
40
41 with self.assertRaises(RepositoryError):
41 with self.assertRaises(RepositoryError):
42 # Should fail on direct clone call, which as of this writing does not happen outside of class
42 # Should fail on direct clone call, which as of this writing does not happen outside of class
43 clone_fail_repo = GitRepository(get_new_dir('injection-repo'), create=True)
43 clone_fail_repo = GitRepository(get_new_dir('injection-repo'), create=True)
44 clone_fail_repo.clone(repo_inject_path, update_after_clone=True,)
44 clone_fail_repo.clone(repo_inject_path, update_after_clone=True,)
45
45
46 # Verify correct quoting of evil characters that should work on posix file systems
46 # Verify correct quoting of evil characters that should work on posix file systems
47 if sys.platform == 'win32':
47 if sys.platform == 'win32':
48 # windows does not allow '"' in dir names
48 # windows does not allow '"' in dir names
49 tricky_path = get_new_dir("tricky-path-repo-$'`")
49 # and some versions of the git client don't like ` and '
50 tricky_path = get_new_dir("tricky-path-repo-$")
50 else:
51 else:
51 tricky_path = get_new_dir("tricky-path-repo-$'\"`")
52 tricky_path = get_new_dir("tricky-path-repo-$'\"`")
52 successfully_cloned = GitRepository(tricky_path, src_url=TEST_GIT_REPO, update_after_clone=True, create=True)
53 successfully_cloned = GitRepository(tricky_path, src_url=TEST_GIT_REPO, update_after_clone=True, create=True)
53 # Repo should have been created
54 # Repo should have been created
54 self.assertFalse(successfully_cloned._repo.bare)
55 self.assertFalse(successfully_cloned._repo.bare)
55
56
56 if sys.platform == 'win32':
57 if sys.platform == 'win32':
57 # windows does not allow '"' in dir names
58 # windows does not allow '"' in dir names
58 tricky_path_2 = get_new_dir("tricky-path-2-repo-$'`")
59 # and some versions of the git client don't like ` and '
60 tricky_path_2 = get_new_dir("tricky-path-2-repo-$")
59 else:
61 else:
60 tricky_path_2 = get_new_dir("tricky-path-2-repo-$'\"`")
62 tricky_path_2 = get_new_dir("tricky-path-2-repo-$'\"`")
61 successfully_cloned2 = GitRepository(tricky_path_2, src_url=tricky_path, bare=True, create=True)
63 successfully_cloned2 = GitRepository(tricky_path_2, src_url=tricky_path, bare=True, create=True)
62 # Repo should have been created and thus used correct quoting for clone
64 # Repo should have been created and thus used correct quoting for clone
63 self.assertTrue(successfully_cloned2._repo.bare)
65 self.assertTrue(successfully_cloned2._repo.bare)
64
66
65 # Should pass because URL has been properly quoted
67 # Should pass because URL has been properly quoted
66 successfully_cloned.pull(tricky_path_2)
68 successfully_cloned.pull(tricky_path_2)
67 successfully_cloned2.fetch(tricky_path)
69 successfully_cloned2.fetch(tricky_path)
68
70
69 def test_repo_create_with_spaces_in_path(self):
71 def test_repo_create_with_spaces_in_path(self):
70 repo_path = get_new_dir("path with spaces")
72 repo_path = get_new_dir("path with spaces")
71 repo = GitRepository(repo_path, src_url=None, bare=True, create=True)
73 repo = GitRepository(repo_path, src_url=None, bare=True, create=True)
72 # Repo should have been created
74 # Repo should have been created
73 self.assertTrue(repo._repo.bare)
75 self.assertTrue(repo._repo.bare)
74
76
75 def test_repo_clone(self):
77 def test_repo_clone(self):
76 self.__check_for_existing_repo()
78 self.__check_for_existing_repo()
77 repo = GitRepository(TEST_GIT_REPO)
79 repo = GitRepository(TEST_GIT_REPO)
78 repo_clone = GitRepository(TEST_GIT_REPO_CLONE,
80 repo_clone = GitRepository(TEST_GIT_REPO_CLONE,
79 src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
81 src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
80 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
82 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
81 # Checking hashes of changesets should be enough
83 # Checking hashes of changesets should be enough
82 for changeset in repo.get_changesets():
84 for changeset in repo.get_changesets():
83 raw_id = changeset.raw_id
85 raw_id = changeset.raw_id
84 self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id)
86 self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id)
85
87
86 def test_repo_clone_with_spaces_in_path(self):
88 def test_repo_clone_with_spaces_in_path(self):
87 repo_path = get_new_dir("path with spaces")
89 repo_path = get_new_dir("path with spaces")
88 successfully_cloned = GitRepository(repo_path, src_url=TEST_GIT_REPO, update_after_clone=True, create=True)
90 successfully_cloned = GitRepository(repo_path, src_url=TEST_GIT_REPO, update_after_clone=True, create=True)
89 # Repo should have been created
91 # Repo should have been created
90 self.assertFalse(successfully_cloned._repo.bare)
92 self.assertFalse(successfully_cloned._repo.bare)
91
93
92 successfully_cloned.pull(TEST_GIT_REPO)
94 successfully_cloned.pull(TEST_GIT_REPO)
93 self.repo.fetch(repo_path)
95 self.repo.fetch(repo_path)
94
96
95 def test_repo_clone_without_create(self):
97 def test_repo_clone_without_create(self):
96 self.assertRaises(RepositoryError, GitRepository,
98 self.assertRaises(RepositoryError, GitRepository,
97 TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
99 TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
98
100
99 def test_repo_clone_with_update(self):
101 def test_repo_clone_with_update(self):
100 repo = GitRepository(TEST_GIT_REPO)
102 repo = GitRepository(TEST_GIT_REPO)
101 clone_path = TEST_GIT_REPO_CLONE + '_with_update'
103 clone_path = TEST_GIT_REPO_CLONE + '_with_update'
102 repo_clone = GitRepository(clone_path,
104 repo_clone = GitRepository(clone_path,
103 create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
105 create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
104 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
106 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
105
107
106 #check if current workdir was updated
108 #check if current workdir was updated
107 fpath = os.path.join(clone_path, 'MANIFEST.in')
109 fpath = os.path.join(clone_path, 'MANIFEST.in')
108 self.assertEqual(True, os.path.isfile(fpath),
110 self.assertEqual(True, os.path.isfile(fpath),
109 'Repo was cloned and updated but file %s could not be found'
111 'Repo was cloned and updated but file %s could not be found'
110 % fpath)
112 % fpath)
111
113
112 def test_repo_clone_without_update(self):
114 def test_repo_clone_without_update(self):
113 repo = GitRepository(TEST_GIT_REPO)
115 repo = GitRepository(TEST_GIT_REPO)
114 clone_path = TEST_GIT_REPO_CLONE + '_without_update'
116 clone_path = TEST_GIT_REPO_CLONE + '_without_update'
115 repo_clone = GitRepository(clone_path,
117 repo_clone = GitRepository(clone_path,
116 create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
118 create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
117 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
119 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
118 #check if current workdir was *NOT* updated
120 #check if current workdir was *NOT* updated
119 fpath = os.path.join(clone_path, 'MANIFEST.in')
121 fpath = os.path.join(clone_path, 'MANIFEST.in')
120 # Make sure it's not bare repo
122 # Make sure it's not bare repo
121 self.assertFalse(repo_clone._repo.bare)
123 self.assertFalse(repo_clone._repo.bare)
122 self.assertEqual(False, os.path.isfile(fpath),
124 self.assertEqual(False, os.path.isfile(fpath),
123 'Repo was cloned and updated but file %s was found'
125 'Repo was cloned and updated but file %s was found'
124 % fpath)
126 % fpath)
125
127
126 def test_repo_clone_into_bare_repo(self):
128 def test_repo_clone_into_bare_repo(self):
127 repo = GitRepository(TEST_GIT_REPO)
129 repo = GitRepository(TEST_GIT_REPO)
128 clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
130 clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
129 repo_clone = GitRepository(clone_path, create=True,
131 repo_clone = GitRepository(clone_path, create=True,
130 src_url=repo.path, bare=True)
132 src_url=repo.path, bare=True)
131 self.assertTrue(repo_clone._repo.bare)
133 self.assertTrue(repo_clone._repo.bare)
132
134
133 def test_create_repo_is_not_bare_by_default(self):
135 def test_create_repo_is_not_bare_by_default(self):
134 repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
136 repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
135 self.assertFalse(repo._repo.bare)
137 self.assertFalse(repo._repo.bare)
136
138
137 def test_create_bare_repo(self):
139 def test_create_bare_repo(self):
138 repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
140 repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
139 self.assertTrue(repo._repo.bare)
141 self.assertTrue(repo._repo.bare)
140
142
141 def test_revisions(self):
143 def test_revisions(self):
142 # there are 112 revisions (by now)
144 # there are 112 revisions (by now)
143 # so we can assume they would be available from now on
145 # so we can assume they would be available from now on
144 subset = set([
146 subset = set([
145 'c1214f7e79e02fc37156ff215cd71275450cffc3',
147 'c1214f7e79e02fc37156ff215cd71275450cffc3',
146 '38b5fe81f109cb111f549bfe9bb6b267e10bc557',
148 '38b5fe81f109cb111f549bfe9bb6b267e10bc557',
147 'fa6600f6848800641328adbf7811fd2372c02ab2',
149 'fa6600f6848800641328adbf7811fd2372c02ab2',
148 '102607b09cdd60e2793929c4f90478be29f85a17',
150 '102607b09cdd60e2793929c4f90478be29f85a17',
149 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
151 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
150 '2d1028c054665b962fa3d307adfc923ddd528038',
152 '2d1028c054665b962fa3d307adfc923ddd528038',
151 'd7e0d30fbcae12c90680eb095a4f5f02505ce501',
153 'd7e0d30fbcae12c90680eb095a4f5f02505ce501',
152 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
154 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
153 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
155 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
154 '8430a588b43b5d6da365400117c89400326e7992',
156 '8430a588b43b5d6da365400117c89400326e7992',
155 'd955cd312c17b02143c04fa1099a352b04368118',
157 'd955cd312c17b02143c04fa1099a352b04368118',
156 'f67b87e5c629c2ee0ba58f85197e423ff28d735b',
158 'f67b87e5c629c2ee0ba58f85197e423ff28d735b',
157 'add63e382e4aabc9e1afdc4bdc24506c269b7618',
159 'add63e382e4aabc9e1afdc4bdc24506c269b7618',
158 'f298fe1189f1b69779a4423f40b48edf92a703fc',
160 'f298fe1189f1b69779a4423f40b48edf92a703fc',
159 'bd9b619eb41994cac43d67cf4ccc8399c1125808',
161 'bd9b619eb41994cac43d67cf4ccc8399c1125808',
160 '6e125e7c890379446e98980d8ed60fba87d0f6d1',
162 '6e125e7c890379446e98980d8ed60fba87d0f6d1',
161 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd',
163 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd',
162 '0b05e4ed56c802098dfc813cbe779b2f49e92500',
164 '0b05e4ed56c802098dfc813cbe779b2f49e92500',
163 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
165 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
164 '45223f8f114c64bf4d6f853e3c35a369a6305520',
166 '45223f8f114c64bf4d6f853e3c35a369a6305520',
165 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
167 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
166 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68',
168 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68',
167 '27d48942240f5b91dfda77accd2caac94708cc7d',
169 '27d48942240f5b91dfda77accd2caac94708cc7d',
168 '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
170 '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
169 'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
171 'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
170 self.assertTrue(subset.issubset(set(self.repo.revisions)))
172 self.assertTrue(subset.issubset(set(self.repo.revisions)))
171
173
172
174
173
175
174 def test_slicing(self):
176 def test_slicing(self):
175 #4 1 5 10 95
177 #4 1 5 10 95
176 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
178 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
177 (10, 20, 10), (5, 100, 95)]:
179 (10, 20, 10), (5, 100, 95)]:
178 revs = list(self.repo[sfrom:sto])
180 revs = list(self.repo[sfrom:sto])
179 self.assertEqual(len(revs), size)
181 self.assertEqual(len(revs), size)
180 self.assertEqual(revs[0], self.repo.get_changeset(sfrom))
182 self.assertEqual(revs[0], self.repo.get_changeset(sfrom))
181 self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1))
183 self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1))
182
184
183
185
184 def test_branches(self):
186 def test_branches(self):
185 # TODO: Need more tests here
187 # TODO: Need more tests here
186 # Removed (those are 'remotes' branches for cloned repo)
188 # Removed (those are 'remotes' branches for cloned repo)
187 #self.assertTrue('master' in self.repo.branches)
189 #self.assertTrue('master' in self.repo.branches)
188 #self.assertTrue('gittree' in self.repo.branches)
190 #self.assertTrue('gittree' in self.repo.branches)
189 #self.assertTrue('web-branch' in self.repo.branches)
191 #self.assertTrue('web-branch' in self.repo.branches)
190 for name, id in self.repo.branches.items():
192 for name, id in self.repo.branches.items():
191 self.assertTrue(isinstance(
193 self.assertTrue(isinstance(
192 self.repo.get_changeset(id), GitChangeset))
194 self.repo.get_changeset(id), GitChangeset))
193
195
194 def test_tags(self):
196 def test_tags(self):
195 # TODO: Need more tests here
197 # TODO: Need more tests here
196 self.assertTrue('v0.1.1' in self.repo.tags)
198 self.assertTrue('v0.1.1' in self.repo.tags)
197 self.assertTrue('v0.1.2' in self.repo.tags)
199 self.assertTrue('v0.1.2' in self.repo.tags)
198 for name, id in self.repo.tags.items():
200 for name, id in self.repo.tags.items():
199 self.assertTrue(isinstance(
201 self.assertTrue(isinstance(
200 self.repo.get_changeset(id), GitChangeset))
202 self.repo.get_changeset(id), GitChangeset))
201
203
202 def _test_single_changeset_cache(self, revision):
204 def _test_single_changeset_cache(self, revision):
203 chset = self.repo.get_changeset(revision)
205 chset = self.repo.get_changeset(revision)
204 self.assertTrue(revision in self.repo.changesets)
206 self.assertTrue(revision in self.repo.changesets)
205 self.assertTrue(chset is self.repo.changesets[revision])
207 self.assertTrue(chset is self.repo.changesets[revision])
206
208
207 def test_initial_changeset(self):
209 def test_initial_changeset(self):
208 id = self.repo.revisions[0]
210 id = self.repo.revisions[0]
209 init_chset = self.repo.get_changeset(id)
211 init_chset = self.repo.get_changeset(id)
210 self.assertEqual(init_chset.message, 'initial import\n')
212 self.assertEqual(init_chset.message, 'initial import\n')
211 self.assertEqual(init_chset.author,
213 self.assertEqual(init_chset.author,
212 'Marcin Kuzminski <marcin@python-blog.com>')
214 'Marcin Kuzminski <marcin@python-blog.com>')
213 for path in ('vcs/__init__.py',
215 for path in ('vcs/__init__.py',
214 'vcs/backends/BaseRepository.py',
216 'vcs/backends/BaseRepository.py',
215 'vcs/backends/__init__.py'):
217 'vcs/backends/__init__.py'):
216 self.assertTrue(isinstance(init_chset.get_node(path), FileNode))
218 self.assertTrue(isinstance(init_chset.get_node(path), FileNode))
217 for path in ('', 'vcs', 'vcs/backends'):
219 for path in ('', 'vcs', 'vcs/backends'):
218 self.assertTrue(isinstance(init_chset.get_node(path), DirNode))
220 self.assertTrue(isinstance(init_chset.get_node(path), DirNode))
219
221
220 self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar')
222 self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar')
221
223
222 node = init_chset.get_node('vcs/')
224 node = init_chset.get_node('vcs/')
223 self.assertTrue(hasattr(node, 'kind'))
225 self.assertTrue(hasattr(node, 'kind'))
224 self.assertEqual(node.kind, NodeKind.DIR)
226 self.assertEqual(node.kind, NodeKind.DIR)
225
227
226 node = init_chset.get_node('vcs')
228 node = init_chset.get_node('vcs')
227 self.assertTrue(hasattr(node, 'kind'))
229 self.assertTrue(hasattr(node, 'kind'))
228 self.assertEqual(node.kind, NodeKind.DIR)
230 self.assertEqual(node.kind, NodeKind.DIR)
229
231
230 node = init_chset.get_node('vcs/__init__.py')
232 node = init_chset.get_node('vcs/__init__.py')
231 self.assertTrue(hasattr(node, 'kind'))
233 self.assertTrue(hasattr(node, 'kind'))
232 self.assertEqual(node.kind, NodeKind.FILE)
234 self.assertEqual(node.kind, NodeKind.FILE)
233
235
234 def test_not_existing_changeset(self):
236 def test_not_existing_changeset(self):
235 self.assertRaises(RepositoryError, self.repo.get_changeset,
237 self.assertRaises(RepositoryError, self.repo.get_changeset,
236 'f' * 40)
238 'f' * 40)
237
239
238 def test_changeset10(self):
240 def test_changeset10(self):
239
241
240 chset10 = self.repo.get_changeset(self.repo.revisions[9])
242 chset10 = self.repo.get_changeset(self.repo.revisions[9])
241 README = """===
243 README = """===
242 VCS
244 VCS
243 ===
245 ===
244
246
245 Various Version Control System management abstraction layer for Python.
247 Various Version Control System management abstraction layer for Python.
246
248
247 Introduction
249 Introduction
248 ------------
250 ------------
249
251
250 TODO: To be written...
252 TODO: To be written...
251
253
252 """
254 """
253 node = chset10.get_node('README.rst')
255 node = chset10.get_node('README.rst')
254 self.assertEqual(node.kind, NodeKind.FILE)
256 self.assertEqual(node.kind, NodeKind.FILE)
255 self.assertEqual(node.content, README)
257 self.assertEqual(node.content, README)
256
258
257
259
258 class GitChangesetTest(unittest.TestCase):
260 class GitChangesetTest(unittest.TestCase):
259
261
260 def setUp(self):
262 def setUp(self):
261 self.repo = GitRepository(TEST_GIT_REPO)
263 self.repo = GitRepository(TEST_GIT_REPO)
262
264
263 def test_default_changeset(self):
265 def test_default_changeset(self):
264 tip = self.repo.get_changeset()
266 tip = self.repo.get_changeset()
265 self.assertEqual(tip, self.repo.get_changeset(None))
267 self.assertEqual(tip, self.repo.get_changeset(None))
266 self.assertEqual(tip, self.repo.get_changeset('tip'))
268 self.assertEqual(tip, self.repo.get_changeset('tip'))
267
269
268 def test_root_node(self):
270 def test_root_node(self):
269 tip = self.repo.get_changeset()
271 tip = self.repo.get_changeset()
270 self.assertTrue(tip.root is tip.get_node(''))
272 self.assertTrue(tip.root is tip.get_node(''))
271
273
272 def test_lazy_fetch(self):
274 def test_lazy_fetch(self):
273 """
275 """
274 Test if changeset's nodes expands and are cached as we walk through
276 Test if changeset's nodes expands and are cached as we walk through
275 the revision. This test is somewhat hard to write as order of tests
277 the revision. This test is somewhat hard to write as order of tests
276 is a key here. Written by running command after command in a shell.
278 is a key here. Written by running command after command in a shell.
277 """
279 """
278 hex = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
280 hex = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
279 self.assertTrue(hex in self.repo.revisions)
281 self.assertTrue(hex in self.repo.revisions)
280 chset = self.repo.get_changeset(hex)
282 chset = self.repo.get_changeset(hex)
281 self.assertTrue(len(chset.nodes) == 0)
283 self.assertTrue(len(chset.nodes) == 0)
282 root = chset.root
284 root = chset.root
283 self.assertTrue(len(chset.nodes) == 1)
285 self.assertTrue(len(chset.nodes) == 1)
284 self.assertTrue(len(root.nodes) == 8)
286 self.assertTrue(len(root.nodes) == 8)
285 # accessing root.nodes updates chset.nodes
287 # accessing root.nodes updates chset.nodes
286 self.assertTrue(len(chset.nodes) == 9)
288 self.assertTrue(len(chset.nodes) == 9)
287
289
288 docs = root.get_node('docs')
290 docs = root.get_node('docs')
289 # we haven't yet accessed anything new as docs dir was already cached
291 # we haven't yet accessed anything new as docs dir was already cached
290 self.assertTrue(len(chset.nodes) == 9)
292 self.assertTrue(len(chset.nodes) == 9)
291 self.assertTrue(len(docs.nodes) == 8)
293 self.assertTrue(len(docs.nodes) == 8)
292 # accessing docs.nodes updates chset.nodes
294 # accessing docs.nodes updates chset.nodes
293 self.assertTrue(len(chset.nodes) == 17)
295 self.assertTrue(len(chset.nodes) == 17)
294
296
295 self.assertTrue(docs is chset.get_node('docs'))
297 self.assertTrue(docs is chset.get_node('docs'))
296 self.assertTrue(docs is root.nodes[0])
298 self.assertTrue(docs is root.nodes[0])
297 self.assertTrue(docs is root.dirs[0])
299 self.assertTrue(docs is root.dirs[0])
298 self.assertTrue(docs is chset.get_node('docs'))
300 self.assertTrue(docs is chset.get_node('docs'))
299
301
300 def test_nodes_with_changeset(self):
302 def test_nodes_with_changeset(self):
301 hex = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
303 hex = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
302 chset = self.repo.get_changeset(hex)
304 chset = self.repo.get_changeset(hex)
303 root = chset.root
305 root = chset.root
304 docs = root.get_node('docs')
306 docs = root.get_node('docs')
305 self.assertTrue(docs is chset.get_node('docs'))
307 self.assertTrue(docs is chset.get_node('docs'))
306 api = docs.get_node('api')
308 api = docs.get_node('api')
307 self.assertTrue(api is chset.get_node('docs/api'))
309 self.assertTrue(api is chset.get_node('docs/api'))
308 index = api.get_node('index.rst')
310 index = api.get_node('index.rst')
309 self.assertTrue(index is chset.get_node('docs/api/index.rst'))
311 self.assertTrue(index is chset.get_node('docs/api/index.rst'))
310 self.assertTrue(index is chset.get_node('docs') \
312 self.assertTrue(index is chset.get_node('docs') \
311 .get_node('api') \
313 .get_node('api') \
312 .get_node('index.rst'))
314 .get_node('index.rst'))
313
315
314 def test_branch_and_tags(self):
316 def test_branch_and_tags(self):
315 """
317 """
316 rev0 = self.repo.revisions[0]
318 rev0 = self.repo.revisions[0]
317 chset0 = self.repo.get_changeset(rev0)
319 chset0 = self.repo.get_changeset(rev0)
318 self.assertEqual(chset0.branch, 'master')
320 self.assertEqual(chset0.branch, 'master')
319 self.assertEqual(chset0.tags, [])
321 self.assertEqual(chset0.tags, [])
320
322
321 rev10 = self.repo.revisions[10]
323 rev10 = self.repo.revisions[10]
322 chset10 = self.repo.get_changeset(rev10)
324 chset10 = self.repo.get_changeset(rev10)
323 self.assertEqual(chset10.branch, 'master')
325 self.assertEqual(chset10.branch, 'master')
324 self.assertEqual(chset10.tags, [])
326 self.assertEqual(chset10.tags, [])
325
327
326 rev44 = self.repo.revisions[44]
328 rev44 = self.repo.revisions[44]
327 chset44 = self.repo.get_changeset(rev44)
329 chset44 = self.repo.get_changeset(rev44)
328 self.assertEqual(chset44.branch, 'web-branch')
330 self.assertEqual(chset44.branch, 'web-branch')
329
331
330 tip = self.repo.get_changeset('tip')
332 tip = self.repo.get_changeset('tip')
331 self.assertTrue('tip' in tip.tags)
333 self.assertTrue('tip' in tip.tags)
332 """
334 """
333 # Those tests would fail - branches are now going
335 # Those tests would fail - branches are now going
334 # to be changed at main API in order to support git backend
336 # to be changed at main API in order to support git backend
335 pass
337 pass
336
338
337 def _test_slices(self, limit, offset):
339 def _test_slices(self, limit, offset):
338 count = self.repo.count()
340 count = self.repo.count()
339 changesets = self.repo.get_changesets(limit=limit, offset=offset)
341 changesets = self.repo.get_changesets(limit=limit, offset=offset)
340 idx = 0
342 idx = 0
341 for changeset in changesets:
343 for changeset in changesets:
342 rev = offset + idx
344 rev = offset + idx
343 idx += 1
345 idx += 1
344 rev_id = self.repo.revisions[rev]
346 rev_id = self.repo.revisions[rev]
345 if idx > limit:
347 if idx > limit:
346 pytest.fail("Exceeded limit already (getting revision %s, "
348 pytest.fail("Exceeded limit already (getting revision %s, "
347 "there are %s total revisions, offset=%s, limit=%s)"
349 "there are %s total revisions, offset=%s, limit=%s)"
348 % (rev_id, count, offset, limit))
350 % (rev_id, count, offset, limit))
349 self.assertEqual(changeset, self.repo.get_changeset(rev_id))
351 self.assertEqual(changeset, self.repo.get_changeset(rev_id))
350 result = list(self.repo.get_changesets(limit=limit, offset=offset))
352 result = list(self.repo.get_changesets(limit=limit, offset=offset))
351 start = offset
353 start = offset
352 end = limit and offset + limit or None
354 end = limit and offset + limit or None
353 sliced = list(self.repo[start:end])
355 sliced = list(self.repo[start:end])
354 pytest.failUnlessEqual(result, sliced,
356 pytest.failUnlessEqual(result, sliced,
355 msg="Comparison failed for limit=%s, offset=%s"
357 msg="Comparison failed for limit=%s, offset=%s"
356 "(get_changeset returned: %s and sliced: %s"
358 "(get_changeset returned: %s and sliced: %s"
357 % (limit, offset, result, sliced))
359 % (limit, offset, result, sliced))
358
360
359 def _test_file_size(self, revision, path, size):
361 def _test_file_size(self, revision, path, size):
360 node = self.repo.get_changeset(revision).get_node(path)
362 node = self.repo.get_changeset(revision).get_node(path)
361 self.assertTrue(node.is_file())
363 self.assertTrue(node.is_file())
362 self.assertEqual(node.size, size)
364 self.assertEqual(node.size, size)
363
365
364 def test_file_size(self):
366 def test_file_size(self):
365 to_check = (
367 to_check = (
366 ('c1214f7e79e02fc37156ff215cd71275450cffc3',
368 ('c1214f7e79e02fc37156ff215cd71275450cffc3',
367 'vcs/backends/BaseRepository.py', 502),
369 'vcs/backends/BaseRepository.py', 502),
368 ('d7e0d30fbcae12c90680eb095a4f5f02505ce501',
370 ('d7e0d30fbcae12c90680eb095a4f5f02505ce501',
369 'vcs/backends/hg.py', 854),
371 'vcs/backends/hg.py', 854),
370 ('6e125e7c890379446e98980d8ed60fba87d0f6d1',
372 ('6e125e7c890379446e98980d8ed60fba87d0f6d1',
371 'setup.py', 1068),
373 'setup.py', 1068),
372
374
373 ('d955cd312c17b02143c04fa1099a352b04368118',
375 ('d955cd312c17b02143c04fa1099a352b04368118',
374 'vcs/backends/base.py', 2921),
376 'vcs/backends/base.py', 2921),
375 ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
377 ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
376 'vcs/backends/base.py', 3936),
378 'vcs/backends/base.py', 3936),
377 ('f50f42baeed5af6518ef4b0cb2f1423f3851a941',
379 ('f50f42baeed5af6518ef4b0cb2f1423f3851a941',
378 'vcs/backends/base.py', 6189),
380 'vcs/backends/base.py', 6189),
379 )
381 )
380 for revision, path, size in to_check:
382 for revision, path, size in to_check:
381 self._test_file_size(revision, path, size)
383 self._test_file_size(revision, path, size)
382
384
383 def test_file_history(self):
385 def test_file_history(self):
384 # we can only check if those revisions are present in the history
386 # we can only check if those revisions are present in the history
385 # as we cannot update this test every time file is changed
387 # as we cannot update this test every time file is changed
386 files = {
388 files = {
387 'setup.py': [
389 'setup.py': [
388 '54386793436c938cff89326944d4c2702340037d',
390 '54386793436c938cff89326944d4c2702340037d',
389 '51d254f0ecf5df2ce50c0b115741f4cf13985dab',
391 '51d254f0ecf5df2ce50c0b115741f4cf13985dab',
390 '998ed409c795fec2012b1c0ca054d99888b22090',
392 '998ed409c795fec2012b1c0ca054d99888b22090',
391 '5e0eb4c47f56564395f76333f319d26c79e2fb09',
393 '5e0eb4c47f56564395f76333f319d26c79e2fb09',
392 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
394 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
393 '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e',
395 '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e',
394 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
396 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
395 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
397 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
396 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
398 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
397 ],
399 ],
398 'vcs/nodes.py': [
400 'vcs/nodes.py': [
399 '33fa3223355104431402a888fa77a4e9956feb3e',
401 '33fa3223355104431402a888fa77a4e9956feb3e',
400 'fa014c12c26d10ba682fadb78f2a11c24c8118e1',
402 'fa014c12c26d10ba682fadb78f2a11c24c8118e1',
401 'e686b958768ee96af8029fe19c6050b1a8dd3b2b',
403 'e686b958768ee96af8029fe19c6050b1a8dd3b2b',
402 'ab5721ca0a081f26bf43d9051e615af2cc99952f',
404 'ab5721ca0a081f26bf43d9051e615af2cc99952f',
403 'c877b68d18e792a66b7f4c529ea02c8f80801542',
405 'c877b68d18e792a66b7f4c529ea02c8f80801542',
404 '4313566d2e417cb382948f8d9d7c765330356054',
406 '4313566d2e417cb382948f8d9d7c765330356054',
405 '6c2303a793671e807d1cfc70134c9ca0767d98c2',
407 '6c2303a793671e807d1cfc70134c9ca0767d98c2',
406 '54386793436c938cff89326944d4c2702340037d',
408 '54386793436c938cff89326944d4c2702340037d',
407 '54000345d2e78b03a99d561399e8e548de3f3203',
409 '54000345d2e78b03a99d561399e8e548de3f3203',
408 '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b',
410 '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b',
409 '2d03ca750a44440fb5ea8b751176d1f36f8e8f46',
411 '2d03ca750a44440fb5ea8b751176d1f36f8e8f46',
410 '2a08b128c206db48c2f0b8f70df060e6db0ae4f8',
412 '2a08b128c206db48c2f0b8f70df060e6db0ae4f8',
411 '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b',
413 '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b',
412 'ac71e9503c2ca95542839af0ce7b64011b72ea7c',
414 'ac71e9503c2ca95542839af0ce7b64011b72ea7c',
413 '12669288fd13adba2a9b7dd5b870cc23ffab92d2',
415 '12669288fd13adba2a9b7dd5b870cc23ffab92d2',
414 '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382',
416 '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382',
415 '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5',
417 '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5',
416 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
418 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
417 'f50f42baeed5af6518ef4b0cb2f1423f3851a941',
419 'f50f42baeed5af6518ef4b0cb2f1423f3851a941',
418 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25',
420 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25',
419 'f15c21f97864b4f071cddfbf2750ec2e23859414',
421 'f15c21f97864b4f071cddfbf2750ec2e23859414',
420 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade',
422 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade',
421 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b',
423 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b',
422 '84dec09632a4458f79f50ddbbd155506c460b4f9',
424 '84dec09632a4458f79f50ddbbd155506c460b4f9',
423 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
425 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
424 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
426 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
425 '3bf1c5868e570e39569d094f922d33ced2fa3b2b',
427 '3bf1c5868e570e39569d094f922d33ced2fa3b2b',
426 'b8d04012574729d2c29886e53b1a43ef16dd00a1',
428 'b8d04012574729d2c29886e53b1a43ef16dd00a1',
427 '6970b057cffe4aab0a792aa634c89f4bebf01441',
429 '6970b057cffe4aab0a792aa634c89f4bebf01441',
428 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
430 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
429 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
431 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
430 ],
432 ],
431 'vcs/backends/git.py': [
433 'vcs/backends/git.py': [
432 '4cf116ad5a457530381135e2f4c453e68a1b0105',
434 '4cf116ad5a457530381135e2f4c453e68a1b0105',
433 '9a751d84d8e9408e736329767387f41b36935153',
435 '9a751d84d8e9408e736329767387f41b36935153',
434 'cb681fb539c3faaedbcdf5ca71ca413425c18f01',
436 'cb681fb539c3faaedbcdf5ca71ca413425c18f01',
435 '428f81bb652bcba8d631bce926e8834ff49bdcc6',
437 '428f81bb652bcba8d631bce926e8834ff49bdcc6',
436 '180ab15aebf26f98f714d8c68715e0f05fa6e1c7',
438 '180ab15aebf26f98f714d8c68715e0f05fa6e1c7',
437 '2b8e07312a2e89e92b90426ab97f349f4bce2a3a',
439 '2b8e07312a2e89e92b90426ab97f349f4bce2a3a',
438 '50e08c506174d8645a4bb517dd122ac946a0f3bf',
440 '50e08c506174d8645a4bb517dd122ac946a0f3bf',
439 '54000345d2e78b03a99d561399e8e548de3f3203',
441 '54000345d2e78b03a99d561399e8e548de3f3203',
440 ],
442 ],
441 }
443 }
442 for path, revs in files.items():
444 for path, revs in files.items():
443 node = self.repo.get_changeset(revs[0]).get_node(path)
445 node = self.repo.get_changeset(revs[0]).get_node(path)
444 node_revs = [chset.raw_id for chset in node.history]
446 node_revs = [chset.raw_id for chset in node.history]
445 self.assertTrue(set(revs).issubset(set(node_revs)),
447 self.assertTrue(set(revs).issubset(set(node_revs)),
446 "We assumed that %s is subset of revisions for which file %s "
448 "We assumed that %s is subset of revisions for which file %s "
447 "has been changed, and history of that node returned: %s"
449 "has been changed, and history of that node returned: %s"
448 % (revs, path, node_revs))
450 % (revs, path, node_revs))
449
451
450 def test_file_annotate(self):
452 def test_file_annotate(self):
451 files = {
453 files = {
452 'vcs/backends/__init__.py': {
454 'vcs/backends/__init__.py': {
453 'c1214f7e79e02fc37156ff215cd71275450cffc3': {
455 'c1214f7e79e02fc37156ff215cd71275450cffc3': {
454 'lines_no': 1,
456 'lines_no': 1,
455 'changesets': [
457 'changesets': [
456 'c1214f7e79e02fc37156ff215cd71275450cffc3',
458 'c1214f7e79e02fc37156ff215cd71275450cffc3',
457 ],
459 ],
458 },
460 },
459 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': {
461 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': {
460 'lines_no': 21,
462 'lines_no': 21,
461 'changesets': [
463 'changesets': [
462 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
464 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
463 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
465 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
464 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
466 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
465 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
467 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
466 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
468 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
467 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
469 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
468 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
470 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
469 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
471 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
470 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
472 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
471 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
473 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
472 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
474 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
473 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
475 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
474 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
476 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
475 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
477 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
476 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
478 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
477 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
479 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
478 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
480 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
479 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
481 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
480 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
482 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
481 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
483 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
482 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
484 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
483 ],
485 ],
484 },
486 },
485 'e29b67bd158580fc90fc5e9111240b90e6e86064': {
487 'e29b67bd158580fc90fc5e9111240b90e6e86064': {
486 'lines_no': 32,
488 'lines_no': 32,
487 'changesets': [
489 'changesets': [
488 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
490 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
489 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
491 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
490 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
492 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
491 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
493 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
492 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
494 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
493 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
495 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
494 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
496 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
495 '54000345d2e78b03a99d561399e8e548de3f3203',
497 '54000345d2e78b03a99d561399e8e548de3f3203',
496 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
498 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
497 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
499 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
498 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
500 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
499 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
501 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
500 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
502 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
501 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
503 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
502 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
504 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
503 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
505 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
504 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
506 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
505 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
507 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
506 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
508 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
507 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
509 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
508 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
510 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
509 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
511 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
510 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
512 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
511 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
513 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
512 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
514 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
513 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
515 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
514 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
516 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
515 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
517 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
516 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
518 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
517 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
519 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
518 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
520 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
519 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
521 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
520 ],
522 ],
521 },
523 },
522 },
524 },
523 }
525 }
524
526
525 for fname, revision_dict in files.items():
527 for fname, revision_dict in files.items():
526 for rev, data in revision_dict.items():
528 for rev, data in revision_dict.items():
527 cs = self.repo.get_changeset(rev)
529 cs = self.repo.get_changeset(rev)
528
530
529 l1_1 = [x[1] for x in cs.get_file_annotate(fname)]
531 l1_1 = [x[1] for x in cs.get_file_annotate(fname)]
530 l1_2 = [x[2]().raw_id for x in cs.get_file_annotate(fname)]
532 l1_2 = [x[2]().raw_id for x in cs.get_file_annotate(fname)]
531 self.assertEqual(l1_1, l1_2)
533 self.assertEqual(l1_1, l1_2)
532 l1 = l1_1
534 l1 = l1_1
533 l2 = files[fname][rev]['changesets']
535 l2 = files[fname][rev]['changesets']
534 self.assertTrue(l1 == l2 , "The lists of revision for %s@rev %s"
536 self.assertTrue(l1 == l2 , "The lists of revision for %s@rev %s"
535 "from annotation list should match each other, "
537 "from annotation list should match each other, "
536 "got \n%s \nvs \n%s " % (fname, rev, l1, l2))
538 "got \n%s \nvs \n%s " % (fname, rev, l1, l2))
537
539
538 def test_files_state(self):
540 def test_files_state(self):
539 """
541 """
540 Tests state of FileNodes.
542 Tests state of FileNodes.
541 """
543 """
542 node = self.repo \
544 node = self.repo \
543 .get_changeset('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0') \
545 .get_changeset('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0') \
544 .get_node('vcs/utils/diffs.py')
546 .get_node('vcs/utils/diffs.py')
545 self.assertTrue(node.state, NodeState.ADDED)
547 self.assertTrue(node.state, NodeState.ADDED)
546 self.assertTrue(node.added)
548 self.assertTrue(node.added)
547 self.assertFalse(node.changed)
549 self.assertFalse(node.changed)
548 self.assertFalse(node.not_changed)
550 self.assertFalse(node.not_changed)
549 self.assertFalse(node.removed)
551 self.assertFalse(node.removed)
550
552
551 node = self.repo \
553 node = self.repo \
552 .get_changeset('33fa3223355104431402a888fa77a4e9956feb3e') \
554 .get_changeset('33fa3223355104431402a888fa77a4e9956feb3e') \
553 .get_node('.hgignore')
555 .get_node('.hgignore')
554 self.assertTrue(node.state, NodeState.CHANGED)
556 self.assertTrue(node.state, NodeState.CHANGED)
555 self.assertFalse(node.added)
557 self.assertFalse(node.added)
556 self.assertTrue(node.changed)
558 self.assertTrue(node.changed)
557 self.assertFalse(node.not_changed)
559 self.assertFalse(node.not_changed)
558 self.assertFalse(node.removed)
560 self.assertFalse(node.removed)
559
561
560 node = self.repo \
562 node = self.repo \
561 .get_changeset('e29b67bd158580fc90fc5e9111240b90e6e86064') \
563 .get_changeset('e29b67bd158580fc90fc5e9111240b90e6e86064') \
562 .get_node('setup.py')
564 .get_node('setup.py')
563 self.assertTrue(node.state, NodeState.NOT_CHANGED)
565 self.assertTrue(node.state, NodeState.NOT_CHANGED)
564 self.assertFalse(node.added)
566 self.assertFalse(node.added)
565 self.assertFalse(node.changed)
567 self.assertFalse(node.changed)
566 self.assertTrue(node.not_changed)
568 self.assertTrue(node.not_changed)
567 self.assertFalse(node.removed)
569 self.assertFalse(node.removed)
568
570
569 # If node has REMOVED state then trying to fetch it would raise
571 # If node has REMOVED state then trying to fetch it would raise
570 # ChangesetError exception
572 # ChangesetError exception
571 chset = self.repo.get_changeset(
573 chset = self.repo.get_changeset(
572 'fa6600f6848800641328adbf7811fd2372c02ab2')
574 'fa6600f6848800641328adbf7811fd2372c02ab2')
573 path = 'vcs/backends/BaseRepository.py'
575 path = 'vcs/backends/BaseRepository.py'
574 self.assertRaises(NodeDoesNotExistError, chset.get_node, path)
576 self.assertRaises(NodeDoesNotExistError, chset.get_node, path)
575 # but it would be one of ``removed`` (changeset's attribute)
577 # but it would be one of ``removed`` (changeset's attribute)
576 self.assertTrue(path in [rf.path for rf in chset.removed])
578 self.assertTrue(path in [rf.path for rf in chset.removed])
577
579
578 chset = self.repo.get_changeset(
580 chset = self.repo.get_changeset(
579 '54386793436c938cff89326944d4c2702340037d')
581 '54386793436c938cff89326944d4c2702340037d')
580 changed = ['setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py',
582 changed = ['setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py',
581 'vcs/nodes.py']
583 'vcs/nodes.py']
582 self.assertEqual(set(changed), set([f.path for f in chset.changed]))
584 self.assertEqual(set(changed), set([f.path for f in chset.changed]))
583
585
584 def test_commit_message_is_unicode(self):
586 def test_commit_message_is_unicode(self):
585 for cs in self.repo:
587 for cs in self.repo:
586 self.assertEqual(type(cs.message), unicode)
588 self.assertEqual(type(cs.message), unicode)
587
589
588 def test_changeset_author_is_unicode(self):
590 def test_changeset_author_is_unicode(self):
589 for cs in self.repo:
591 for cs in self.repo:
590 self.assertEqual(type(cs.author), unicode)
592 self.assertEqual(type(cs.author), unicode)
591
593
592 def test_repo_files_content_is_unicode(self):
594 def test_repo_files_content_is_unicode(self):
593 changeset = self.repo.get_changeset()
595 changeset = self.repo.get_changeset()
594 for node in changeset.get_node('/'):
596 for node in changeset.get_node('/'):
595 if node.is_file():
597 if node.is_file():
596 self.assertEqual(type(node.content), unicode)
598 self.assertEqual(type(node.content), unicode)
597
599
598 def test_wrong_path(self):
600 def test_wrong_path(self):
599 # There is 'setup.py' in the root dir but not there:
601 # There is 'setup.py' in the root dir but not there:
600 path = 'foo/bar/setup.py'
602 path = 'foo/bar/setup.py'
601 tip = self.repo.get_changeset()
603 tip = self.repo.get_changeset()
602 self.assertRaises(VCSError, tip.get_node, path)
604 self.assertRaises(VCSError, tip.get_node, path)
603
605
604 def test_author_email(self):
606 def test_author_email(self):
605 self.assertEqual('marcin@python-blog.com',
607 self.assertEqual('marcin@python-blog.com',
606 self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3') \
608 self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3') \
607 .author_email)
609 .author_email)
608 self.assertEqual('lukasz.balcerzak@python-center.pl',
610 self.assertEqual('lukasz.balcerzak@python-center.pl',
609 self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b') \
611 self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b') \
610 .author_email)
612 .author_email)
611 self.assertEqual('',
613 self.assertEqual('',
612 self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992') \
614 self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992') \
613 .author_email)
615 .author_email)
614
616
615 def test_author_username(self):
617 def test_author_username(self):
616 self.assertEqual('Marcin Kuzminski',
618 self.assertEqual('Marcin Kuzminski',
617 self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3') \
619 self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3') \
618 .author_name)
620 .author_name)
619 self.assertEqual('Lukasz Balcerzak',
621 self.assertEqual('Lukasz Balcerzak',
620 self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b') \
622 self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b') \
621 .author_name)
623 .author_name)
622 self.assertEqual('marcink none@none',
624 self.assertEqual('marcink none@none',
623 self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992') \
625 self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992') \
624 .author_name)
626 .author_name)
625
627
626
628
627 class GitSpecificTest(unittest.TestCase):
629 class GitSpecificTest(unittest.TestCase):
628
630
629 def test_error_is_raised_for_added_if_diff_name_status_is_wrong(self):
631 def test_error_is_raised_for_added_if_diff_name_status_is_wrong(self):
630 repo = mock.MagicMock()
632 repo = mock.MagicMock()
631 changeset = GitChangeset(repo, 'foobar')
633 changeset = GitChangeset(repo, 'foobar')
632 changeset._diff_name_status = 'foobar'
634 changeset._diff_name_status = 'foobar'
633 with self.assertRaises(VCSError):
635 with self.assertRaises(VCSError):
634 changeset.added
636 changeset.added
635
637
636 def test_error_is_raised_for_changed_if_diff_name_status_is_wrong(self):
638 def test_error_is_raised_for_changed_if_diff_name_status_is_wrong(self):
637 repo = mock.MagicMock()
639 repo = mock.MagicMock()
638 changeset = GitChangeset(repo, 'foobar')
640 changeset = GitChangeset(repo, 'foobar')
639 changeset._diff_name_status = 'foobar'
641 changeset._diff_name_status = 'foobar'
640 with self.assertRaises(VCSError):
642 with self.assertRaises(VCSError):
641 changeset.added
643 changeset.added
642
644
643 def test_error_is_raised_for_removed_if_diff_name_status_is_wrong(self):
645 def test_error_is_raised_for_removed_if_diff_name_status_is_wrong(self):
644 repo = mock.MagicMock()
646 repo = mock.MagicMock()
645 changeset = GitChangeset(repo, 'foobar')
647 changeset = GitChangeset(repo, 'foobar')
646 changeset._diff_name_status = 'foobar'
648 changeset._diff_name_status = 'foobar'
647 with self.assertRaises(VCSError):
649 with self.assertRaises(VCSError):
648 changeset.added
650 changeset.added
649
651
650
652
651 class GitSpecificWithRepoTest(_BackendTestMixin, unittest.TestCase):
653 class GitSpecificWithRepoTest(_BackendTestMixin, unittest.TestCase):
652 backend_alias = 'git'
654 backend_alias = 'git'
653
655
654 @classmethod
656 @classmethod
655 def _get_commits(cls):
657 def _get_commits(cls):
656 return [
658 return [
657 {
659 {
658 'message': 'Initial',
660 'message': 'Initial',
659 'author': 'Joe Doe <joe.doe@example.com>',
661 'author': 'Joe Doe <joe.doe@example.com>',
660 'date': datetime.datetime(2010, 1, 1, 20),
662 'date': datetime.datetime(2010, 1, 1, 20),
661 'added': [
663 'added': [
662 FileNode('foobar/static/js/admin/base.js', content='base'),
664 FileNode('foobar/static/js/admin/base.js', content='base'),
663 FileNode('foobar/static/admin', content='admin',
665 FileNode('foobar/static/admin', content='admin',
664 mode=0120000), # this is a link
666 mode=0120000), # this is a link
665 FileNode('foo', content='foo'),
667 FileNode('foo', content='foo'),
666 ],
668 ],
667 },
669 },
668 {
670 {
669 'message': 'Second',
671 'message': 'Second',
670 'author': 'Joe Doe <joe.doe@example.com>',
672 'author': 'Joe Doe <joe.doe@example.com>',
671 'date': datetime.datetime(2010, 1, 1, 22),
673 'date': datetime.datetime(2010, 1, 1, 22),
672 'added': [
674 'added': [
673 FileNode('foo2', content='foo2'),
675 FileNode('foo2', content='foo2'),
674 ],
676 ],
675 },
677 },
676 ]
678 ]
677
679
678 def test_paths_slow_traversing(self):
680 def test_paths_slow_traversing(self):
679 cs = self.repo.get_changeset()
681 cs = self.repo.get_changeset()
680 self.assertEqual(cs.get_node('foobar').get_node('static').get_node('js')
682 self.assertEqual(cs.get_node('foobar').get_node('static').get_node('js')
681 .get_node('admin').get_node('base.js').content, 'base')
683 .get_node('admin').get_node('base.js').content, 'base')
682
684
683 def test_paths_fast_traversing(self):
685 def test_paths_fast_traversing(self):
684 cs = self.repo.get_changeset()
686 cs = self.repo.get_changeset()
685 self.assertEqual(cs.get_node('foobar/static/js/admin/base.js').content,
687 self.assertEqual(cs.get_node('foobar/static/js/admin/base.js').content,
686 'base')
688 'base')
687
689
688 def test_workdir_get_branch(self):
690 def test_workdir_get_branch(self):
689 self.repo.run_git_command(['checkout', '-b', 'production'])
691 self.repo.run_git_command(['checkout', '-b', 'production'])
690 # Regression test: one of following would fail if we don't check
692 # Regression test: one of following would fail if we don't check
691 # .git/HEAD file
693 # .git/HEAD file
692 self.repo.run_git_command(['checkout', 'production'])
694 self.repo.run_git_command(['checkout', 'production'])
693 self.assertEqual(self.repo.workdir.get_branch(), 'production')
695 self.assertEqual(self.repo.workdir.get_branch(), 'production')
694 self.repo.run_git_command(['checkout', 'master'])
696 self.repo.run_git_command(['checkout', 'master'])
695 self.assertEqual(self.repo.workdir.get_branch(), 'master')
697 self.assertEqual(self.repo.workdir.get_branch(), 'master')
696
698
697 def test_get_diff_runs_git_command_with_hashes(self):
699 def test_get_diff_runs_git_command_with_hashes(self):
698 self.repo.run_git_command = mock.Mock(return_value=['', ''])
700 self.repo.run_git_command = mock.Mock(return_value=['', ''])
699 self.repo.get_diff(0, 1)
701 self.repo.get_diff(0, 1)
700 self.repo.run_git_command.assert_called_once_with(
702 self.repo.run_git_command.assert_called_once_with(
701 ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
703 ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
702 self.repo._get_revision(0), self.repo._get_revision(1)])
704 self.repo._get_revision(0), self.repo._get_revision(1)])
703
705
704 def test_get_diff_runs_git_command_with_str_hashes(self):
706 def test_get_diff_runs_git_command_with_str_hashes(self):
705 self.repo.run_git_command = mock.Mock(return_value=['', ''])
707 self.repo.run_git_command = mock.Mock(return_value=['', ''])
706 self.repo.get_diff(self.repo.EMPTY_CHANGESET, 1)
708 self.repo.get_diff(self.repo.EMPTY_CHANGESET, 1)
707 self.repo.run_git_command.assert_called_once_with(
709 self.repo.run_git_command.assert_called_once_with(
708 ['show', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
710 ['show', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
709 self.repo._get_revision(1)])
711 self.repo._get_revision(1)])
710
712
711 def test_get_diff_runs_git_command_with_path_if_its_given(self):
713 def test_get_diff_runs_git_command_with_path_if_its_given(self):
712 self.repo.run_git_command = mock.Mock(return_value=['', ''])
714 self.repo.run_git_command = mock.Mock(return_value=['', ''])
713 self.repo.get_diff(0, 1, 'foo')
715 self.repo.get_diff(0, 1, 'foo')
714 self.repo.run_git_command.assert_called_once_with(
716 self.repo.run_git_command.assert_called_once_with(
715 ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
717 ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
716 self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
718 self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
717
719
718
720
719 class GitRegressionTest(_BackendTestMixin, unittest.TestCase):
721 class GitRegressionTest(_BackendTestMixin, unittest.TestCase):
720 backend_alias = 'git'
722 backend_alias = 'git'
721
723
722 @classmethod
724 @classmethod
723 def _get_commits(cls):
725 def _get_commits(cls):
724 return [
726 return [
725 {
727 {
726 'message': 'Initial',
728 'message': 'Initial',
727 'author': 'Joe Doe <joe.doe@example.com>',
729 'author': 'Joe Doe <joe.doe@example.com>',
728 'date': datetime.datetime(2010, 1, 1, 20),
730 'date': datetime.datetime(2010, 1, 1, 20),
729 'added': [
731 'added': [
730 FileNode('bot/__init__.py', content='base'),
732 FileNode('bot/__init__.py', content='base'),
731 FileNode('bot/templates/404.html', content='base'),
733 FileNode('bot/templates/404.html', content='base'),
732 FileNode('bot/templates/500.html', content='base'),
734 FileNode('bot/templates/500.html', content='base'),
733 ],
735 ],
734 },
736 },
735 {
737 {
736 'message': 'Second',
738 'message': 'Second',
737 'author': 'Joe Doe <joe.doe@example.com>',
739 'author': 'Joe Doe <joe.doe@example.com>',
738 'date': datetime.datetime(2010, 1, 1, 22),
740 'date': datetime.datetime(2010, 1, 1, 22),
739 'added': [
741 'added': [
740 FileNode('bot/build/migrations/1.py', content='foo2'),
742 FileNode('bot/build/migrations/1.py', content='foo2'),
741 FileNode('bot/build/migrations/2.py', content='foo2'),
743 FileNode('bot/build/migrations/2.py', content='foo2'),
742 FileNode('bot/build/static/templates/f.html', content='foo2'),
744 FileNode('bot/build/static/templates/f.html', content='foo2'),
743 FileNode('bot/build/static/templates/f1.html', content='foo2'),
745 FileNode('bot/build/static/templates/f1.html', content='foo2'),
744 FileNode('bot/build/templates/err.html', content='foo2'),
746 FileNode('bot/build/templates/err.html', content='foo2'),
745 FileNode('bot/build/templates/err2.html', content='foo2'),
747 FileNode('bot/build/templates/err2.html', content='foo2'),
746 ],
748 ],
747 },
749 },
748 ]
750 ]
749
751
750 def test_similar_paths(self):
752 def test_similar_paths(self):
751 cs = self.repo.get_changeset()
753 cs = self.repo.get_changeset()
752 paths = lambda *n:[x.path for x in n]
754 paths = lambda *n:[x.path for x in n]
753 self.assertEqual(paths(*cs.get_nodes('bot')), ['bot/build', 'bot/templates', 'bot/__init__.py'])
755 self.assertEqual(paths(*cs.get_nodes('bot')), ['bot/build', 'bot/templates', 'bot/__init__.py'])
754 self.assertEqual(paths(*cs.get_nodes('bot/build')), ['bot/build/migrations', 'bot/build/static', 'bot/build/templates'])
756 self.assertEqual(paths(*cs.get_nodes('bot/build')), ['bot/build/migrations', 'bot/build/static', 'bot/build/templates'])
755 self.assertEqual(paths(*cs.get_nodes('bot/build/static')), ['bot/build/static/templates'])
757 self.assertEqual(paths(*cs.get_nodes('bot/build/static')), ['bot/build/static/templates'])
756 # this get_nodes below causes troubles !
758 # this get_nodes below causes troubles !
757 self.assertEqual(paths(*cs.get_nodes('bot/build/static/templates')), ['bot/build/static/templates/f.html', 'bot/build/static/templates/f1.html'])
759 self.assertEqual(paths(*cs.get_nodes('bot/build/static/templates')), ['bot/build/static/templates/f.html', 'bot/build/static/templates/f1.html'])
758 self.assertEqual(paths(*cs.get_nodes('bot/build/templates')), ['bot/build/templates/err.html', 'bot/build/templates/err2.html'])
760 self.assertEqual(paths(*cs.get_nodes('bot/build/templates')), ['bot/build/templates/err.html', 'bot/build/templates/err2.html'])
759 self.assertEqual(paths(*cs.get_nodes('bot/templates/')), ['bot/templates/404.html', 'bot/templates/500.html'])
761 self.assertEqual(paths(*cs.get_nodes('bot/templates/')), ['bot/templates/404.html', 'bot/templates/500.html'])
760
762
761
763
762 class GitHooksTest(unittest.TestCase):
764 class GitHooksTest(unittest.TestCase):
763 """
765 """
764 Tests related to hook functionality of Git repositories.
766 Tests related to hook functionality of Git repositories.
765 """
767 """
766
768
767 def setUp(self):
769 def setUp(self):
768 # For each run we want a fresh repo.
770 # For each run we want a fresh repo.
769 self.repo_directory = get_new_dir("githookrepo")
771 self.repo_directory = get_new_dir("githookrepo")
770 self.repo = GitRepository(self.repo_directory, create=True)
772 self.repo = GitRepository(self.repo_directory, create=True)
771
773
772 # Create a dictionary where keys are hook names, and values are paths to
774 # Create a dictionary where keys are hook names, and values are paths to
773 # them. Deduplicates code in tests a bit.
775 # them. Deduplicates code in tests a bit.
774 self.hook_directory = self.repo.get_hook_location()
776 self.hook_directory = self.repo.get_hook_location()
775 self.kallithea_hooks = {h: os.path.join(self.hook_directory, h) for h in ("pre-receive", "post-receive")}
777 self.kallithea_hooks = {h: os.path.join(self.hook_directory, h) for h in ("pre-receive", "post-receive")}
776
778
777 def test_hooks_created_if_missing(self):
779 def test_hooks_created_if_missing(self):
778 """
780 """
779 Tests if hooks are installed in repository if they are missing.
781 Tests if hooks are installed in repository if they are missing.
780 """
782 """
781
783
782 for hook, hook_path in self.kallithea_hooks.iteritems():
784 for hook, hook_path in self.kallithea_hooks.iteritems():
783 if os.path.exists(hook_path):
785 if os.path.exists(hook_path):
784 os.remove(hook_path)
786 os.remove(hook_path)
785
787
786 ScmModel().install_git_hooks(repo=self.repo)
788 ScmModel().install_git_hooks(repo=self.repo)
787
789
788 for hook, hook_path in self.kallithea_hooks.iteritems():
790 for hook, hook_path in self.kallithea_hooks.iteritems():
789 self.assertTrue(os.path.exists(hook_path))
791 self.assertTrue(os.path.exists(hook_path))
790
792
791 def test_kallithea_hooks_updated(self):
793 def test_kallithea_hooks_updated(self):
792 """
794 """
793 Tests if hooks are updated if they are Kallithea hooks already.
795 Tests if hooks are updated if they are Kallithea hooks already.
794 """
796 """
795
797
796 for hook, hook_path in self.kallithea_hooks.iteritems():
798 for hook, hook_path in self.kallithea_hooks.iteritems():
797 with open(hook_path, "w") as f:
799 with open(hook_path, "w") as f:
798 f.write("KALLITHEA_HOOK_VER=0.0.0\nJUST_BOGUS")
800 f.write("KALLITHEA_HOOK_VER=0.0.0\nJUST_BOGUS")
799
801
800 ScmModel().install_git_hooks(repo=self.repo)
802 ScmModel().install_git_hooks(repo=self.repo)
801
803
802 for hook, hook_path in self.kallithea_hooks.iteritems():
804 for hook, hook_path in self.kallithea_hooks.iteritems():
803 with open(hook_path) as f:
805 with open(hook_path) as f:
804 self.assertNotIn("JUST_BOGUS", f.read())
806 self.assertNotIn("JUST_BOGUS", f.read())
805
807
806 def test_custom_hooks_untouched(self):
808 def test_custom_hooks_untouched(self):
807 """
809 """
808 Tests if hooks are left untouched if they are not Kallithea hooks.
810 Tests if hooks are left untouched if they are not Kallithea hooks.
809 """
811 """
810
812
811 for hook, hook_path in self.kallithea_hooks.iteritems():
813 for hook, hook_path in self.kallithea_hooks.iteritems():
812 with open(hook_path, "w") as f:
814 with open(hook_path, "w") as f:
813 f.write("#!/bin/bash\n#CUSTOM_HOOK")
815 f.write("#!/bin/bash\n#CUSTOM_HOOK")
814
816
815 ScmModel().install_git_hooks(repo=self.repo)
817 ScmModel().install_git_hooks(repo=self.repo)
816
818
817 for hook, hook_path in self.kallithea_hooks.iteritems():
819 for hook, hook_path in self.kallithea_hooks.iteritems():
818 with open(hook_path) as f:
820 with open(hook_path) as f:
819 self.assertIn("CUSTOM_HOOK", f.read())
821 self.assertIn("CUSTOM_HOOK", f.read())
820
822
821 def test_custom_hooks_forced_update(self):
823 def test_custom_hooks_forced_update(self):
822 """
824 """
823 Tests if hooks are forcefully updated even though they are custom hooks.
825 Tests if hooks are forcefully updated even though they are custom hooks.
824 """
826 """
825
827
826 for hook, hook_path in self.kallithea_hooks.iteritems():
828 for hook, hook_path in self.kallithea_hooks.iteritems():
827 with open(hook_path, "w") as f:
829 with open(hook_path, "w") as f:
828 f.write("#!/bin/bash\n#CUSTOM_HOOK")
830 f.write("#!/bin/bash\n#CUSTOM_HOOK")
829
831
830 ScmModel().install_git_hooks(repo=self.repo, force_create=True)
832 ScmModel().install_git_hooks(repo=self.repo, force_create=True)
831
833
832 for hook, hook_path in self.kallithea_hooks.iteritems():
834 for hook, hook_path in self.kallithea_hooks.iteritems():
833 with open(hook_path) as f:
835 with open(hook_path) as f:
834 self.assertIn("KALLITHEA_HOOK_VER", f.read())
836 self.assertIn("KALLITHEA_HOOK_VER", f.read())
835
837
836
838
837 if __name__ == '__main__':
839 if __name__ == '__main__':
838 unittest.main()
840 unittest.main()
General Comments 0
You need to be logged in to leave comments. Login now