|
|
diff --git a/vcs/backends/base.py b/vcs/backends/base.py
|
|
|
index 212267ca23949807b8d89fa8ca495827dcfab3b1..ad17f16634da602503ed4ddd7cdd2e1ccdf4bed4 100644
|
|
|
--- a/vcs/backends/base.py
|
|
|
+++ b/vcs/backends/base.py
|
|
|
@@ -54,6 +54,7 @@ class BaseRepository(object):
|
|
|
"""
|
|
|
scm = None
|
|
|
DEFAULT_BRANCH_NAME = None
|
|
|
+ EMPTY_CHANGESET = '0' * 40
|
|
|
|
|
|
def __init__(self, repo_path, create=False, **kwargs):
|
|
|
"""
|
|
|
@@ -204,6 +205,23 @@ class BaseRepository(object):
|
|
|
"""
|
|
|
raise NotImplementedError
|
|
|
|
|
|
+ def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
|
|
|
+ context=3):
|
|
|
+ """
|
|
|
+ Returns (git like) *diff*, as plain text. Shows changes introduced by
|
|
|
+ ``rev2`` since ``rev1``.
|
|
|
+
|
|
|
+ :param rev1: Entry point from which diff is shown. Can be
|
|
|
+ ``self.EMPTY_CHANGESET`` - in this case, patch showing all
|
|
|
+ the changes since empty state of the repository until ``rev2``
|
|
|
+ :param rev2: Until which revision changes should be shown.
|
|
|
+ :param ignore_whitespace: If set to ``True``, would not show whitespace
|
|
|
+ changes. Defaults to ``False``.
|
|
|
+ :param context: How many lines before/after changed lines should be
|
|
|
+ shown. Defaults to ``3``.
|
|
|
+ """
|
|
|
+ raise NotImplementedError
|
|
|
+
|
|
|
# ========== #
|
|
|
# COMMIT API #
|
|
|
# ========== #
|
|
|
@@ -341,7 +359,6 @@ class BaseChangeset(object):
|
|
|
otherwise; trying to access this attribute while there is no
|
|
|
changesets would raise ``EmptyRepositoryError``
|
|
|
"""
|
|
|
-
|
|
|
def __str__(self):
|
|
|
return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
|
|
|
self.short_id)
|
|
|
@@ -591,7 +608,6 @@ class BaseChangeset(object):
|
|
|
return data
|
|
|
|
|
|
|
|
|
-
|
|
|
class BaseWorkdir(object):
|
|
|
"""
|
|
|
Working directory representation of single repository.
|
|
|
diff --git a/vcs/backends/git/repository.py b/vcs/backends/git/repository.py
|
|
|
index 8b9d1247fdee44e7a021b80e4965d8609cfd5720..e9f04e74dedd2f57417eb91dd2f4f7c61ec7e097 100644
|
|
|
--- a/vcs/backends/git/repository.py
|
|
|
+++ b/vcs/backends/git/repository.py
|
|
|
@@ -12,6 +12,7 @@
|
|
|
import os
|
|
|
import re
|
|
|
import time
|
|
|
+import inspect
|
|
|
import posixpath
|
|
|
from dulwich.repo import Repo, NotGitRepository
|
|
|
#from dulwich.config import ConfigFile
|
|
|
@@ -101,21 +102,6 @@ class GitRepository(BaseRepository):
|
|
|
"stderr:\n%s" % (cmd, se))
|
|
|
return so, se
|
|
|
|
|
|
- def _get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
|
|
|
- context=3):
|
|
|
- rev1 = self._get_revision(rev1)
|
|
|
- rev2 = self._get_revision(rev2)
|
|
|
-
|
|
|
- if ignore_whitespace:
|
|
|
- cmd = 'diff -U%s -w %s %s' % (context, rev1, rev2)
|
|
|
- else:
|
|
|
- cmd = 'diff -U%s %s %s' % (context, rev1, rev2)
|
|
|
- if path:
|
|
|
- cmd += ' -- "%s"' % path
|
|
|
- so, se = self.run_git_command(cmd)
|
|
|
-
|
|
|
- return so
|
|
|
-
|
|
|
def _check_url(self, url):
|
|
|
"""
|
|
|
Functon will check given url and try to verify if it's a valid
|
|
|
@@ -322,6 +308,8 @@ class GitRepository(BaseRepository):
|
|
|
Returns ``GitChangeset`` object representing commit from git repository
|
|
|
at the given revision or head (most recent commit) if None given.
|
|
|
"""
|
|
|
+ if isinstance(revision, GitChangeset):
|
|
|
+ return revision
|
|
|
revision = self._get_revision(revision)
|
|
|
changeset = GitChangeset(repository=self, revision=revision)
|
|
|
return changeset
|
|
|
@@ -398,6 +386,49 @@ class GitRepository(BaseRepository):
|
|
|
for rev in revs:
|
|
|
yield self.get_changeset(rev)
|
|
|
|
|
|
+ def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
|
|
|
+ context=3):
|
|
|
+ """
|
|
|
+ Returns (git like) *diff*, as plain text. Shows changes introduced by
|
|
|
+ ``rev2`` since ``rev1``.
|
|
|
+
|
|
|
+ :param rev1: Entry point from which diff is shown. Can be
|
|
|
+ ``self.EMPTY_CHANGESET`` - in this case, patch showing all
|
|
|
+ the changes since empty state of the repository until ``rev2``
|
|
|
+ :param rev2: Until which revision changes should be shown.
|
|
|
+ :param ignore_whitespace: If set to ``True``, would not show whitespace
|
|
|
+ changes. Defaults to ``False``.
|
|
|
+ :param context: How many lines before/after changed lines should be
|
|
|
+ shown. Defaults to ``3``.
|
|
|
+ """
|
|
|
+ flags = ['-U%s' % context]
|
|
|
+ if ignore_whitespace:
|
|
|
+ flags.append('-w')
|
|
|
+
|
|
|
+ if rev1 == self.EMPTY_CHANGESET:
|
|
|
+ rev2 = self.get_changeset(rev2).raw_id
|
|
|
+ cmd = ' '.join(['show'] + flags + [rev2])
|
|
|
+ else:
|
|
|
+ rev1 = self.get_changeset(rev1).raw_id
|
|
|
+ rev2 = self.get_changeset(rev2).raw_id
|
|
|
+ cmd = ' '.join(['diff'] + flags + [rev1, rev2])
|
|
|
+
|
|
|
+ if path:
|
|
|
+ cmd += ' -- "%s"' % path
|
|
|
+ stdout, stderr = self.run_git_command(cmd)
|
|
|
+ # If we used 'show' command, strip first few lines (until actual diff
|
|
|
+ # starts)
|
|
|
+ if rev1 == self.EMPTY_CHANGESET:
|
|
|
+ lines = stdout.splitlines()
|
|
|
+ x = 0
|
|
|
+ for line in lines:
|
|
|
+ if line.startswith('diff'):
|
|
|
+ break
|
|
|
+ x += 1
|
|
|
+ # Append new line just like 'diff' command do
|
|
|
+ stdout = '\n'.join(lines[x:]) + '\n'
|
|
|
+ return stdout
|
|
|
+
|
|
|
@LazyProperty
|
|
|
def in_memory_changeset(self):
|
|
|
"""
|
|
|
diff --git a/vcs/backends/hg.py b/vcs/backends/hg.py
|
|
|
index f1f9f95e4d476ab01d8e7b02a8b59034c0740a3b..b7d63c552c39b2f8aaec17ef46551369c8b8e793 100644
|
|
|
--- a/vcs/backends/hg.py
|
|
|
+++ b/vcs/backends/hg.py
|
|
|
@@ -256,13 +256,32 @@ class MercurialRepository(BaseRepository):
|
|
|
|
|
|
return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
|
|
|
|
|
|
- def _get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
|
|
|
+ def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
|
|
|
context=3):
|
|
|
+ """
|
|
|
+ Returns (git like) *diff*, as plain text. Shows changes introduced by
|
|
|
+ ``rev2`` since ``rev1``.
|
|
|
+
|
|
|
+ :param rev1: Entry point from which diff is shown. Can be
|
|
|
+ ``self.EMPTY_CHANGESET`` - in this case, patch showing all
|
|
|
+ the changes since empty state of the repository until ``rev2``
|
|
|
+ :param rev2: Until which revision changes should be shown.
|
|
|
+ :param ignore_whitespace: If set to ``True``, would not show whitespace
|
|
|
+ changes. Defaults to ``False``.
|
|
|
+ :param context: How many lines before/after changed lines should be
|
|
|
+ shown. Defaults to ``3``.
|
|
|
+ """
|
|
|
+ # Check if given revisions are present at repository (may raise
|
|
|
+ # ChangesetDoesNotExistError)
|
|
|
+ if rev1 != self.EMPTY_CHANGESET:
|
|
|
+ self.get_changeset(rev1)
|
|
|
+ self.get_changeset(rev2)
|
|
|
+
|
|
|
file_filter = match(self.path, '', [path])
|
|
|
- return patch.diff(self._repo, rev1, rev2, match=file_filter,
|
|
|
+ return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
|
|
|
opts=diffopts(git=True,
|
|
|
ignorews=ignore_whitespace,
|
|
|
- context=context))
|
|
|
+ context=context)))
|
|
|
|
|
|
def _check_url(self, url):
|
|
|
"""
|
|
|
diff --git a/vcs/tests/test_git.py b/vcs/tests/test_git.py
|
|
|
index 30da035a2a35c3dca14064778e97188b6d4ce5d6..d4b82b9e612af8bb5bf490a827377c7c2567735a 100644
|
|
|
--- a/vcs/tests/test_git.py
|
|
|
+++ b/vcs/tests/test_git.py
|
|
|
@@ -639,19 +639,19 @@ class GitSpecificWithRepoTest(BackendTestMixin, unittest.TestCase):
|
|
|
|
|
|
def test_get_diff_runs_git_command_with_hashes(self):
|
|
|
self.repo.run_git_command = mock.Mock(return_value=['', ''])
|
|
|
- self.repo._get_diff(0, 1)
|
|
|
+ self.repo.get_diff(0, 1)
|
|
|
self.repo.run_git_command.assert_called_once_with('diff -U%s %s %s' %
|
|
|
(3, self.repo._get_revision(0), self.repo._get_revision(1)))
|
|
|
|
|
|
def test_get_diff_runs_git_command_with_str_hashes(self):
|
|
|
self.repo.run_git_command = mock.Mock(return_value=['', ''])
|
|
|
- self.repo._get_diff('0' * 40, 1)
|
|
|
- self.repo.run_git_command.assert_called_once_with('diff -U%s %s %s' %
|
|
|
- (3, self.repo._get_revision(0), self.repo._get_revision(1)))
|
|
|
+ self.repo.get_diff(self.repo.EMPTY_CHANGESET, 1)
|
|
|
+ self.repo.run_git_command.assert_called_once_with('show -U%s %s' %
|
|
|
+ (3, self.repo._get_revision(1)))
|
|
|
|
|
|
def test_get_diff_runs_git_command_with_path_if_its_given(self):
|
|
|
self.repo.run_git_command = mock.Mock(return_value=['', ''])
|
|
|
- self.repo._get_diff(0, 1, 'foo')
|
|
|
+ self.repo.get_diff(0, 1, 'foo')
|
|
|
self.repo.run_git_command.assert_called_once_with('diff -U%s %s %s -- "foo"'
|
|
|
% (3, self.repo._get_revision(0), self.repo._get_revision(1)))
|
|
|
|
|
|
diff --git a/vcs/tests/test_repository.py b/vcs/tests/test_repository.py
|
|
|
index e34033e29fa9b3d3366b723beab129cee73869b9..b6e3f419778d6009229e9108824acaf83eea1784 100644
|
|
|
--- a/vcs/tests/test_repository.py
|
|
|
+++ b/vcs/tests/test_repository.py
|
|
|
@@ -1,9 +1,12 @@
|
|
|
from __future__ import with_statement
|
|
|
+import datetime
|
|
|
from base import BackendTestMixin
|
|
|
from conf import SCM_TESTS
|
|
|
+from conf import TEST_USER_CONFIG_FILE
|
|
|
+from vcs.nodes import FileNode
|
|
|
from vcs.utils.compat import unittest
|
|
|
+from vcs.exceptions import ChangesetDoesNotExistError
|
|
|
|
|
|
-from conf import TEST_USER_CONFIG_FILE
|
|
|
|
|
|
class RepositoryBaseTest(BackendTestMixin):
|
|
|
recreate_repo_per_test = False
|
|
|
@@ -29,6 +32,176 @@ class RepositoryBaseTest(BackendTestMixin):
|
|
|
'foo.bar@example.com')
|
|
|
|
|
|
|
|
|
+
|
|
|
+class RepositoryGetDiffTest(BackendTestMixin):
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def _get_commits(cls):
|
|
|
+ commits = [
|
|
|
+ {
|
|
|
+ 'message': 'Initial commit',
|
|
|
+ 'author': 'Joe Doe <joe.doe@example.com>',
|
|
|
+ 'date': datetime.datetime(2010, 1, 1, 20),
|
|
|
+ 'added': [
|
|
|
+ FileNode(b'foobar', content='foobar'),
|
|
|
+ FileNode(b'foobar2', content='foobar2'),
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'message': 'Changed foobar, added foobar3',
|
|
|
+ 'author': 'Jane Doe <jane.doe@example.com>',
|
|
|
+ 'date': datetime.datetime(2010, 1, 1, 21),
|
|
|
+ 'added': [
|
|
|
+ FileNode(b'foobar3', content='foobar3'),
|
|
|
+ ],
|
|
|
+ 'changed': [
|
|
|
+ FileNode(b'foobar', 'FOOBAR'),
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'message': 'Removed foobar, changed foobar3',
|
|
|
+ 'author': 'Jane Doe <jane.doe@example.com>',
|
|
|
+ 'date': datetime.datetime(2010, 1, 1, 22),
|
|
|
+ 'changed': [
|
|
|
+ FileNode(b'foobar3', content='FOOBAR\nFOOBAR\nFOOBAR\n'),
|
|
|
+ ],
|
|
|
+ 'removed': [FileNode(b'foobar')],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ return commits
|
|
|
+
|
|
|
+ def test_raise_for_wrong(self):
|
|
|
+ with self.assertRaises(ChangesetDoesNotExistError):
|
|
|
+ self.repo.get_diff('a' * 40, 'b' * 40)
|
|
|
+
|
|
|
+class GitRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase):
|
|
|
+ backend_alias = 'git'
|
|
|
+
|
|
|
+ def test_initial_commit_diff(self):
|
|
|
+ initial_rev = self.repo.revisions[0]
|
|
|
+ self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar
|
|
|
+new file mode 100644
|
|
|
+index 0000000..f6ea049
|
|
|
+--- /dev/null
|
|
|
++++ b/foobar
|
|
|
+@@ -0,0 +1 @@
|
|
|
++foobar
|
|
|
+\ No newline at end of file
|
|
|
+diff --git a/foobar2 b/foobar2
|
|
|
+new file mode 100644
|
|
|
+index 0000000..e8c9d6b
|
|
|
+--- /dev/null
|
|
|
++++ b/foobar2
|
|
|
+@@ -0,0 +1 @@
|
|
|
++foobar2
|
|
|
+\ No newline at end of file
|
|
|
+''')
|
|
|
+
|
|
|
+ def test_second_changeset_diff(self):
|
|
|
+ revs = self.repo.revisions
|
|
|
+ self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar
|
|
|
+index f6ea049..389865b 100644
|
|
|
+--- a/foobar
|
|
|
++++ b/foobar
|
|
|
+@@ -1 +1 @@
|
|
|
+-foobar
|
|
|
+\ No newline at end of file
|
|
|
++FOOBAR
|
|
|
+\ No newline at end of file
|
|
|
+diff --git a/foobar3 b/foobar3
|
|
|
+new file mode 100644
|
|
|
+index 0000000..c11c37d
|
|
|
+--- /dev/null
|
|
|
++++ b/foobar3
|
|
|
+@@ -0,0 +1 @@
|
|
|
++foobar3
|
|
|
+\ No newline at end of file
|
|
|
+''')
|
|
|
+
|
|
|
+ def test_third_changeset_diff(self):
|
|
|
+ revs = self.repo.revisions
|
|
|
+ self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar
|
|
|
+deleted file mode 100644
|
|
|
+index 389865b..0000000
|
|
|
+--- a/foobar
|
|
|
++++ /dev/null
|
|
|
+@@ -1 +0,0 @@
|
|
|
+-FOOBAR
|
|
|
+\ No newline at end of file
|
|
|
+diff --git a/foobar3 b/foobar3
|
|
|
+index c11c37d..f932447 100644
|
|
|
+--- a/foobar3
|
|
|
++++ b/foobar3
|
|
|
+@@ -1 +1,3 @@
|
|
|
+-foobar3
|
|
|
+\ No newline at end of file
|
|
|
++FOOBAR
|
|
|
++FOOBAR
|
|
|
++FOOBAR
|
|
|
+''')
|
|
|
+
|
|
|
+
|
|
|
+class HgRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase):
|
|
|
+ backend_alias = 'hg'
|
|
|
+
|
|
|
+ def test_initial_commit_diff(self):
|
|
|
+ initial_rev = self.repo.revisions[0]
|
|
|
+ self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar
|
|
|
+new file mode 100755
|
|
|
+--- /dev/null
|
|
|
++++ b/foobar
|
|
|
+@@ -0,0 +1,1 @@
|
|
|
++foobar
|
|
|
+\ No newline at end of file
|
|
|
+diff --git a/foobar2 b/foobar2
|
|
|
+new file mode 100755
|
|
|
+--- /dev/null
|
|
|
++++ b/foobar2
|
|
|
+@@ -0,0 +1,1 @@
|
|
|
++foobar2
|
|
|
+\ No newline at end of file
|
|
|
+''')
|
|
|
+
|
|
|
+ def test_second_changeset_diff(self):
|
|
|
+ revs = self.repo.revisions
|
|
|
+ self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar
|
|
|
+--- a/foobar
|
|
|
++++ b/foobar
|
|
|
+@@ -1,1 +1,1 @@
|
|
|
+-foobar
|
|
|
+\ No newline at end of file
|
|
|
++FOOBAR
|
|
|
+\ No newline at end of file
|
|
|
+diff --git a/foobar3 b/foobar3
|
|
|
+new file mode 100755
|
|
|
+--- /dev/null
|
|
|
++++ b/foobar3
|
|
|
+@@ -0,0 +1,1 @@
|
|
|
++foobar3
|
|
|
+\ No newline at end of file
|
|
|
+''')
|
|
|
+
|
|
|
+ def test_third_changeset_diff(self):
|
|
|
+ revs = self.repo.revisions
|
|
|
+ self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar
|
|
|
+deleted file mode 100755
|
|
|
+--- a/foobar
|
|
|
++++ /dev/null
|
|
|
+@@ -1,1 +0,0 @@
|
|
|
+-FOOBAR
|
|
|
+\ No newline at end of file
|
|
|
+diff --git a/foobar3 b/foobar3
|
|
|
+--- a/foobar3
|
|
|
++++ b/foobar3
|
|
|
+@@ -1,1 +1,3 @@
|
|
|
+-foobar3
|
|
|
+\ No newline at end of file
|
|
|
++FOOBAR
|
|
|
++FOOBAR
|
|
|
++FOOBAR
|
|
|
+''')
|
|
|
+
|
|
|
+
|
|
|
# For each backend create test case class
|
|
|
for alias in SCM_TESTS:
|
|
|
attrs = {
|
|
|
@@ -38,7 +211,6 @@ for alias in SCM_TESTS:
|
|
|
bases = (RepositoryBaseTest, unittest.TestCase)
|
|
|
globals()[cls_name] = type(cls_name, bases, attrs)
|
|
|
|
|
|
-
|
|
|
if __name__ == '__main__':
|
|
|
unittest.main()
|
|
|
|