# Copyright (c) 2012, 2013, 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for the 'import' command""" import inspect from string import lower import mock from testtools.matchers import Equals from git_upstream.lib.pygitcompat import Commit from git_upstream import main from git_upstream.tests.base import BaseTestCase class SubstringMatcher(object): def __init__(self, containing): self.containing = lower(containing) def __eq__(self, other): return lower(other).find(self.containing) > -1 def __unicode__(self): return 'a string containing "%s"' % self.containing def __str__(self): return unicode(self).encode('utf-8') __repr__ = __unicode__ class TestImportCommand(BaseTestCase): commands, parser = main.build_parsers() def test_basic(self): """Test that default behaviour and options work Repository layout being checked (assumed already replayed) C---D local/master / A---B---E---F upstream/master """ tree = [ ('A', []), ('B', ['A']), ('C', ['B']), ('D', ['C']), ('E', ['B']), ('F', ['E']) ] branches = { 'head': ('master', 'D'), 'upstream': ('upstream/master', 'F') } self._build_git_tree(tree, branches.values()) self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master') args = self.parser.parse_args(['-q', 'import', 'upstream/master']) self.assertThat(args.cmd.run(args), Equals(True), "import command failed to complete successfully") def test_basic_additional(self): """Test that default behaviour and options work Repository layout being checked (assumed already replayed) C---D packaging/master \ E---F local/master / A---B---G---H upstream/master """ tree = [ ('A', []), ('B', ['A']), ('C', []), ('D', ['C']), ('E', ['B', 'D']), ('F', ['E']), ('G', ['B']), ('H', ['G']) ] branches = { 'head': ('master', 'F'), 'upstream': ('upstream/master', 'H'), 'packaging': ('packaging/master', 'D') } self._build_git_tree(tree, branches.values()) self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master') args = self.parser.parse_args(['-q', 'import', 'upstream/master', 'packaging/master']) self.assertThat(args.cmd.run(args), Equals(True), "import command failed to complete successfully") def test_basic_additional_missed(self): """Test that forgetting an additional branch that was previously included results in a warning to the user. Repository layout being checked (assumed already replayed) C---D packaging/master \ E---F local/master / A---B---G---H upstream/master """ tree = [ ('A', []), ('B', ['A']), ('C', []), ('D', ['C']), ('E', ['B', 'D']), ('F', ['E']), ('G', ['B']), ('H', ['G']) ] branches = { 'head': ('master', 'F'), 'upstream': ('upstream/master', 'H'), 'packaging': ('packaging/master', 'D') } self._build_git_tree(tree, branches.values()) self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master') args = self.parser.parse_args(['import', 'upstream/master']) with mock.patch.object(args.cmd.log, 'warning') as mock_logger: self.assertThat(args.cmd.run(args), Equals(True), "import command failed to complete successfully") mock_logger.assert_called_with( SubstringMatcher( containing="Previous import merged additional")) def test_import_switch_branches_search(self): """Test that the import sub-command can correctly switch branches when importing from upstream when given a usable search-ref. Repository layout being checked (assumed already replayed) E---F local/master / C---D upstream/stable / A---B---G---H upstream/master New branch to be tracked will be upstream/master, so the resulting commits found should just be E & F. Test that result is as follows E---F---I local/master / / C---D / upstream/stable / / / E1--F1 / / A---B---G---H upstream/master """ tree = [ ('A', []), ('B', ['A']), ('C', ['B']), ('D', ['C']), ('E', ['D']), ('F', ['E']), ('G', ['B']), ('H', ['G']) ] branches = { 'head': ('master', 'F'), 'upstream': ('upstream/master', 'H'), 'stable': ('upstream/stable', 'D') } self._build_git_tree(tree, branches.values()) self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master') args = self.parser.parse_args(['-q', 'import']) self.assertThat(args.cmd.run(args), Equals(True), "import command failed to complete successfully") changes = list(Commit.iter_items( self.repo, 'upstream/master..master^2')) self.assertThat(len(changes), Equals(2), "should only have seen two changes, got: %s" % ", ".join(["%s:%s" % (commit.hexsha, commit.message.splitlines()[0]) for commit in changes])) for commit, node in zip(changes, ['F', 'E']): subject = commit.message.splitlines()[0] node_subject = self._graph[node].message.splitlines()[0] self.assertThat(subject, Equals(node_subject), "subject '%s' of commit '%s' does not match " "subject '%s' of node '%s'" % ( subject, commit.hexsha, node_subject, node)) def test_import_switch_branches_fails_without_search_ref(self): """Test that the import sub-command finds additional changes when not given a search-ref to look under. Repository layout being checked (assumed already replayed) E---F local/master / C---D upstream/stable / A---B---G---H upstream/master New branch to be tracked will be upstream/master, so the resulting commits found will be C, D, E & F because of not telling the searcher to look under all of the namespace. Test that result is as follows E---F-------------I local/master / / C---D / upstream/stable / / / C1---D1---E1--F1 / / A---B---G---H upstream/master """ tree = [ ('A', []), ('B', ['A']), ('C', ['B']), ('D', ['C']), ('E', ['D']), ('F', ['E']), ('G', ['B']), ('H', ['G']) ] # use 'custom/*' to ensure defaults are overridden correctly branches = { 'head': ('master', 'F'), 'upstream': ('custom/master', 'H'), 'stable': ('custom/stable', 'D') } self._build_git_tree(tree, branches.values()) self.git.tag(inspect.currentframe().f_code.co_name, 'custom/master') args = self.parser.parse_args(['-q', 'import', '--into=master', 'custom/master']) self.assertThat(args.cmd.run(args), Equals(True), "import command failed to complete successfully") changes = list(Commit.iter_items( self.repo, 'custom/master..master^2')) local_rebased = ['F', 'E', 'D', 'C'] self.assertThat(len(changes), Equals(len(local_rebased)), "should only have seen two changes, got: %s" % ", ".join(["%s:%s" % (commit.hexsha, commit.message.splitlines()[0]) for commit in changes])) for commit, node in zip(changes, local_rebased): subject = commit.message.splitlines()[0] node_subject = self._graph[node].message.splitlines()[0] self.assertThat(subject, Equals(node_subject), "subject '%s' of commit '%s' does not match " "subject '%s' of node '%s'" % ( subject, commit.hexsha, node_subject, node)) def test_import_switch_branches_search_ref_custom_namespace(self): """Test that the import sub-command can correctly switch branches when importing from upstream when given a usable search-ref. Repository layout being checked (assumed already replayed) E---F local/master / C---D upstream/stable / A---B---G---H upstream/master New branch to be tracked will be upstream/master, so the resulting commits found should just be E & F. Test that result is as follows E---F---I local/master / / C---D / upstream/stable / / / E1--F1 / / A---B---G---H upstream/master """ tree = [ ('A', []), ('B', ['A']), ('C', ['B']), ('D', ['C']), ('E', ['D']), ('F', ['E']), ('G', ['B']), ('H', ['G']) ] # use 'custom/*' to ensure defaults are overridden correctly branches = { 'head': ('master', 'F'), 'upstream': ('custom/master', 'H'), 'stable': ('custom/stable', 'D') } self._build_git_tree(tree, branches.values()) self.git.tag(inspect.currentframe().f_code.co_name, 'custom/master') args = self.parser.parse_args(['-q', 'import', '--search-refs=custom/*', '--search-refs=custom-d/*', '--into=master', 'custom/master']) self.assertThat(args.cmd.run(args), Equals(True), "import command failed to complete successfully") changes = list(Commit.iter_items( self.repo, 'custom/master..master^2')) self.assertThat(len(changes), Equals(2), "should only have seen two changes, got: %s" % ", ".join(["%s:%s" % (commit.hexsha, commit.message.splitlines()[0]) for commit in changes])) for commit, node in zip(changes, ['F', 'E']): subject = commit.message.splitlines()[0] node_subject = self._graph[node].message.splitlines()[0] self.assertThat(subject, Equals(node_subject), "subject '%s' of commit '%s' does not match " "subject '%s' of node '%s'" % ( subject, commit.hexsha, node_subject, node)) def test_import_finish(self): """Test the --finish option to the import command Given manual intervention to resolve conflicts and complete the import, check that we can finish the import by manually requesting the merge to be performed by this tool. Repository layout being checked (assumed already replayed) C---D local/master / / C1---D1 import/F / / A---B---E---F upstream/master Test that result is as follows C---D------- G local/master / / / C1---D1 import/F / / A---B---E---F upstream/master """ tree = [ ('A', []), ('B', ['A']), ('C', ['B']), ('D', ['C']), ('E', ['B']), ('F', ['E']), ('C1', ['F']), ('D1', ['C1']) ] branches = { 'head': ('master', 'D'), 'upstream': ('upstream/master', 'F'), 'import': ('import/F', 'D1') } self._build_git_tree(tree, branches.values()) self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master') args = self.parser.parse_args(['-q', 'import', '--finish', '--import-branch=import/F', '--into=master', 'upstream/master']) self.assertThat(args.cmd.run(args), Equals(True), "import command failed to complete successfully") changes = list(Commit.iter_items( self.repo, 'upstream/master..master^2')) self.assertThat(len(changes), Equals(2), "should only have seen two changes, got: %s" % ", ".join(["%s:%s" % (commit.hexsha, commit.message.splitlines()[0]) for commit in changes])) self.assertThat(self.repo.git.rev_parse('master^{tree}'), Equals(self.repo.git.rev_parse('import/F^{tree}')), "--finish option failed to merge correctly") commit = self.git.rev_list('master', parents=True, max_count=1).split() parents = commit[1:] self.assertThat(parents, Equals([self._graph['D'].hexsha, self._graph['D1'].hexsha]), "import --finish merge does contain the correct " "parents")