##// END OF EJS Templates
vcs: introduce 'branches' attribute on changesets, making it possible for Git to show multiple branches for a changeset...
vcs: introduce 'branches' attribute on changesets, making it possible for Git to show multiple branches for a changeset Mercurial changesets will always have have exactly one branch (which might be "default"). The VCS data model was the same. Git allows for a changeset to have 0 or more branches ... and possibly one of them as active. The right data model is thus to have an enumerable of branches. We thus add a 'branches' attribute and use it where applicable. The existing 'branch' attribute used some heuristics to decide which branch use as "the" branch ... and in some places code (and tests) rely on that. We thus keep that old method, knowing that some of its uses probably should move to 'branches'. The code for retrieving Git branches is based on work by Dominik Ruf.

File last commit:

r7067:3dbb625d default
r7067:3dbb625d default
Show More
test_changesets.py
392 lines | 14.5 KiB | text/x-python | PythonLexer
# encoding: utf8
import time
import datetime
import pytest
from kallithea.lib import vcs
from kallithea.lib.vcs.backends.base import BaseChangeset
from kallithea.lib.vcs.nodes import (
FileNode, AddedFileNodesGenerator,
ChangedFileNodesGenerator, RemovedFileNodesGenerator
)
from kallithea.lib.vcs.exceptions import (
BranchDoesNotExistError, ChangesetDoesNotExistError,
RepositoryError, EmptyRepositoryError
)
from kallithea.tests.vcs.base import _BackendTestMixin
from kallithea.tests.vcs.conf import SCM_TESTS, get_new_dir
class TestBaseChangeset(object):
def test_as_dict(self):
changeset = BaseChangeset()
changeset.id = 'ID'
changeset.raw_id = 'RAW_ID'
changeset.short_id = 'SHORT_ID'
changeset.revision = 1009
changeset.date = datetime.datetime(2011, 1, 30, 1, 45)
changeset.message = 'Message of a commit'
changeset.author = 'Joe Doe <joe.doe@example.com>'
changeset.added = [FileNode('foo/bar/baz'), FileNode(u'foobar'), FileNode(u'blåbærgrød')]
changeset.changed = []
changeset.removed = []
assert changeset.as_dict() == {
'id': 'ID',
'raw_id': 'RAW_ID',
'short_id': 'SHORT_ID',
'revision': 1009,
'date': datetime.datetime(2011, 1, 30, 1, 45),
'message': 'Message of a commit',
'author': {
'name': 'Joe Doe',
'email': 'joe.doe@example.com',
},
'added': ['foo/bar/baz', 'foobar', u'bl\xe5b\xe6rgr\xf8d'],
'changed': [],
'removed': [],
}
class _ChangesetsWithCommitsTestCaseixin(_BackendTestMixin):
recreate_repo_per_test = True
@classmethod
def _get_commits(cls):
start_date = datetime.datetime(2010, 1, 1, 20)
for x in xrange(5):
yield {
'message': 'Commit %d' % x,
'author': 'Joe Doe <joe.doe@example.com>',
'date': start_date + datetime.timedelta(hours=12 * x),
'added': [
FileNode('file_%d.txt' % x, content='Foobar %d' % x),
],
}
def test_new_branch(self):
self.imc.add(vcs.nodes.FileNode('docs/index.txt',
content='Documentation\n'))
foobar_tip = self.imc.commit(
message=u'New branch: foobar',
author=u'joe',
branch='foobar',
)
assert 'foobar' in self.repo.branches
assert foobar_tip.branch == 'foobar'
assert foobar_tip.branches == ['foobar']
# 'foobar' should be the only branch that contains the new commit
branch_tips = self.repo.branches.values()
assert branch_tips.count(str(foobar_tip.raw_id)) == 1
def test_new_head_in_default_branch(self):
tip = self.repo.get_changeset()
self.imc.add(vcs.nodes.FileNode('docs/index.txt',
content='Documentation\n'))
foobar_tip = self.imc.commit(
message=u'New branch: foobar',
author=u'joe',
branch='foobar',
parents=[tip],
)
self.imc.change(vcs.nodes.FileNode('docs/index.txt',
content='Documentation\nand more...\n'))
newtip = self.imc.commit(
message=u'At default branch',
author=u'joe',
branch=foobar_tip.branch,
parents=[foobar_tip],
)
newest_tip = self.imc.commit(
message=u'Merged with %s' % foobar_tip.raw_id,
author=u'joe',
branch=self.backend_class.DEFAULT_BRANCH_NAME,
parents=[newtip, foobar_tip],
)
assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
assert newest_tip.branches == [self.backend_class.DEFAULT_BRANCH_NAME]
def test_get_changesets_respects_branch_name(self):
tip = self.repo.get_changeset()
self.imc.add(vcs.nodes.FileNode('docs/index.txt',
content='Documentation\n'))
doc_changeset = self.imc.commit(
message=u'New branch: docs',
author=u'joe',
branch='docs',
)
self.imc.add(vcs.nodes.FileNode('newfile', content=''))
self.imc.commit(
message=u'Back in default branch',
author=u'joe',
parents=[tip],
)
default_branch_changesets = self.repo.get_changesets(
branch_name=self.repo.DEFAULT_BRANCH_NAME)
assert doc_changeset not in default_branch_changesets
def test_get_changeset_by_branch(self):
for branch, sha in self.repo.branches.iteritems():
assert sha == self.repo.get_changeset(branch).raw_id
def test_get_changeset_by_tag(self):
for tag, sha in self.repo.tags.iteritems():
assert sha == self.repo.get_changeset(tag).raw_id
def test_get_changeset_parents(self):
for test_rev in [1, 2, 3]:
sha = self.repo.get_changeset(test_rev-1)
assert [sha] == self.repo.get_changeset(test_rev).parents
def test_get_changeset_children(self):
for test_rev in [1, 2, 3]:
sha = self.repo.get_changeset(test_rev+1)
assert [sha] == self.repo.get_changeset(test_rev).children
class _ChangesetsTestCaseMixin(_BackendTestMixin):
recreate_repo_per_test = False
@classmethod
def _get_commits(cls):
start_date = datetime.datetime(2010, 1, 1, 20)
for x in xrange(5):
yield {
'message': u'Commit %d' % x,
'author': u'Joe Doe <joe.doe@example.com>',
'date': start_date + datetime.timedelta(hours=12 * x),
'added': [
FileNode('file_%d.txt' % x, content='Foobar %d' % x),
],
}
def test_simple(self):
tip = self.repo.get_changeset()
assert tip.date == datetime.datetime(2010, 1, 3, 20)
def test_get_changesets_is_ordered_by_date(self):
changesets = list(self.repo.get_changesets())
ordered_by_date = sorted(changesets,
key=lambda cs: cs.date)
assert changesets == ordered_by_date
def test_get_changesets_respects_start(self):
second_id = self.repo.revisions[1]
changesets = list(self.repo.get_changesets(start=second_id))
assert len(changesets) == 4
def test_get_changesets_numerical_id_respects_start(self):
second_id = 1
changesets = list(self.repo.get_changesets(start=second_id))
assert len(changesets) == 4
def test_get_changesets_includes_start_changeset(self):
second_id = self.repo.revisions[1]
changesets = list(self.repo.get_changesets(start=second_id))
assert changesets[0].raw_id == second_id
def test_get_changesets_respects_end(self):
second_id = self.repo.revisions[1]
changesets = list(self.repo.get_changesets(end=second_id))
assert changesets[-1].raw_id == second_id
assert len(changesets) == 2
def test_get_changesets_numerical_id_respects_end(self):
second_id = 1
changesets = list(self.repo.get_changesets(end=second_id))
assert changesets.index(changesets[-1]) == second_id
assert len(changesets) == 2
def test_get_changesets_respects_both_start_and_end(self):
second_id = self.repo.revisions[1]
third_id = self.repo.revisions[2]
changesets = list(self.repo.get_changesets(start=second_id,
end=third_id))
assert len(changesets) == 2
def test_get_changesets_numerical_id_respects_both_start_and_end(self):
changesets = list(self.repo.get_changesets(start=2, end=3))
assert len(changesets) == 2
def test_get_changesets_on_empty_repo_raises_EmptyRepository_error(self):
Backend = self.get_backend()
repo_path = get_new_dir(str(time.time()))
repo = Backend(repo_path, create=True)
with pytest.raises(EmptyRepositoryError):
list(repo.get_changesets(start='foobar'))
def test_get_changesets_includes_end_changeset(self):
second_id = self.repo.revisions[1]
changesets = list(self.repo.get_changesets(end=second_id))
assert changesets[-1].raw_id == second_id
def test_get_changesets_respects_start_date(self):
start_date = datetime.datetime(2010, 2, 1)
for cs in self.repo.get_changesets(start_date=start_date):
assert cs.date >= start_date
def test_get_changesets_respects_end_date(self):
start_date = datetime.datetime(2010, 1, 1)
end_date = datetime.datetime(2010, 2, 1)
for cs in self.repo.get_changesets(start_date=start_date,
end_date=end_date):
assert cs.date >= start_date
assert cs.date <= end_date
def test_get_changesets_respects_start_date_and_end_date(self):
end_date = datetime.datetime(2010, 2, 1)
for cs in self.repo.get_changesets(end_date=end_date):
assert cs.date <= end_date
def test_get_changesets_respects_reverse(self):
changesets_id_list = [cs.raw_id for cs in
self.repo.get_changesets(reverse=True)]
assert changesets_id_list == list(reversed(self.repo.revisions))
def test_get_filenodes_generator(self):
tip = self.repo.get_changeset()
filepaths = [node.path for node in tip.get_filenodes_generator()]
assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
def test_size(self):
tip = self.repo.get_changeset()
size = 5 * len('Foobar N') # Size of 5 files
assert tip.size == size
def test_author(self):
tip = self.repo.get_changeset()
assert tip.author == u'Joe Doe <joe.doe@example.com>'
def test_author_name(self):
tip = self.repo.get_changeset()
assert tip.author_name == u'Joe Doe'
def test_author_email(self):
tip = self.repo.get_changeset()
assert tip.author_email == u'joe.doe@example.com'
def test_get_changesets_raise_changesetdoesnotexist_for_wrong_start(self):
with pytest.raises(ChangesetDoesNotExistError):
list(self.repo.get_changesets(start='foobar'))
def test_get_changesets_raise_changesetdoesnotexist_for_wrong_end(self):
with pytest.raises(ChangesetDoesNotExistError):
list(self.repo.get_changesets(end='foobar'))
def test_get_changesets_raise_branchdoesnotexist_for_wrong_branch_name(self):
with pytest.raises(BranchDoesNotExistError):
list(self.repo.get_changesets(branch_name='foobar'))
def test_get_changesets_raise_repositoryerror_for_wrong_start_end(self):
start = self.repo.revisions[-1]
end = self.repo.revisions[0]
with pytest.raises(RepositoryError):
list(self.repo.get_changesets(start=start, end=end))
def test_get_changesets_numerical_id_reversed(self):
with pytest.raises(RepositoryError):
[x for x in self.repo.get_changesets(start=3, end=2)]
def test_get_changesets_numerical_id_respects_both_start_and_end_last(self):
with pytest.raises(RepositoryError):
last = len(self.repo.revisions)
list(self.repo.get_changesets(start=last-1, end=last-2))
def test_get_changesets_numerical_id_last_zero_error(self):
with pytest.raises(RepositoryError):
last = len(self.repo.revisions)
list(self.repo.get_changesets(start=last-1, end=0))
class _ChangesetsChangesTestCaseMixin(_BackendTestMixin):
recreate_repo_per_test = False
@classmethod
def _get_commits(cls):
return [
{
'message': u'Initial',
'author': u'Joe Doe <joe.doe@example.com>',
'date': datetime.datetime(2010, 1, 1, 20),
'added': [
FileNode('foo/bar', content='foo'),
FileNode('foo/bał', content='foo'),
FileNode('foobar', content='foo'),
FileNode('qwe', content='foo'),
],
},
{
'message': u'Massive changes',
'author': u'Joe Doe <joe.doe@example.com>',
'date': datetime.datetime(2010, 1, 1, 22),
'added': [FileNode('fallout', content='War never changes')],
'changed': [
FileNode('foo/bar', content='baz'),
FileNode('foobar', content='baz'),
],
'removed': [FileNode('qwe')],
},
]
def test_initial_commit(self):
changeset = self.repo.get_changeset(0)
assert sorted(list(changeset.added)) == sorted([
changeset.get_node('foo/bar'),
changeset.get_node('foo/bał'),
changeset.get_node('foobar'),
changeset.get_node('qwe'),
])
assert list(changeset.changed) == []
assert list(changeset.removed) == []
assert u'foo/ba\u0142' in changeset.as_dict()['added']
assert u'foo/ba\u0142' in changeset.__json__(with_file_list=True)['added']
def test_head_added(self):
changeset = self.repo.get_changeset()
assert isinstance(changeset.added, AddedFileNodesGenerator)
assert list(changeset.added) == [
changeset.get_node('fallout'),
]
assert isinstance(changeset.changed, ChangedFileNodesGenerator)
assert list(changeset.changed) == [
changeset.get_node('foo/bar'),
changeset.get_node('foobar'),
]
assert isinstance(changeset.removed, RemovedFileNodesGenerator)
assert len(changeset.removed) == 1
assert list(changeset.removed)[0].path == 'qwe'
def test_get_filemode(self):
changeset = self.repo.get_changeset()
assert 33188 == changeset.get_file_mode('foo/bar')
def test_get_filemode_non_ascii(self):
changeset = self.repo.get_changeset()
assert 33188 == changeset.get_file_mode('foo/bał')
assert 33188 == changeset.get_file_mode(u'foo/bał')
# For each backend create test case class
for alias in SCM_TESTS:
attrs = {
'backend_alias': alias,
}
# tests with additional commits
cls_name = 'Test' + alias.title() + 'ChangesetsWithCommits'
globals()[cls_name] = type(cls_name, (_ChangesetsWithCommitsTestCaseixin,), attrs)
# tests without additional commits
cls_name = 'Test' + alias.title() + 'Changesets'
globals()[cls_name] = type(cls_name, (_ChangesetsTestCaseMixin,), attrs)
# tests changes
cls_name = 'Test' + alias.title() + 'ChangesetsChanges'
globals()[cls_name] = type(cls_name, (_ChangesetsChangesTestCaseMixin,), attrs)