diff --git a/rhodecode/lib/vcs/backends/git/changeset.py b/rhodecode/lib/vcs/backends/git/changeset.py --- a/rhodecode/lib/vcs/backends/git/changeset.py +++ b/rhodecode/lib/vcs/backends/git/changeset.py @@ -104,7 +104,7 @@ class GitChangeset(BaseChangeset): return path def _get_id_for_path(self, path): - + path = safe_str(path) # FIXME: Please, spare a couple of minutes and make those codes cleaner; if not path in self._paths: path = path.strip('/') @@ -154,7 +154,7 @@ class GitChangeset(BaseChangeset): if not path in self._paths: raise NodeDoesNotExistError("There is no file nor directory " "at the given path '%s' at revision %s" - % (path, self.short_id)) + % (path, safe_str(self.short_id))) return self._paths[path] def _get_kind(self, path): @@ -254,6 +254,7 @@ class GitChangeset(BaseChangeset): Returns stat mode of the file at the given ``path``. """ # ensure path is traversed + path = safe_str(path) self._get_id_for_path(path) return self._stat_modes[path] diff --git a/rhodecode/lib/vcs/backends/git/inmemory.py b/rhodecode/lib/vcs/backends/git/inmemory.py --- a/rhodecode/lib/vcs/backends/git/inmemory.py +++ b/rhodecode/lib/vcs/backends/git/inmemory.py @@ -46,7 +46,7 @@ class GitInMemoryChangeset(BaseInMemoryC for node in self.added + self.changed: # Compute subdirs if needed dirpath, nodename = posixpath.split(node.path) - dirnames = dirpath and dirpath.split('/') or [] + dirnames = map(safe_str, dirpath and dirpath.split('/') or []) parent = commit_tree ancestors = [('', parent)] diff --git a/rhodecode/lib/vcs/nodes.py b/rhodecode/lib/vcs/nodes.py --- a/rhodecode/lib/vcs/nodes.py +++ b/rhodecode/lib/vcs/nodes.py @@ -16,7 +16,7 @@ import mimetypes from rhodecode.lib.vcs.backends.base import EmptyChangeset from rhodecode.lib.vcs.exceptions import NodeError, RemovedFileNodeError from rhodecode.lib.vcs.utils.lazy import LazyProperty -from rhodecode.lib.vcs.utils import safe_unicode +from rhodecode.lib.vcs.utils import safe_unicode, safe_str class NodeKind: @@ -100,8 +100,8 @@ class Node(object): def __init__(self, path, kind): if path.startswith('/'): raise NodeError("Cannot initialize Node objects with slash at " - "the beginning as only relative paths are supported") - self.path = path.rstrip('/') + "the beginning as only relative paths are supported") + self.path = safe_str(path.rstrip('/')) # we store paths as str if path == '' and kind != NodeKind.DIR: raise NodeError("Only DirNode and its subclasses may be " "initialized with empty path") diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py --- a/rhodecode/model/scm.py +++ b/rhodecode/model/scm.py @@ -580,8 +580,8 @@ class ScmModel(BaseModel): raise NonRelativePathError('%s is not an relative path' % f_path) if f_path: f_path = os.path.normpath(f_path) + content = nodes[f_path]['content'] f_path = safe_str(f_path) - content = nodes[f_path]['content'] # decoding here will force that we have proper encoded values # in any other case this will throw exceptions and deny commit if isinstance(content, (basestring,)): diff --git a/rhodecode/tests/vcs/test_changesets.py b/rhodecode/tests/vcs/test_changesets.py --- a/rhodecode/tests/vcs/test_changesets.py +++ b/rhodecode/tests/vcs/test_changesets.py @@ -1,3 +1,4 @@ +# encoding: utf8 from __future__ import with_statement import time @@ -302,6 +303,7 @@ class ChangesetsChangesTestCaseMixin(Bac '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'), ], @@ -323,6 +325,7 @@ class ChangesetsChangesTestCaseMixin(Bac changeset = self.repo.get_changeset(0) self.assertItemsEqual(changeset.added, [ changeset.get_node('foo/bar'), + changeset.get_node('foo/bał'), changeset.get_node('foobar'), changeset.get_node('qwe'), ]) @@ -344,6 +347,14 @@ class ChangesetsChangesTestCaseMixin(Bac self.assertEqual(len(changeset.removed), 1) self.assertEqual(list(changeset.removed)[0].path, 'qwe') + def test_get_filemode(self): + changeset = self.repo.get_changeset() + self.assertEqual(33188, changeset.get_file_mode('foo/bar')) + + def test_get_filemode_non_ascii(self): + changeset = self.repo.get_changeset() + self.assertEqual(33188, changeset.get_file_mode('foo/bał')) + self.assertEqual(33188, changeset.get_file_mode(u'foo/bał')) # For each backend create test case class for alias in SCM_TESTS: diff --git a/rhodecode/tests/vcs/test_inmemchangesets.py b/rhodecode/tests/vcs/test_inmemchangesets.py --- a/rhodecode/tests/vcs/test_inmemchangesets.py +++ b/rhodecode/tests/vcs/test_inmemchangesets.py @@ -1,3 +1,4 @@ +# encoding: utf8 """ Tests so called "in memory changesets" commit API of vcs. """ @@ -18,6 +19,7 @@ from rhodecode.lib.vcs.exceptions import from rhodecode.lib.vcs.nodes import DirNode from rhodecode.lib.vcs.nodes import FileNode from rhodecode.lib.vcs.utils.compat import unittest +from rhodecode.lib.vcs.utils import safe_unicode class InMemoryChangesetTestMixin(object): @@ -112,6 +114,28 @@ class InMemoryChangesetTestMixin(object) self.assertEqual(changeset.get_node('foobar/foobaz/file').content, 'foo') self.assertEqual(changeset.get_node('foobar/barbaz').content, 'foo') + def test_add_non_ascii_files(self): + rev_count = len(self.repo.revisions) + to_add = [ + FileNode('żółwik/zwierzątko', content='ćććć'), + FileNode(u'żółwik/zwierzątko_uni', content=u'ćććć'), + ] + for node in to_add: + self.imc.add(node) + message = u'Added: %s' % ', '.join((node.path for node in self.nodes)) + author = unicode(self.__class__) + changeset = self.imc.commit(message=message, author=author) + + newtip = self.repo.get_changeset() + self.assertEqual(changeset, newtip) + self.assertEqual(rev_count + 1, len(self.repo.revisions)) + self.assertEqual(newtip.message, message) + self.assertEqual(newtip.author, author) + self.assertTrue(not any((self.imc.added, self.imc.changed, + self.imc.removed))) + for node in to_add: + self.assertEqual(newtip.get_node(node.path).content, node.content) + def test_add_raise_already_added(self): node = FileNode('foobar', content='baz') self.imc.add(node) @@ -140,7 +164,37 @@ class InMemoryChangesetTestMixin(object) self.assertNotEqual(tip, newtip) self.assertNotEqual(tip.id, newtip.id) self.assertEqual(newtip.get_node('foo/bar/baz').content, - 'My **changed** content') + 'My **changed** content') + + def test_change_non_ascii(self): + to_add = [ + FileNode('żółwik/zwierzątko', content='ćććć'), + FileNode(u'żółwik/zwierzątko_uni', content=u'ćććć'), + ] + for node in to_add: + self.imc.add(node) + + tip = self.imc.commit(u'Initial', u'joe.doe@example.com') + + # Change node's content + node = FileNode('żółwik/zwierzątko', content='My **changed** content') + self.imc.change(node) + self.imc.commit(u'Changed %s' % safe_unicode(node.path), + u'joe.doe@example.com') + + node = FileNode(u'żółwik/zwierzątko_uni', content=u'My **changed** content') + self.imc.change(node) + self.imc.commit(u'Changed %s' % safe_unicode(node.path), + u'joe.doe@example.com') + + newtip = self.repo.get_changeset() + self.assertNotEqual(tip, newtip) + self.assertNotEqual(tip.id, newtip.id) + + self.assertEqual(newtip.get_node('żółwik/zwierzątko').content, + 'My **changed** content') + self.assertEqual(newtip.get_node('żółwik/zwierzątko_uni').content, + 'My **changed** content') def test_change_raise_empty_repository(self): node = FileNode('foobar')