def test(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual(set(['README']), self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as readme: readme.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual(set(['README']), self.git.changed_files()) self.assertEqual(set(['README', 'INSTALL']), self.git.changed_files(include_untracked=True)) try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.commit('API Changes.') except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README')) as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual(set(['README', 'CHANGES']), git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found')
def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({'README'}, self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with self.assertRaises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote('origin') as origin_uri: # We shouldn't be fooled by remotes with origin in their name. with self.mkremote('temp_origin'): origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as fp: fp.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual({'README'}, self.git.changed_files()) self.assertEqual({'README', 'INSTALL'}, self.git.changed_files(include_untracked=True)) # Confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to='non-existent')) self.git.commit('API Changes.') try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call( ['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date('HEAD')) self.assertEqual(self.git.commit_date('HEAD'), self.git.commit_date('HEAD')) self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README'), 'r') as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual({'README', 'CHANGES'}, git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found')
class GitTest(unittest.TestCase): @staticmethod def init_repo(remote_name, remote): # TODO (peiyu) clean this up, use `git_util.initialize_repo`. subprocess.check_call(['git', 'init']) subprocess.check_call( ['git', 'config', 'user.email', '*****@*****.**']) subprocess.check_call(['git', 'config', 'user.name', 'Your Name']) subprocess.check_call(['git', 'config', 'commit.gpgSign', 'false']) subprocess.check_call(['git', 'remote', 'add', remote_name, remote]) def setUp(self): self.origin = safe_mkdtemp() with pushd(self.origin): subprocess.check_call(['git', 'init', '--bare']) self.gitdir = safe_mkdtemp() self.worktree = safe_mkdtemp() self.readme_file = os.path.join(self.worktree, 'README') with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.init_repo('depot', self.origin) touch(self.readme_file) subprocess.check_call(['git', 'add', 'README']) safe_mkdir(os.path.join(self.worktree, 'dir')) with open(os.path.join(self.worktree, 'dir', 'f'), 'w') as f: f.write("file in subdir") # Make some symlinks os.symlink('f', os.path.join(self.worktree, 'dir', 'relative-symlink')) os.symlink( 'no-such-file', os.path.join(self.worktree, 'dir', 'relative-nonexistent')) os.symlink( 'dir/f', os.path.join(self.worktree, 'dir', 'not-absolute\u2764')) os.symlink('../README', os.path.join(self.worktree, 'dir', 'relative-dotdot')) os.symlink('dir', os.path.join(self.worktree, 'link-to-dir')) os.symlink('README/f', os.path.join(self.worktree, 'not-a-dir')) os.symlink('loop1', os.path.join(self.worktree, 'loop2')) os.symlink('loop2', os.path.join(self.worktree, 'loop1')) subprocess.check_call([ 'git', 'add', 'README', 'dir', 'loop1', 'loop2', 'link-to-dir', 'not-a-dir' ]) subprocess.check_call([ 'git', 'commit', '-am', 'initial commit with decode -> \x81b' ]) self.initial_rev = subprocess.check_output( ['git', 'rev-parse', 'HEAD']).strip() subprocess.check_call(['git', 'tag', 'first']) subprocess.check_call(['git', 'push', '--tags', 'depot', 'master']) subprocess.check_call( ['git', 'branch', '--set-upstream-to', 'depot/master']) with safe_open(self.readme_file, 'wb') as readme: readme.write('Hello World.\u2764'.encode()) subprocess.check_call(['git', 'commit', '-am', 'Update README.']) self.current_rev = subprocess.check_output( ['git', 'rev-parse', 'HEAD']).strip() self.clone2 = safe_mkdtemp() with pushd(self.clone2): self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) with safe_open(os.path.realpath('README'), 'a') as readme: readme.write('--') subprocess.check_call(['git', 'commit', '-am', 'Update README 2.']) subprocess.check_call( ['git', 'push', '--tags', 'origin', 'master']) self.git = Git(gitdir=self.gitdir, worktree=self.worktree) @contextmanager def mkremote(self, remote_name): with temporary_dir() as remote_uri: subprocess.check_call( ['git', 'remote', 'add', remote_name, remote_uri]) try: yield remote_uri finally: subprocess.check_call(['git', 'remote', 'remove', remote_name]) def tearDown(self): safe_rmtree(self.origin) safe_rmtree(self.gitdir) safe_rmtree(self.worktree) safe_rmtree(self.clone2) def test_listdir(self): reader = self.git.repo_reader(self.initial_rev) for dirname in '.', './.': results = reader.listdir(dirname) self.assertEqual([ b'README', b'dir', b'link-to-dir', b'loop1', b'loop2', b'not-a-dir' ], sorted(results)) for dirname in 'dir', './dir': results = reader.listdir(dirname) self.assertEqual([ b'f', 'not-absolute\u2764'.encode(), b'relative-dotdot', b'relative-nonexistent', b'relative-symlink' ], sorted(results)) results = reader.listdir('link-to-dir') self.assertEqual([ b'f', 'not-absolute\u2764'.encode(), b'relative-dotdot', b'relative-nonexistent', b'relative-symlink' ], sorted(results)) with self.assertRaises(reader.MissingFileException): with reader.listdir('bogus'): pass def test_lstat(self): reader = self.git.repo_reader(self.initial_rev) def lstat(*components): return type(reader.lstat(os.path.join(*components))) self.assertEqual(reader.Symlink, lstat('dir', 'relative-symlink')) self.assertEqual(reader.Symlink, lstat('not-a-dir')) self.assertEqual(reader.File, lstat('README')) self.assertEqual(reader.Dir, lstat('dir')) self.assertEqual(type(None), lstat('nope-not-here')) def test_readlink(self): reader = self.git.repo_reader(self.initial_rev) def readlink(*components): return reader.readlink(os.path.join(*components)) self.assertEqual('dir/f', readlink('dir', 'relative-symlink')) self.assertEqual(None, readlink('not-a-dir')) self.assertEqual(None, readlink('README')) self.assertEqual(None, readlink('dir')) self.assertEqual(None, readlink('nope-not-here')) def test_open(self): reader = self.git.repo_reader(self.initial_rev) with reader.open('README') as f: self.assertEqual(b'', f.read()) with reader.open('dir/f') as f: self.assertEqual(b'file in subdir', f.read()) with self.assertRaises(reader.MissingFileException): with reader.open('no-such-file') as f: self.assertEqual(b'', f.read()) with self.assertRaises(reader.MissingFileException): with reader.open('dir/no-such-file') as f: pass with self.assertRaises(reader.IsDirException): with reader.open('dir') as f: self.assertEqual(b'', f.read()) current_reader = self.git.repo_reader(self.current_rev) with current_reader.open('README') as f: self.assertEqual('Hello World.\u2764'.encode(), f.read()) with current_reader.open('link-to-dir/f') as f: self.assertEqual(b'file in subdir', f.read()) with current_reader.open('dir/relative-symlink') as f: self.assertEqual(b'file in subdir', f.read()) with self.assertRaises(current_reader.SymlinkLoopException): with current_reader.open('loop1') as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open('dir/relative-nonexistent') as f: pass with self.assertRaises(current_reader.NotADirException): with current_reader.open('not-a-dir') as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open('dir/not-absolute\u2764') as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open('dir/relative-nonexistent') as f: pass with current_reader.open('dir/relative-dotdot') as f: self.assertEqual('Hello World.\u2764'.encode(), f.read()) def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({'README'}, self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with self.assertRaises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote('origin') as origin_uri: # We shouldn't be fooled by remotes with origin in their name. with self.mkremote('temp_origin'): origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as fp: fp.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual({'README'}, self.git.changed_files()) self.assertEqual({'README', 'INSTALL'}, self.git.changed_files(include_untracked=True)) # Confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to='non-existent')) self.git.commit('API Changes.') try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call( ['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date('HEAD')) self.assertEqual(self.git.commit_date('HEAD'), self.git.commit_date('HEAD')) self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README'), 'r') as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual({'README', 'CHANGES'}, git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found') def test_detect_worktree(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) def worktree_relative_to(cwd, expected): # Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'. orig_cwd = os.getcwd() try: abs_cwd = os.path.join(clone, cwd) if not os.path.isdir(abs_cwd): os.mkdir(abs_cwd) os.chdir(abs_cwd) actual = Git.detect_worktree() self.assertEqual(expected, actual) finally: os.chdir(orig_cwd) worktree_relative_to('..', None) worktree_relative_to('.', clone) worktree_relative_to('is', clone) worktree_relative_to('is/a', clone) worktree_relative_to('is/a/dir', clone) def test_detect_worktree_no_cwd(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) def worktree_relative_to(some_dir, expected): # Given a directory relative to the worktree, tests that the worktree is detected as 'expected'. subdir = os.path.join(clone, some_dir) if not os.path.isdir(subdir): os.mkdir(subdir) actual = Git.detect_worktree(subdir=subdir) self.assertEqual(expected, actual) worktree_relative_to('..', None) worktree_relative_to('.', clone) worktree_relative_to('is', clone) worktree_relative_to('is/a', clone) worktree_relative_to('is/a/dir', clone) @property def test_changes_in(self): """Test finding changes in a diffspecs To some extent this is just testing functionality of git not pants, since all pants says is that it will pass the diffspec to git diff-tree, but this should serve to at least document the functionality we belive works. """ with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), 'w') as fp: fp.write(content) subprocess.check_call(['git', 'add', '.']) subprocess.check_call( ['git', 'commit', '-m', f'change {files}']) return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() # We can get changes in HEAD or by SHA c1 = commit_contents_to_files('1', 'foo') self.assertEqual({'foo'}, self.git.changes_in('HEAD')) self.assertEqual({'foo'}, self.git.changes_in(c1)) # Changes in new HEAD, from old-to-new HEAD, in old HEAD, or from old-old-head to new. commit_contents_to_files('2', 'bar') self.assertEqual({'bar'}, self.git.changes_in('HEAD')) self.assertEqual({'bar'}, self.git.changes_in('HEAD^..HEAD')) self.assertEqual({'foo'}, self.git.changes_in('HEAD^')) self.assertEqual({'foo'}, self.git.changes_in('HEAD~1')) self.assertEqual({'foo', 'bar'}, self.git.changes_in('HEAD^^..HEAD')) # New commit doesn't change results-by-sha self.assertEqual({'foo'}, self.git.changes_in(c1)) # Files changed in multiple diffs within a range c3 = commit_contents_to_files('3', 'foo') self.assertEqual({'foo', 'bar'}, self.git.changes_in(f'{c1}..{c3}')) # Changes in a tag subprocess.check_call(['git', 'tag', 'v1']) self.assertEqual({'foo'}, self.git.changes_in('v1')) # Introduce a new filename c4 = commit_contents_to_files('4', 'baz') self.assertEqual({'baz'}, self.git.changes_in('HEAD')) # Tag-to-sha self.assertEqual({'baz'}, self.git.changes_in(f"v1..{c4}")) # We can get multiple changes from one ref commit_contents_to_files('5', 'foo', 'bar') self.assertEqual({'foo', 'bar'}, self.git.changes_in('HEAD')) self.assertEqual({'foo', 'bar', 'baz'}, self.git.changes_in('HEAD~4..HEAD')) self.assertEqual({'foo', 'bar', 'baz'}, self.git.changes_in(f'{c1}..HEAD')) self.assertEqual({'foo', 'bar', 'baz'}, self.git.changes_in(f'{c1}..{c4}')) def test_changelog_utf8(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(message, encoding, content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), 'w') as fp: fp.write(content) subprocess.check_call(['git', 'add', '.']) subprocess.check_call([ 'git', 'config', '--local', '--add', 'i18n.commitencoding', encoding ]) subprocess.check_call( ['git', 'config', '--local', 'commit.gpgSign', 'false']) try: subprocess.check_call( ['git', 'commit', '-m', message.encode(encoding)]) finally: subprocess.check_call([ 'git', 'config', '--local', '--unset-all', 'i18n.commitencoding' ]) return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() # Mix in a non-UTF-8 author to all commits to exercise the corner described here does not # adversely impact the ability to render the changelog (even if rendering for certain # characters is incorrect): http://comments.gmane.org/gmane.comp.version-control.git/262685 # NB: This method of override requires we include `user.name` and `user.email` even though we # only use `user.name` to exercise non-UTF-8. Without `user.email`, it will be unset and # commits can then fail on machines without a proper hostname setup for git to fall back to # when concocting a last-ditch `user.email`. non_utf8_config = dedent(""" [user] name = Noralf Trønnes email = [email protected] """).encode('iso-8859-1') with open(os.path.join(self.gitdir, 'config'), 'wb') as fp: fp.write(non_utf8_config) # Note the copyright symbol is used as the non-ascii character in the next 3 commits commit_contents_to_files('START1 © END', 'iso-8859-1', '1', 'foo') commit_contents_to_files('START2 © END', 'latin1', '1', 'bar') commit_contents_to_files('START3 © END', 'utf-8', '1', 'baz') commit_contents_to_files('START4 ~ END', 'us-ascii', '1', 'bip') # Prove our non-utf-8 encodings were stored in the commit metadata. log = subprocess.check_output(['git', 'log', '--format=%e']) self.assertEqual([b'us-ascii', b'latin1', b'iso-8859-1'], [_f for _f in log.strip().splitlines() if _f]) # And show that the git log successfully transcodes all the commits none-the-less to utf-8 changelog = self.git.changelog() # The ascii commit should combine with the iso-8859-1 author an fail to transcode the # o-with-stroke character, and so it should be replaced with the utf-8 replacement character # \uFFF or �. self.assertIn('Noralf Tr�nnes', changelog) self.assertIn('Noralf Tr\uFFFDnnes', changelog) # For the other 3 commits, each of iso-8859-1, latin1 and utf-8 have an encoding for the # o-with-stroke character - \u00F8 or ø - so we should find it; self.assertIn('Noralf Trønnes', changelog) self.assertIn('Noralf Tr\u00F8nnes', changelog) self.assertIn('START1 © END', changelog) self.assertIn('START2 © END', changelog) self.assertIn('START3 © END', changelog) self.assertIn('START4 ~ END', changelog) def test_refresh_with_conflict(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({'README'}, self.git.changed_files(from_commit='HEAD^')) self.assertEqual({'README'}, self.git.changes_in('HEAD')) # Create a change on this branch that is incompatible with the change to master with open(self.readme_file, 'w') as readme: readme.write('Conflict') subprocess.check_call(['git', 'commit', '-am', 'Conflict']) self.assertEqual( set(), self.git.changed_files(include_untracked=True, from_commit='HEAD')) with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=False) # The repo is dirty self.assertEqual({'README'}, self.git.changed_files(include_untracked=True, from_commit='HEAD')) with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'HEAD']) # Now try with leave_clean with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=True) # The repo is clean self.assertEqual( set(), self.git.changed_files(include_untracked=True, from_commit='HEAD')) def test_commit_with_new_untracked_file_adds_file(self): new_file = os.path.join(self.worktree, 'untracked_file') touch(new_file) self.assertEqual({'untracked_file'}, self.git.changed_files(include_untracked=True)) self.git.add(new_file) self.assertEqual({'untracked_file'}, self.git.changed_files()) self.git.commit('API Changes.') self.assertEqual(set(), self.git.changed_files(include_untracked=True))
class GitTest(unittest.TestCase): @staticmethod def init_repo(remote_name, remote): subprocess.check_call(['git', 'init']) subprocess.check_call(['git', 'config', 'user.email', '*****@*****.**']) subprocess.check_call(['git', 'config', 'user.name', 'Your Name']) subprocess.check_call(['git', 'remote', 'add', remote_name, remote]) @classmethod def setUp(self): self.origin = safe_mkdtemp() with pushd(self.origin): subprocess.check_call(['git', 'init', '--bare']) self.gitdir = safe_mkdtemp() self.worktree = safe_mkdtemp() self.readme_file = os.path.join(self.worktree, 'README') with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.init_repo('depot', self.origin) touch(self.readme_file) subprocess.check_call(['git', 'add', 'README']) subprocess.check_call(['git', 'commit', '-am', 'initial commit with decode -> \x81b']) subprocess.check_call(['git', 'tag', 'first']) subprocess.check_call(['git', 'push', '--tags', 'depot', 'master']) subprocess.check_call(['git', 'branch', '--set-upstream', 'master', 'depot/master']) with safe_open(self.readme_file, 'w') as readme: readme.write('Hello World.') subprocess.check_call(['git', 'commit', '-am', 'Update README.']) self.clone2 = safe_mkdtemp() with pushd(self.clone2): self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) with safe_open(os.path.realpath('README'), 'a') as readme: readme.write('--') subprocess.check_call(['git', 'commit', '-am', 'Update README 2.']) subprocess.check_call(['git', 'push', '--tags', 'origin', 'master']) self.git = Git(gitdir=self.gitdir, worktree=self.worktree) @staticmethod @contextmanager def mkremote(remote_name): with temporary_dir() as remote_uri: subprocess.check_call(['git', 'remote', 'add', remote_name, remote_uri]) try: yield remote_uri finally: subprocess.check_call(['git', 'remote', 'remove', remote_name]) @classmethod def tearDown(self): safe_rmtree(self.origin) safe_rmtree(self.gitdir) safe_rmtree(self.worktree) safe_rmtree(self.clone2) def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual(set(['README']), self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with pytest.raises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote('origin') as origin_uri: origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as readme: readme.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual(set(['README']), self.git.changed_files()) self.assertEqual(set(['README', 'INSTALL']), self.git.changed_files(include_untracked=True)) # confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to='non-existent')) self.git.commit('API Changes.') try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date('HEAD')) self.assertEqual(self.git.commit_date('HEAD'), self.git.commit_date('HEAD')) self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README')) as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual(set(['README', 'CHANGES']), git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found') def test_detect_worktree(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) def worktree_relative_to(cwd, expected): """Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'.""" orig_cwd = os.getcwd() try: abs_cwd = os.path.join(clone, cwd) if not os.path.isdir(abs_cwd): os.mkdir(abs_cwd) os.chdir(abs_cwd) actual = Git.detect_worktree() self.assertEqual(expected, actual) finally: os.chdir(orig_cwd) worktree_relative_to('..', None) worktree_relative_to('.', clone) worktree_relative_to('is', clone) worktree_relative_to('is/a', clone) worktree_relative_to('is/a/dir', clone) def test_diffspec(self): """Test finding changes in a diffspecs To some extent this is just testing functionality of git not pants, since all pants says is that it will pass the diffspec to git diff-tree, but this should serve to at least document the functionality we belive works. """ with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), 'w') as fp: fp.write(content) subprocess.check_call(['git', 'add', '.']) subprocess.check_call(['git', 'commit', '-m', 'change '+path]) return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() # We can get changes in HEAD or by SHA c1 = commit_contents_to_files('1', 'foo') self.assertEqual(set(['foo']), self.git.changes_in('HEAD')) self.assertEqual(set(['foo']), self.git.changes_in(c1)) # Changes in new HEAD, from old-to-new HEAD, in old HEAD, or from old-old-head to new. c2 = commit_contents_to_files('2', 'bar') self.assertEqual(set(['bar']), self.git.changes_in('HEAD')) self.assertEqual(set(['bar']), self.git.changes_in('HEAD^..HEAD')) self.assertEqual(set(['foo']), self.git.changes_in('HEAD^')) self.assertEqual(set(['foo']), self.git.changes_in('HEAD~1')) self.assertEqual(set(['foo', 'bar']), self.git.changes_in('HEAD^^..HEAD')) # New commit doesn't change results-by-sha self.assertEqual(set(['foo']), self.git.changes_in(c1)) # Files changed in multiple diffs within a range c3 = commit_contents_to_files('3', 'foo') self.assertEqual(set(['foo', 'bar']), self.git.changes_in('{}..{}'.format(c1, c3))) # Changes in a tag subprocess.check_call(['git', 'tag', 'v1']) self.assertEqual(set(['foo']), self.git.changes_in('v1')) # Introduce a new filename c4 = commit_contents_to_files('4', 'baz') self.assertEqual(set(['baz']), self.git.changes_in('HEAD')) # Tag-to-sha self.assertEqual(set(['baz']), self.git.changes_in('{}..{}'.format('v1', c4))) # We can get multiple changes from one ref c5 = commit_contents_to_files('5', 'foo', 'bar') self.assertEqual(set(['foo', 'bar']), self.git.changes_in('HEAD')) self.assertEqual(set(['foo', 'bar', 'baz']), self.git.changes_in('HEAD~4..HEAD')) self.assertEqual(set(['foo', 'bar', 'baz']), self.git.changes_in('{}..HEAD'.format(c1))) self.assertEqual(set(['foo', 'bar', 'baz']), self.git.changes_in('{}..{}'.format(c1, c4))) def test_refresh_with_conflict(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.assertEqual(set(), self.git.changed_files()) self.assertEqual(set(['README']), self.git.changed_files(from_commit='HEAD^')) self.assertEqual(set(['README']), self.git.changes_in('HEAD')) # Create a change on this branch that is incompatible with the change to master with open(self.readme_file, 'w') as readme: readme.write('Conflict') subprocess.check_call(['git', 'commit', '-am', 'Conflict']) self.assertEquals(set([]), self.git.changed_files(include_untracked=True, from_commit='HEAD')) with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=False) # The repo is dirty self.assertEquals(set(['README']), self.git.changed_files(include_untracked=True, from_commit='HEAD')) with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'HEAD']) # Now try with leave_clean with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=True) # The repo is clean self.assertEquals(set([]), self.git.changed_files(include_untracked=True, from_commit='HEAD'))
class GitTest(unittest.TestCase): @staticmethod def init_repo(remote_name, remote): # TODO (peiyu) clean this up, use `git_util.initialize_repo`. subprocess.check_call(['git', 'init']) subprocess.check_call(['git', 'config', 'user.email', '*****@*****.**']) subprocess.check_call(['git', 'config', 'user.name', 'Your Name']) subprocess.check_call(['git', 'remote', 'add', remote_name, remote]) def setUp(self): self.origin = safe_mkdtemp() with pushd(self.origin): subprocess.check_call(['git', 'init', '--bare']) self.gitdir = safe_mkdtemp() self.worktree = safe_mkdtemp() self.readme_file = os.path.join(self.worktree, 'README') with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.init_repo('depot', self.origin) touch(self.readme_file) subprocess.check_call(['git', 'add', 'README']) safe_mkdir(os.path.join(self.worktree, 'dir')) with open(os.path.join(self.worktree, 'dir', 'f'), 'w') as f: f.write("file in subdir") # Make some symlinks os.symlink('f', os.path.join(self.worktree, 'dir', 'relative-symlink')) os.symlink('no-such-file', os.path.join(self.worktree, 'dir', 'relative-nonexistent')) os.symlink('dir/f', os.path.join(self.worktree, 'dir', 'not-absolute\u2764')) os.symlink('../README', os.path.join(self.worktree, 'dir', 'relative-dotdot')) os.symlink('dir', os.path.join(self.worktree, 'link-to-dir')) os.symlink('README/f', os.path.join(self.worktree, 'not-a-dir')) os.symlink('loop1', os.path.join(self.worktree, 'loop2')) os.symlink('loop2', os.path.join(self.worktree, 'loop1')) subprocess.check_call(['git', 'add', 'README', 'dir', 'loop1', 'loop2', 'link-to-dir', 'not-a-dir']) subprocess.check_call(['git', 'commit', '-am', 'initial commit with decode -> \x81b']) self.initial_rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() subprocess.check_call(['git', 'tag', 'first']) subprocess.check_call(['git', 'push', '--tags', 'depot', 'master']) subprocess.check_call(['git', 'branch', '--set-upstream-to', 'depot/master']) with safe_open(self.readme_file, 'w') as readme: readme.write('Hello World.\u2764'.encode('utf-8')) subprocess.check_call(['git', 'commit', '-am', 'Update README.']) self.current_rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() self.clone2 = safe_mkdtemp() with pushd(self.clone2): self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) with safe_open(os.path.realpath('README'), 'a') as readme: readme.write('--') subprocess.check_call(['git', 'commit', '-am', 'Update README 2.']) subprocess.check_call(['git', 'push', '--tags', 'origin', 'master']) self.git = Git(gitdir=self.gitdir, worktree=self.worktree) @contextmanager def mkremote(self, remote_name): with temporary_dir() as remote_uri: subprocess.check_call(['git', 'remote', 'add', remote_name, remote_uri]) try: yield remote_uri finally: subprocess.check_call(['git', 'remote', 'remove', remote_name]) def tearDown(self): safe_rmtree(self.origin) safe_rmtree(self.gitdir) safe_rmtree(self.worktree) safe_rmtree(self.clone2) def test_listdir(self): reader = self.git.repo_reader(self.initial_rev) for dirname in '.', './.': results = reader.listdir(dirname) self.assertEquals(['README', 'dir', 'link-to-dir', 'loop1', 'loop2', 'not-a-dir'], sorted(results)) for dirname in 'dir', './dir': results = reader.listdir(dirname) self.assertEquals(['f', 'not-absolute\u2764'.encode('utf-8'), 'relative-dotdot', 'relative-nonexistent', 'relative-symlink'], sorted(results)) results = reader.listdir('link-to-dir') self.assertEquals(['f', 'not-absolute\u2764'.encode('utf-8'), 'relative-dotdot', 'relative-nonexistent', 'relative-symlink'], sorted(results)) with self.assertRaises(reader.MissingFileException): with reader.listdir('bogus'): pass def test_lstat(self): reader = self.git.repo_reader(self.initial_rev) def lstat(*components): return type(reader.lstat(os.path.join(*components))) self.assertEquals(reader.Symlink, lstat('dir', 'relative-symlink')) self.assertEquals(reader.Symlink, lstat('not-a-dir')) self.assertEquals(reader.File, lstat('README')) self.assertEquals(reader.Dir, lstat('dir')) self.assertEquals(types.NoneType, lstat('nope-not-here')) def test_readlink(self): reader = self.git.repo_reader(self.initial_rev) def readlink(*components): return reader.readlink(os.path.join(*components)) self.assertEquals('dir/f', readlink('dir', 'relative-symlink')) self.assertEquals(None, readlink('not-a-dir')) self.assertEquals(None, readlink('README')) self.assertEquals(None, readlink('dir')) self.assertEquals(None, readlink('nope-not-here')) def test_open(self): reader = self.git.repo_reader(self.initial_rev) with reader.open('README') as f: self.assertEquals('', f.read()) with reader.open('dir/f') as f: self.assertEquals('file in subdir', f.read()) with self.assertRaises(reader.MissingFileException): with reader.open('no-such-file') as f: self.assertEquals('', f.read()) with self.assertRaises(reader.MissingFileException): with reader.open('dir/no-such-file') as f: pass with self.assertRaises(reader.IsDirException): with reader.open('dir') as f: self.assertEquals('', f.read()) current_reader = self.git.repo_reader(self.current_rev) with current_reader.open('README') as f: self.assertEquals('Hello World.\u2764'.encode('utf-8'), f.read()) with current_reader.open('link-to-dir/f') as f: self.assertEquals('file in subdir', f.read()) with current_reader.open('dir/relative-symlink') as f: self.assertEquals('file in subdir', f.read()) with self.assertRaises(current_reader.SymlinkLoopException): with current_reader.open('loop1') as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open('dir/relative-nonexistent') as f: pass with self.assertRaises(current_reader.NotADirException): with current_reader.open('not-a-dir') as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open('dir/not-absolute\u2764') as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open('dir/relative-nonexistent') as f: pass with current_reader.open('dir/relative-dotdot') as f: self.assertEquals('Hello World.\u2764'.encode('utf-8'), f.read()) def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({'README'}, self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with self.assertRaises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote('origin') as origin_uri: # We shouldn't be fooled by remotes with origin in their name. with self.mkremote('temp_origin'): origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as fp: fp.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual({'README'}, self.git.changed_files()) self.assertEqual({'README', 'INSTALL'}, self.git.changed_files(include_untracked=True)) # Confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to='non-existent')) self.git.commit('API Changes.') try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date('HEAD')) self.assertEqual(self.git.commit_date('HEAD'), self.git.commit_date('HEAD')) self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README')) as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual({'README', 'CHANGES'}, git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found') def test_detect_worktree(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) def worktree_relative_to(cwd, expected): # Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'. orig_cwd = os.getcwd() try: abs_cwd = os.path.join(clone, cwd) if not os.path.isdir(abs_cwd): os.mkdir(abs_cwd) os.chdir(abs_cwd) actual = Git.detect_worktree() self.assertEqual(expected, actual) finally: os.chdir(orig_cwd) worktree_relative_to('..', None) worktree_relative_to('.', clone) worktree_relative_to('is', clone) worktree_relative_to('is/a', clone) worktree_relative_to('is/a/dir', clone) def test_detect_worktree_no_cwd(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) def worktree_relative_to(some_dir, expected): # Given a directory relative to the worktree, tests that the worktree is detected as 'expected'. subdir = os.path.join(clone, some_dir) if not os.path.isdir(subdir): os.mkdir(subdir) actual = Git.detect_worktree(subdir=subdir) self.assertEqual(expected, actual) worktree_relative_to('..', None) worktree_relative_to('.', clone) worktree_relative_to('is', clone) worktree_relative_to('is/a', clone) worktree_relative_to('is/a/dir', clone) @property def test_changes_in(self): """Test finding changes in a diffspecs To some extent this is just testing functionality of git not pants, since all pants says is that it will pass the diffspec to git diff-tree, but this should serve to at least document the functionality we belive works. """ with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), 'w') as fp: fp.write(content) subprocess.check_call(['git', 'add', '.']) subprocess.check_call(['git', 'commit', '-m', 'change {}'.format(files)]) return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() # We can get changes in HEAD or by SHA c1 = commit_contents_to_files('1', 'foo') self.assertEqual({'foo'}, self.git.changes_in('HEAD')) self.assertEqual({'foo'}, self.git.changes_in(c1)) # Changes in new HEAD, from old-to-new HEAD, in old HEAD, or from old-old-head to new. commit_contents_to_files('2', 'bar') self.assertEqual({'bar'}, self.git.changes_in('HEAD')) self.assertEqual({'bar'}, self.git.changes_in('HEAD^..HEAD')) self.assertEqual({'foo'}, self.git.changes_in('HEAD^')) self.assertEqual({'foo'}, self.git.changes_in('HEAD~1')) self.assertEqual({'foo', 'bar'}, self.git.changes_in('HEAD^^..HEAD')) # New commit doesn't change results-by-sha self.assertEqual({'foo'}, self.git.changes_in(c1)) # Files changed in multiple diffs within a range c3 = commit_contents_to_files('3', 'foo') self.assertEqual({'foo', 'bar'}, self.git.changes_in('{}..{}'.format(c1, c3))) # Changes in a tag subprocess.check_call(['git', 'tag', 'v1']) self.assertEqual({'foo'}, self.git.changes_in('v1')) # Introduce a new filename c4 = commit_contents_to_files('4', 'baz') self.assertEqual({'baz'}, self.git.changes_in('HEAD')) # Tag-to-sha self.assertEqual({'baz'}, self.git.changes_in('{}..{}'.format('v1', c4))) # We can get multiple changes from one ref commit_contents_to_files('5', 'foo', 'bar') self.assertEqual({'foo', 'bar'}, self.git.changes_in('HEAD')) self.assertEqual({'foo', 'bar', 'baz'}, self.git.changes_in('HEAD~4..HEAD')) self.assertEqual({'foo', 'bar', 'baz'}, self.git.changes_in('{}..HEAD'.format(c1))) self.assertEqual({'foo', 'bar', 'baz'}, self.git.changes_in('{}..{}'.format(c1, c4))) def test_changelog_utf8(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(message, encoding, content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), 'w') as fp: fp.write(content) subprocess.check_call(['git', 'add', '.']) subprocess.check_call(['git', 'config', '--local', '--add', 'i18n.commitencoding', encoding]) try: subprocess.check_call(['git', 'commit', '-m', message.encode(encoding)]) finally: subprocess.check_call(['git', 'config', '--local', '--unset-all', 'i18n.commitencoding']) return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() # Mix in a non-UTF-8 author to all commits to exercise the corner described here does not # adversely impact the ability to render the changelog (even if rendering for certain # characters is incorrect): http://comments.gmane.org/gmane.comp.version-control.git/262685 # NB: This method of override requires we include `user.name` and `user.email` even though we # only use `user.name` to exercise non-UTF-8. Without `user.email`, it will be unset and # commits can then fail on machines without a proper hostname setup for git to fall back to # when concocting a last-ditch `user.email`. non_utf8_config = dedent(""" [user] name = Noralf Trønnes email = [email protected] """).encode('iso-8859-1') with open(os.path.join(self.gitdir, 'config'), 'wb') as fp: fp.write(non_utf8_config) # Note the copyright symbol is used as the non-ascii character in the next 3 commits commit_contents_to_files('START1 © END', 'iso-8859-1', '1', 'foo') commit_contents_to_files('START2 © END', 'latin1', '1', 'bar') commit_contents_to_files('START3 © END', 'utf-8', '1', 'baz') commit_contents_to_files('START4 ~ END', 'us-ascii', '1', 'bip') # Prove our non-utf-8 encodings were stored in the commit metadata. log = subprocess.check_output(['git', 'log', '--format=%e']) self.assertEqual(['us-ascii', 'latin1', 'iso-8859-1'], filter(None, log.strip().splitlines())) # And show that the git log successfully transcodes all the commits none-the-less to utf-8 changelog = self.git.changelog() # The ascii commit should combine with the iso-8859-1 author an fail to transcode the # o-with-stroke character, and so it should be replaced with the utf-8 replacement character # \uFFF or �. self.assertIn('Noralf Tr�nnes', changelog) self.assertIn('Noralf Tr\uFFFDnnes', changelog) # For the other 3 commits, each of iso-8859-1, latin1 and utf-8 have an encoding for the # o-with-stroke character - \u00F8 or ø - so we should find it; self.assertIn('Noralf Trønnes', changelog) self.assertIn('Noralf Tr\u00F8nnes', changelog) self.assertIn('START1 © END', changelog) self.assertIn('START2 © END', changelog) self.assertIn('START3 © END', changelog) self.assertIn('START4 ~ END', changelog) def test_refresh_with_conflict(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({'README'}, self.git.changed_files(from_commit='HEAD^')) self.assertEqual({'README'}, self.git.changes_in('HEAD')) # Create a change on this branch that is incompatible with the change to master with open(self.readme_file, 'w') as readme: readme.write('Conflict') subprocess.check_call(['git', 'commit', '-am', 'Conflict']) self.assertEquals(set(), self.git.changed_files(include_untracked=True, from_commit='HEAD')) with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=False) # The repo is dirty self.assertEquals({'README'}, self.git.changed_files(include_untracked=True, from_commit='HEAD')) with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'HEAD']) # Now try with leave_clean with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=True) # The repo is clean self.assertEquals(set(), self.git.changed_files(include_untracked=True, from_commit='HEAD')) def test_commit_with_new_untracked_file_adds_file(self): new_file = os.path.join(self.worktree, 'untracked_file') touch(new_file) self.assertEqual({'untracked_file'}, self.git.changed_files(include_untracked=True)) self.git.add(new_file) self.assertEqual({'untracked_file'}, self.git.changed_files()) self.git.commit('API Changes.') self.assertEqual(set(), self.git.changed_files(include_untracked=True))
class GitTest(unittest.TestCase): @staticmethod def init_repo(remote_name, remote): subprocess.check_call(['git', 'init']) subprocess.check_call(['git', 'config', 'user.email', '*****@*****.**']) subprocess.check_call(['git', 'config', 'user.name', 'Your Name']) subprocess.check_call(['git', 'remote', 'add', remote_name, remote]) @classmethod def setUp(self): self.origin = safe_mkdtemp() with pushd(self.origin): subprocess.check_call(['git', 'init', '--bare']) self.gitdir = safe_mkdtemp() self.worktree = safe_mkdtemp() self.readme_file = os.path.join(self.worktree, 'README') with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.init_repo('depot', self.origin) touch(self.readme_file) subprocess.check_call(['git', 'add', 'README']) subprocess.check_call(['git', 'commit', '-am', 'initial commit with decode -> \x81b']) subprocess.check_call(['git', 'tag', 'first']) subprocess.check_call(['git', 'push', '--tags', 'depot', 'master']) subprocess.check_call(['git', 'branch', '--set-upstream', 'master', 'depot/master']) with safe_open(self.readme_file, 'w') as readme: readme.write('Hello World.') subprocess.check_call(['git', 'commit', '-am', 'Update README.']) self.clone2 = safe_mkdtemp() with pushd(self.clone2): self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) with safe_open(os.path.realpath('README'), 'a') as readme: readme.write('--') subprocess.check_call(['git', 'commit', '-am', 'Update README 2.']) subprocess.check_call(['git', 'push', '--tags', 'origin', 'master']) self.git = Git(gitdir=self.gitdir, worktree=self.worktree) @staticmethod @contextmanager def mkremote(remote_name): with temporary_dir() as remote_uri: subprocess.check_call(['git', 'remote', 'add', remote_name, remote_uri]) try: yield remote_uri finally: subprocess.check_call(['git', 'remote', 'remove', remote_name]) @classmethod def tearDown(self): safe_rmtree(self.origin) safe_rmtree(self.gitdir) safe_rmtree(self.worktree) safe_rmtree(self.clone2) def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual(set(['README']), self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with pytest.raises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote('origin') as origin_uri: origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as readme: readme.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual(set(['README']), self.git.changed_files()) self.assertEqual(set(['README', 'INSTALL']), self.git.changed_files(include_untracked=True)) # confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to='non-existent')) try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.commit('API Changes.') except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date('HEAD')) self.assertEqual(self.git.commit_date('HEAD'), self.git.commit_date('HEAD')) self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README')) as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual(set(['README', 'CHANGES']), git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found') def test_detect_worktree(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) def worktree_relative_to(cwd, expected): """Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'.""" orig_cwd = os.getcwd() try: abs_cwd = os.path.join(clone, cwd) if not os.path.isdir(abs_cwd): os.mkdir(abs_cwd) os.chdir(abs_cwd) actual = Git.detect_worktree() self.assertEqual(expected, actual) finally: os.chdir(orig_cwd) worktree_relative_to('..', None) worktree_relative_to('.', clone) worktree_relative_to('is', clone) worktree_relative_to('is/a', clone) worktree_relative_to('is/a/dir', clone)
def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({'README'}, self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with self.assertRaises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote('origin') as origin_uri: # We shouldn't be fooled by remotes with origin in their name. with self.mkremote('temp_origin'): origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as fp: fp.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual({'README'}, self.git.changed_files()) self.assertEqual({'README', 'INSTALL'}, self.git.changed_files(include_untracked=True)) # Confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to='non-existent')) self.git.commit('API Changes.') try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date('HEAD')) self.assertEqual(self.git.commit_date('HEAD'), self.git.commit_date('HEAD')) self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call(['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README')) as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual({'README', 'CHANGES'}, git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found')
class GitTest(unittest.TestCase): @staticmethod def init_repo(remote_name, remote): subprocess.check_call(['git', 'init']) subprocess.check_call( ['git', 'config', 'user.email', '*****@*****.**']) subprocess.check_call(['git', 'config', 'user.name', 'Your Name']) subprocess.check_call(['git', 'remote', 'add', remote_name, remote]) @classmethod def setUp(self): self.origin = safe_mkdtemp() with pushd(self.origin): subprocess.check_call(['git', 'init', '--bare']) self.gitdir = safe_mkdtemp() self.worktree = safe_mkdtemp() self.readme_file = os.path.join(self.worktree, 'README') with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.init_repo('depot', self.origin) touch(self.readme_file) subprocess.check_call(['git', 'add', 'README']) subprocess.check_call([ 'git', 'commit', '-am', 'initial commit with decode -> \x81b' ]) subprocess.check_call(['git', 'tag', 'first']) subprocess.check_call(['git', 'push', '--tags', 'depot', 'master']) subprocess.check_call( ['git', 'branch', '--set-upstream', 'master', 'depot/master']) with safe_open(self.readme_file, 'w') as readme: readme.write('Hello World.') subprocess.check_call(['git', 'commit', '-am', 'Update README.']) self.clone2 = safe_mkdtemp() with pushd(self.clone2): self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) with safe_open(os.path.realpath('README'), 'a') as readme: readme.write('--') subprocess.check_call(['git', 'commit', '-am', 'Update README 2.']) subprocess.check_call( ['git', 'push', '--tags', 'origin', 'master']) self.git = Git(gitdir=self.gitdir, worktree=self.worktree) @staticmethod @contextmanager def mkremote(remote_name): with temporary_dir() as remote_uri: subprocess.check_call( ['git', 'remote', 'add', remote_name, remote_uri]) try: yield remote_uri finally: subprocess.check_call(['git', 'remote', 'remove', remote_name]) @classmethod def tearDown(self): safe_rmtree(self.origin) safe_rmtree(self.gitdir) safe_rmtree(self.worktree) safe_rmtree(self.clone2) def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual(set(['README']), self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with pytest.raises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote('origin') as origin_uri: origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as readme: readme.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual(set(['README']), self.git.changed_files()) self.assertEqual(set(['README', 'INSTALL']), self.git.changed_files(include_untracked=True)) # confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to='non-existent')) self.git.commit('API Changes.') try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call( ['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date('HEAD')) self.assertEqual(self.git.commit_date('HEAD'), self.git.commit_date('HEAD')) self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README')) as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual(set(['README', 'CHANGES']), git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found') def test_detect_worktree(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) def worktree_relative_to(cwd, expected): """Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'.""" orig_cwd = os.getcwd() try: abs_cwd = os.path.join(clone, cwd) if not os.path.isdir(abs_cwd): os.mkdir(abs_cwd) os.chdir(abs_cwd) actual = Git.detect_worktree() self.assertEqual(expected, actual) finally: os.chdir(orig_cwd) worktree_relative_to('..', None) worktree_relative_to('.', clone) worktree_relative_to('is', clone) worktree_relative_to('is/a', clone) worktree_relative_to('is/a/dir', clone) def test_refresh_with_conflict(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.assertEqual(set(), self.git.changed_files()) self.assertEqual(set(['README']), self.git.changed_files(from_commit='HEAD^')) # Create a change on this branch that is incompatible with the change to master with open(self.readme_file, 'w') as readme: readme.write('Conflict') subprocess.check_call(['git', 'commit', '-am', 'Conflict']) self.assertEquals( set([]), self.git.changed_files(include_untracked=True, from_commit='HEAD')) with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=False) # The repo is dirty self.assertEquals( set(['README']), self.git.changed_files(include_untracked=True, from_commit='HEAD')) with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'HEAD']) # Now try with leave_clean with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=True) # The repo is clean self.assertEquals( set([]), self.git.changed_files(include_untracked=True, from_commit='HEAD'))
class GitTest(unittest.TestCase): @staticmethod def init_repo(remote_name, remote): # TODO (peiyu) clean this up, use `git_util.initialize_repo`. subprocess.check_call(["git", "init"]) subprocess.check_call( ["git", "config", "user.email", "*****@*****.**"]) subprocess.check_call(["git", "config", "user.name", "Your Name"]) subprocess.check_call(["git", "config", "commit.gpgSign", "false"]) subprocess.check_call(["git", "remote", "add", remote_name, remote]) def setUp(self): self.origin = safe_mkdtemp() with pushd(self.origin): subprocess.check_call(["git", "init", "--bare"]) self.gitdir = safe_mkdtemp() self.worktree = safe_mkdtemp() self.readme_file = os.path.join(self.worktree, "README") with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.init_repo("depot", self.origin) touch(self.readme_file) subprocess.check_call(["git", "add", "README"]) safe_mkdir(os.path.join(self.worktree, "dir")) with open(os.path.join(self.worktree, "dir", "f"), "w") as f: f.write("file in subdir") # Make some symlinks os.symlink("f", os.path.join(self.worktree, "dir", "relative-symlink")) os.symlink( "no-such-file", os.path.join(self.worktree, "dir", "relative-nonexistent")) os.symlink( "dir/f", os.path.join(self.worktree, "dir", "not-absolute\u2764")) os.symlink("../README", os.path.join(self.worktree, "dir", "relative-dotdot")) os.symlink("dir", os.path.join(self.worktree, "link-to-dir")) os.symlink("README/f", os.path.join(self.worktree, "not-a-dir")) os.symlink("loop1", os.path.join(self.worktree, "loop2")) os.symlink("loop2", os.path.join(self.worktree, "loop1")) subprocess.check_call([ "git", "add", "README", "dir", "loop1", "loop2", "link-to-dir", "not-a-dir" ]) subprocess.check_call([ "git", "commit", "-am", "initial commit with decode -> \x81b" ]) self.initial_rev = subprocess.check_output( ["git", "rev-parse", "HEAD"]).strip() subprocess.check_call(["git", "tag", "first"]) subprocess.check_call(["git", "push", "--tags", "depot", "master"]) subprocess.check_call( ["git", "branch", "--set-upstream-to", "depot/master"]) with safe_open(self.readme_file, "wb") as readme: readme.write("Hello World.\u2764".encode()) subprocess.check_call(["git", "commit", "-am", "Update README."]) self.current_rev = subprocess.check_output( ["git", "rev-parse", "HEAD"]).strip() self.clone2 = safe_mkdtemp() with pushd(self.clone2): self.init_repo("origin", self.origin) subprocess.check_call( ["git", "pull", "--tags", "origin", "master:master"]) with safe_open(os.path.realpath("README"), "a") as readme: readme.write("--") subprocess.check_call(["git", "commit", "-am", "Update README 2."]) subprocess.check_call( ["git", "push", "--tags", "origin", "master"]) self.git = Git(gitdir=self.gitdir, worktree=self.worktree) def tearDown(self): safe_rmtree(self.origin) safe_rmtree(self.gitdir) safe_rmtree(self.worktree) safe_rmtree(self.clone2) def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({"README"}, self.git.changed_files(from_commit="HEAD^")) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertEqual("master", self.git.branch_name) def edit_readme(): with open(self.readme_file, "a") as fp: fp.write("More data.") edit_readme() with open(os.path.join(self.worktree, "INSTALL"), "w") as untracked: untracked.write("make install") self.assertEqual({"README"}, self.git.changed_files()) self.assertEqual({"README", "INSTALL"}, self.git.changed_files(include_untracked=True)) # Confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to="non-existent")) def test_detect_worktree(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo("origin", self.origin) subprocess.check_call( ["git", "pull", "--tags", "origin", "master:master"]) def worktree_relative_to(cwd, expected): # Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'. orig_cwd = os.getcwd() try: abs_cwd = os.path.join(clone, cwd) if not os.path.isdir(abs_cwd): os.mkdir(abs_cwd) os.chdir(abs_cwd) actual = Git.detect_worktree() self.assertEqual(expected, actual) finally: os.chdir(orig_cwd) worktree_relative_to("..", None) worktree_relative_to(".", clone) worktree_relative_to("is", clone) worktree_relative_to("is/a", clone) worktree_relative_to("is/a/dir", clone) def test_detect_worktree_no_cwd(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo("origin", self.origin) subprocess.check_call( ["git", "pull", "--tags", "origin", "master:master"]) def worktree_relative_to(some_dir, expected): # Given a directory relative to the worktree, tests that the worktree is detected as 'expected'. subdir = os.path.join(clone, some_dir) if not os.path.isdir(subdir): os.mkdir(subdir) actual = Git.detect_worktree(subdir=subdir) self.assertEqual(expected, actual) worktree_relative_to("..", None) worktree_relative_to(".", clone) worktree_relative_to("is", clone) worktree_relative_to("is/a", clone) worktree_relative_to("is/a/dir", clone) @property def test_changes_in(self): """Test finding changes in a diffspecs. To some extent this is just testing functionality of git not pants, since all pants says is that it will pass the diffspec to git diff-tree, but this should serve to at least document the functionality we believe works. """ with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), "w") as fp: fp.write(content) subprocess.check_call(["git", "add", "."]) subprocess.check_call( ["git", "commit", "-m", f"change {files}"]) return subprocess.check_output(["git", "rev-parse", "HEAD"]).strip() # We can get changes in HEAD or by SHA c1 = commit_contents_to_files("1", "foo") self.assertEqual({"foo"}, self.git.changes_in("HEAD")) self.assertEqual({"foo"}, self.git.changes_in(c1)) # Changes in new HEAD, from old-to-new HEAD, in old HEAD, or from old-old-head to new. commit_contents_to_files("2", "bar") self.assertEqual({"bar"}, self.git.changes_in("HEAD")) self.assertEqual({"bar"}, self.git.changes_in("HEAD^..HEAD")) self.assertEqual({"foo"}, self.git.changes_in("HEAD^")) self.assertEqual({"foo"}, self.git.changes_in("HEAD~1")) self.assertEqual({"foo", "bar"}, self.git.changes_in("HEAD^^..HEAD")) # New commit doesn't change results-by-sha self.assertEqual({"foo"}, self.git.changes_in(c1)) # Files changed in multiple diffs within a range c3 = commit_contents_to_files("3", "foo") self.assertEqual({"foo", "bar"}, self.git.changes_in(f"{c1}..{c3}")) # Changes in a tag subprocess.check_call(["git", "tag", "v1"]) self.assertEqual({"foo"}, self.git.changes_in("v1")) # Introduce a new filename c4 = commit_contents_to_files("4", "baz") self.assertEqual({"baz"}, self.git.changes_in("HEAD")) # Tag-to-sha self.assertEqual({"baz"}, self.git.changes_in(f"v1..{c4}")) # We can get multiple changes from one ref commit_contents_to_files("5", "foo", "bar") self.assertEqual({"foo", "bar"}, self.git.changes_in("HEAD")) self.assertEqual({"foo", "bar", "baz"}, self.git.changes_in("HEAD~4..HEAD")) self.assertEqual({"foo", "bar", "baz"}, self.git.changes_in(f"{c1}..HEAD")) self.assertEqual({"foo", "bar", "baz"}, self.git.changes_in(f"{c1}..{c4}")) def test_commit_with_new_untracked_file_adds_file(self): new_file = os.path.join(self.worktree, "untracked_file") touch(new_file) self.assertEqual({"untracked_file"}, self.git.changed_files(include_untracked=True)) self.git.add(new_file) self.assertEqual({"untracked_file"}, self.git.changed_files()) self.git.commit("API Changes.") self.assertEqual(set(), self.git.changed_files(include_untracked=True))
def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({"README"}, self.git.changed_files(from_commit="HEAD^")) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with self.assertRaises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote("origin") as origin_uri: # We shouldn't be fooled by remotes with origin in their name. with self.mkremote("temp_origin"): origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith("first-"), msg="un-annotated tags should be found") self.assertEqual("master", self.git.branch_name) def edit_readme(): with open(self.readme_file, "a") as fp: fp.write("More data.") edit_readme() with open(os.path.join(self.worktree, "INSTALL"), "w") as untracked: untracked.write("make install") self.assertEqual({"README"}, self.git.changed_files()) self.assertEqual({"README", "INSTALL"}, self.git.changed_files(include_untracked=True)) # Confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to="non-existent")) self.git.commit("API Changes.") try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call( ["git", "reset", "--hard", "depot/master"]) self.git.refresh() edit_readme() self.git.commit("""API '"' " Changes.""") self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date("HEAD")) self.assertEqual(self.git.commit_date("HEAD"), self.git.commit_date("HEAD")) self.git.tag("second", message="""Tagged ' " Changes""") with temporary_dir() as clone: with pushd(clone): self.init_repo("origin", self.origin) subprocess.check_call( ["git", "pull", "--tags", "origin", "master:master"]) with open(os.path.realpath("README"), "r") as readme: self.assertEqual("--More data.", readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath("CHANGES"), "w") as changes: changes.write("none") subprocess.check_call(["git", "add", "CHANGES"]) self.assertEqual({"README", "CHANGES"}, git.changed_files(from_commit="first")) self.assertEqual("master", git.branch_name) self.assertEqual("second", git.tag_name, msg="annotated tags should be found")
class GitTest(unittest.TestCase): @staticmethod def init_repo(remote_name, remote): # TODO (peiyu) clean this up, use `git_util.initialize_repo`. subprocess.check_call(["git", "init"]) subprocess.check_call( ["git", "config", "user.email", "*****@*****.**"]) subprocess.check_call(["git", "config", "user.name", "Your Name"]) subprocess.check_call(["git", "config", "commit.gpgSign", "false"]) subprocess.check_call(["git", "remote", "add", remote_name, remote]) def setUp(self): self.origin = safe_mkdtemp() with pushd(self.origin): subprocess.check_call(["git", "init", "--bare"]) self.gitdir = safe_mkdtemp() self.worktree = safe_mkdtemp() self.readme_file = os.path.join(self.worktree, "README") with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.init_repo("depot", self.origin) touch(self.readme_file) subprocess.check_call(["git", "add", "README"]) safe_mkdir(os.path.join(self.worktree, "dir")) with open(os.path.join(self.worktree, "dir", "f"), "w") as f: f.write("file in subdir") # Make some symlinks os.symlink("f", os.path.join(self.worktree, "dir", "relative-symlink")) os.symlink( "no-such-file", os.path.join(self.worktree, "dir", "relative-nonexistent")) os.symlink( "dir/f", os.path.join(self.worktree, "dir", "not-absolute\u2764")) os.symlink("../README", os.path.join(self.worktree, "dir", "relative-dotdot")) os.symlink("dir", os.path.join(self.worktree, "link-to-dir")) os.symlink("README/f", os.path.join(self.worktree, "not-a-dir")) os.symlink("loop1", os.path.join(self.worktree, "loop2")) os.symlink("loop2", os.path.join(self.worktree, "loop1")) subprocess.check_call([ "git", "add", "README", "dir", "loop1", "loop2", "link-to-dir", "not-a-dir" ]) subprocess.check_call([ "git", "commit", "-am", "initial commit with decode -> \x81b" ]) self.initial_rev = subprocess.check_output( ["git", "rev-parse", "HEAD"]).strip() subprocess.check_call(["git", "tag", "first"]) subprocess.check_call(["git", "push", "--tags", "depot", "master"]) subprocess.check_call( ["git", "branch", "--set-upstream-to", "depot/master"]) with safe_open(self.readme_file, "wb") as readme: readme.write("Hello World.\u2764".encode()) subprocess.check_call(["git", "commit", "-am", "Update README."]) self.current_rev = subprocess.check_output( ["git", "rev-parse", "HEAD"]).strip() self.clone2 = safe_mkdtemp() with pushd(self.clone2): self.init_repo("origin", self.origin) subprocess.check_call( ["git", "pull", "--tags", "origin", "master:master"]) with safe_open(os.path.realpath("README"), "a") as readme: readme.write("--") subprocess.check_call(["git", "commit", "-am", "Update README 2."]) subprocess.check_call( ["git", "push", "--tags", "origin", "master"]) self.git = Git(gitdir=self.gitdir, worktree=self.worktree) @contextmanager def mkremote(self, remote_name): with temporary_dir() as remote_uri: subprocess.check_call( ["git", "remote", "add", remote_name, remote_uri]) try: yield remote_uri finally: subprocess.check_call(["git", "remote", "remove", remote_name]) def tearDown(self): safe_rmtree(self.origin) safe_rmtree(self.gitdir) safe_rmtree(self.worktree) safe_rmtree(self.clone2) def test_listdir(self): reader = self.git.repo_reader(self.initial_rev) for dirname in ".", "./.": results = reader.listdir(dirname) self.assertEqual( [ b"README", b"dir", b"link-to-dir", b"loop1", b"loop2", b"not-a-dir" ], sorted(results), ) for dirname in "dir", "./dir": results = reader.listdir(dirname) self.assertEqual( [ b"f", "not-absolute\u2764".encode(), b"relative-dotdot", b"relative-nonexistent", b"relative-symlink", ], sorted(results), ) results = reader.listdir("link-to-dir") self.assertEqual( [ b"f", "not-absolute\u2764".encode(), b"relative-dotdot", b"relative-nonexistent", b"relative-symlink", ], sorted(results), ) with self.assertRaises(reader.MissingFileException): with reader.listdir("bogus"): pass def test_lstat(self): reader = self.git.repo_reader(self.initial_rev) def lstat(*components): return type(reader.lstat(os.path.join(*components))) self.assertEqual(reader.Symlink, lstat("dir", "relative-symlink")) self.assertEqual(reader.Symlink, lstat("not-a-dir")) self.assertEqual(reader.File, lstat("README")) self.assertEqual(reader.Dir, lstat("dir")) self.assertEqual(type(None), lstat("nope-not-here")) def test_readlink(self): reader = self.git.repo_reader(self.initial_rev) def readlink(*components): return reader.readlink(os.path.join(*components)) self.assertEqual("dir/f", readlink("dir", "relative-symlink")) self.assertEqual(None, readlink("not-a-dir")) self.assertEqual(None, readlink("README")) self.assertEqual(None, readlink("dir")) self.assertEqual(None, readlink("nope-not-here")) def test_open(self): reader = self.git.repo_reader(self.initial_rev) with reader.open("README") as f: self.assertEqual(b"", f.read()) with reader.open("dir/f") as f: self.assertEqual(b"file in subdir", f.read()) with self.assertRaises(reader.MissingFileException): with reader.open("no-such-file") as f: self.assertEqual(b"", f.read()) with self.assertRaises(reader.MissingFileException): with reader.open("dir/no-such-file") as f: pass with self.assertRaises(reader.IsDirException): with reader.open("dir") as f: self.assertEqual(b"", f.read()) current_reader = self.git.repo_reader(self.current_rev) with current_reader.open("README") as f: self.assertEqual("Hello World.\u2764".encode(), f.read()) with current_reader.open("link-to-dir/f") as f: self.assertEqual(b"file in subdir", f.read()) with current_reader.open("dir/relative-symlink") as f: self.assertEqual(b"file in subdir", f.read()) with self.assertRaises(current_reader.SymlinkLoopException): with current_reader.open("loop1") as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open("dir/relative-nonexistent") as f: pass with self.assertRaises(current_reader.NotADirException): with current_reader.open("not-a-dir") as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open("dir/not-absolute\u2764") as f: pass with self.assertRaises(current_reader.MissingFileException): with current_reader.open("dir/relative-nonexistent") as f: pass with current_reader.open("dir/relative-dotdot") as f: self.assertEqual("Hello World.\u2764".encode(), f.read()) def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({"README"}, self.git.changed_files(from_commit="HEAD^")) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with self.assertRaises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote("origin") as origin_uri: # We shouldn't be fooled by remotes with origin in their name. with self.mkremote("temp_origin"): origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith("first-"), msg="un-annotated tags should be found") self.assertEqual("master", self.git.branch_name) def edit_readme(): with open(self.readme_file, "a") as fp: fp.write("More data.") edit_readme() with open(os.path.join(self.worktree, "INSTALL"), "w") as untracked: untracked.write("make install") self.assertEqual({"README"}, self.git.changed_files()) self.assertEqual({"README", "INSTALL"}, self.git.changed_files(include_untracked=True)) # Confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to="non-existent")) self.git.commit("API Changes.") try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call( ["git", "reset", "--hard", "depot/master"]) self.git.refresh() edit_readme() self.git.commit("""API '"' " Changes.""") self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date("HEAD")) self.assertEqual(self.git.commit_date("HEAD"), self.git.commit_date("HEAD")) self.git.tag("second", message="""Tagged ' " Changes""") with temporary_dir() as clone: with pushd(clone): self.init_repo("origin", self.origin) subprocess.check_call( ["git", "pull", "--tags", "origin", "master:master"]) with open(os.path.realpath("README"), "r") as readme: self.assertEqual("--More data.", readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath("CHANGES"), "w") as changes: changes.write("none") subprocess.check_call(["git", "add", "CHANGES"]) self.assertEqual({"README", "CHANGES"}, git.changed_files(from_commit="first")) self.assertEqual("master", git.branch_name) self.assertEqual("second", git.tag_name, msg="annotated tags should be found") def test_detect_worktree(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo("origin", self.origin) subprocess.check_call( ["git", "pull", "--tags", "origin", "master:master"]) def worktree_relative_to(cwd, expected): # Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'. orig_cwd = os.getcwd() try: abs_cwd = os.path.join(clone, cwd) if not os.path.isdir(abs_cwd): os.mkdir(abs_cwd) os.chdir(abs_cwd) actual = Git.detect_worktree() self.assertEqual(expected, actual) finally: os.chdir(orig_cwd) worktree_relative_to("..", None) worktree_relative_to(".", clone) worktree_relative_to("is", clone) worktree_relative_to("is/a", clone) worktree_relative_to("is/a/dir", clone) def test_detect_worktree_no_cwd(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo("origin", self.origin) subprocess.check_call( ["git", "pull", "--tags", "origin", "master:master"]) def worktree_relative_to(some_dir, expected): # Given a directory relative to the worktree, tests that the worktree is detected as 'expected'. subdir = os.path.join(clone, some_dir) if not os.path.isdir(subdir): os.mkdir(subdir) actual = Git.detect_worktree(subdir=subdir) self.assertEqual(expected, actual) worktree_relative_to("..", None) worktree_relative_to(".", clone) worktree_relative_to("is", clone) worktree_relative_to("is/a", clone) worktree_relative_to("is/a/dir", clone) @property def test_changes_in(self): """Test finding changes in a diffspecs. To some extent this is just testing functionality of git not pants, since all pants says is that it will pass the diffspec to git diff-tree, but this should serve to at least document the functionality we belive works. """ with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), "w") as fp: fp.write(content) subprocess.check_call(["git", "add", "."]) subprocess.check_call( ["git", "commit", "-m", f"change {files}"]) return subprocess.check_output(["git", "rev-parse", "HEAD"]).strip() # We can get changes in HEAD or by SHA c1 = commit_contents_to_files("1", "foo") self.assertEqual({"foo"}, self.git.changes_in("HEAD")) self.assertEqual({"foo"}, self.git.changes_in(c1)) # Changes in new HEAD, from old-to-new HEAD, in old HEAD, or from old-old-head to new. commit_contents_to_files("2", "bar") self.assertEqual({"bar"}, self.git.changes_in("HEAD")) self.assertEqual({"bar"}, self.git.changes_in("HEAD^..HEAD")) self.assertEqual({"foo"}, self.git.changes_in("HEAD^")) self.assertEqual({"foo"}, self.git.changes_in("HEAD~1")) self.assertEqual({"foo", "bar"}, self.git.changes_in("HEAD^^..HEAD")) # New commit doesn't change results-by-sha self.assertEqual({"foo"}, self.git.changes_in(c1)) # Files changed in multiple diffs within a range c3 = commit_contents_to_files("3", "foo") self.assertEqual({"foo", "bar"}, self.git.changes_in(f"{c1}..{c3}")) # Changes in a tag subprocess.check_call(["git", "tag", "v1"]) self.assertEqual({"foo"}, self.git.changes_in("v1")) # Introduce a new filename c4 = commit_contents_to_files("4", "baz") self.assertEqual({"baz"}, self.git.changes_in("HEAD")) # Tag-to-sha self.assertEqual({"baz"}, self.git.changes_in(f"v1..{c4}")) # We can get multiple changes from one ref commit_contents_to_files("5", "foo", "bar") self.assertEqual({"foo", "bar"}, self.git.changes_in("HEAD")) self.assertEqual({"foo", "bar", "baz"}, self.git.changes_in("HEAD~4..HEAD")) self.assertEqual({"foo", "bar", "baz"}, self.git.changes_in(f"{c1}..HEAD")) self.assertEqual({"foo", "bar", "baz"}, self.git.changes_in(f"{c1}..{c4}")) def test_changelog_utf8(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(message, encoding, content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), "w") as fp: fp.write(content) subprocess.check_call(["git", "add", "."]) subprocess.check_call([ "git", "config", "--local", "--add", "i18n.commitencoding", encoding ]) subprocess.check_call( ["git", "config", "--local", "commit.gpgSign", "false"]) try: subprocess.check_call( ["git", "commit", "-m", message.encode(encoding)]) finally: subprocess.check_call([ "git", "config", "--local", "--unset-all", "i18n.commitencoding" ]) return subprocess.check_output(["git", "rev-parse", "HEAD"]).strip() # Mix in a non-UTF-8 author to all commits to exercise the corner described here does not # adversely impact the ability to render the changelog (even if rendering for certain # characters is incorrect): http://comments.gmane.org/gmane.comp.version-control.git/262685 # NB: This method of override requires we include `user.name` and `user.email` even though we # only use `user.name` to exercise non-UTF-8. Without `user.email`, it will be unset and # commits can then fail on machines without a proper hostname setup for git to fall back to # when concocting a last-ditch `user.email`. non_utf8_config = dedent(""" [user] name = Noralf Trønnes email = [email protected] """).encode("iso-8859-1") with open(os.path.join(self.gitdir, "config"), "wb") as fp: fp.write(non_utf8_config) # Note the copyright symbol is used as the non-ascii character in the next 3 commits commit_contents_to_files("START1 © END", "iso-8859-1", "1", "foo") commit_contents_to_files("START2 © END", "latin1", "1", "bar") commit_contents_to_files("START3 © END", "utf-8", "1", "baz") commit_contents_to_files("START4 ~ END", "us-ascii", "1", "bip") # Prove our non-utf-8 encodings were stored in the commit metadata. log = subprocess.check_output(["git", "log", "--format=%e"]) self.assertEqual( [b"us-ascii", b"latin1", b"iso-8859-1"], [_f for _f in log.strip().splitlines() if _f], ) # And show that the git log successfully transcodes all the commits none-the-less to utf-8 changelog = self.git.changelog() # The ascii commit should combine with the iso-8859-1 author an fail to transcode the # o-with-stroke character, and so it should be replaced with the utf-8 replacement character # \uFFF or �. self.assertIn("Noralf Tr�nnes", changelog) self.assertIn("Noralf Tr\uFFFDnnes", changelog) # For the other 3 commits, each of iso-8859-1, latin1 and utf-8 have an encoding for the # o-with-stroke character - \u00F8 or ø - so we should find it; self.assertIn("Noralf Trønnes", changelog) self.assertIn("Noralf Tr\u00F8nnes", changelog) self.assertIn("START1 © END", changelog) self.assertIn("START2 © END", changelog) self.assertIn("START3 © END", changelog) self.assertIn("START4 ~ END", changelog) def test_refresh_with_conflict(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.assertEqual(set(), self.git.changed_files()) self.assertEqual({"README"}, self.git.changed_files(from_commit="HEAD^")) self.assertEqual({"README"}, self.git.changes_in("HEAD")) # Create a change on this branch that is incompatible with the change to master with open(self.readme_file, "w") as readme: readme.write("Conflict") subprocess.check_call(["git", "commit", "-am", "Conflict"]) self.assertEqual( set(), self.git.changed_files(include_untracked=True, from_commit="HEAD")) with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=False) # The repo is dirty self.assertEqual({"README"}, self.git.changed_files(include_untracked=True, from_commit="HEAD")) with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(["git", "reset", "--hard", "HEAD"]) # Now try with leave_clean with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=True) # The repo is clean self.assertEqual( set(), self.git.changed_files(include_untracked=True, from_commit="HEAD")) def test_commit_with_new_untracked_file_adds_file(self): new_file = os.path.join(self.worktree, "untracked_file") touch(new_file) self.assertEqual({"untracked_file"}, self.git.changed_files(include_untracked=True)) self.git.add(new_file) self.assertEqual({"untracked_file"}, self.git.changed_files()) self.git.commit("API Changes.") self.assertEqual(set(), self.git.changed_files(include_untracked=True))
class GitTest(unittest.TestCase): @staticmethod def init_repo(remote_name, remote): subprocess.check_call(['git', 'init']) subprocess.check_call( ['git', 'config', 'user.email', '*****@*****.**']) subprocess.check_call(['git', 'config', 'user.name', 'Your Name']) subprocess.check_call(['git', 'remote', 'add', remote_name, remote]) @classmethod def setUp(self): self.origin = safe_mkdtemp() with pushd(self.origin): subprocess.check_call(['git', 'init', '--bare']) self.gitdir = safe_mkdtemp() self.worktree = safe_mkdtemp() self.readme_file = os.path.join(self.worktree, 'README') with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.init_repo('depot', self.origin) touch(self.readme_file) subprocess.check_call(['git', 'add', 'README']) subprocess.check_call([ 'git', 'commit', '-am', 'initial commit with decode -> \x81b' ]) subprocess.check_call(['git', 'tag', 'first']) subprocess.check_call(['git', 'push', '--tags', 'depot', 'master']) subprocess.check_call( ['git', 'branch', '--set-upstream', 'master', 'depot/master']) with safe_open(self.readme_file, 'w') as readme: readme.write('Hello World.') subprocess.check_call(['git', 'commit', '-am', 'Update README.']) self.clone2 = safe_mkdtemp() with pushd(self.clone2): self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) with safe_open(os.path.realpath('README'), 'a') as readme: readme.write('--') subprocess.check_call(['git', 'commit', '-am', 'Update README 2.']) subprocess.check_call( ['git', 'push', '--tags', 'origin', 'master']) self.git = Git(gitdir=self.gitdir, worktree=self.worktree) @staticmethod @contextmanager def mkremote(remote_name): with temporary_dir() as remote_uri: subprocess.check_call( ['git', 'remote', 'add', remote_name, remote_uri]) try: yield remote_uri finally: subprocess.check_call(['git', 'remote', 'remove', remote_name]) @classmethod def tearDown(self): safe_rmtree(self.origin) safe_rmtree(self.gitdir) safe_rmtree(self.worktree) safe_rmtree(self.clone2) def test_integration(self): self.assertEqual(set(), self.git.changed_files()) self.assertEqual(set(['README']), self.git.changed_files(from_commit='HEAD^')) tip_sha = self.git.commit_id self.assertTrue(tip_sha) self.assertTrue(tip_sha in self.git.changelog()) merge_base = self.git.merge_base() self.assertTrue(merge_base) self.assertTrue(merge_base in self.git.changelog()) with pytest.raises(Scm.LocalException): self.git.server_url with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): with self.mkremote('origin') as origin_uri: origin_url = self.git.server_url self.assertEqual(origin_url, origin_uri) self.assertTrue(self.git.tag_name.startswith('first-'), msg='un-annotated tags should be found') self.assertEqual('master', self.git.branch_name) def edit_readme(): with open(self.readme_file, 'a') as readme: readme.write('More data.') edit_readme() with open(os.path.join(self.worktree, 'INSTALL'), 'w') as untracked: untracked.write('make install') self.assertEqual(set(['README']), self.git.changed_files()) self.assertEqual(set(['README', 'INSTALL']), self.git.changed_files(include_untracked=True)) # confirm that files outside of a given relative_to path are ignored self.assertEqual(set(), self.git.changed_files(relative_to='non-existent')) self.git.commit('API Changes.') try: # These changes should be rejected because our branch point from origin is 1 commit behind # the changes pushed there in clone 2. self.git.push() except Scm.RemoteException: with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call( ['git', 'reset', '--hard', 'depot/master']) self.git.refresh() edit_readme() self.git.commit('''API '"' " Changes.''') self.git.push() # HEAD is merged into master self.assertEqual(self.git.commit_date(self.git.merge_base()), self.git.commit_date('HEAD')) self.assertEqual(self.git.commit_date('HEAD'), self.git.commit_date('HEAD')) self.git.tag('second', message='''Tagged ' " Changes''') with temporary_dir() as clone: with pushd(clone): self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) with open(os.path.realpath('README')) as readme: self.assertEqual('--More data.', readme.read()) git = Git() # Check that we can pick up committed and uncommitted changes. with safe_open(os.path.realpath('CHANGES'), 'w') as changes: changes.write('none') subprocess.check_call(['git', 'add', 'CHANGES']) self.assertEqual(set(['README', 'CHANGES']), git.changed_files(from_commit='first')) self.assertEqual('master', git.branch_name) self.assertEqual('second', git.tag_name, msg='annotated tags should be found') def test_detect_worktree(self): with temporary_dir() as _clone: with pushd(_clone): clone = os.path.realpath(_clone) self.init_repo('origin', self.origin) subprocess.check_call( ['git', 'pull', '--tags', 'origin', 'master:master']) def worktree_relative_to(cwd, expected): """Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'.""" orig_cwd = os.getcwd() try: abs_cwd = os.path.join(clone, cwd) if not os.path.isdir(abs_cwd): os.mkdir(abs_cwd) os.chdir(abs_cwd) actual = Git.detect_worktree() self.assertEqual(expected, actual) finally: os.chdir(orig_cwd) worktree_relative_to('..', None) worktree_relative_to('.', clone) worktree_relative_to('is', clone) worktree_relative_to('is/a', clone) worktree_relative_to('is/a/dir', clone) def test_diffspec(self): """Test finding changes in a diffspecs To some extent this is just testing functionality of git not pants, since all pants says is that it will pass the diffspec to git diff-tree, but this should serve to at least document the functionality we belive works. """ with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): def commit_contents_to_files(content, *files): for path in files: with safe_open(os.path.join(self.worktree, path), 'w') as fp: fp.write(content) subprocess.check_call(['git', 'add', '.']) subprocess.check_call( ['git', 'commit', '-m', 'change ' + path]) return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() # We can get changes in HEAD or by SHA c1 = commit_contents_to_files('1', 'foo') self.assertEqual(set(['foo']), self.git.changes_in('HEAD')) self.assertEqual(set(['foo']), self.git.changes_in(c1)) # Changes in new HEAD, from old-to-new HEAD, in old HEAD, or from old-old-head to new. c2 = commit_contents_to_files('2', 'bar') self.assertEqual(set(['bar']), self.git.changes_in('HEAD')) self.assertEqual(set(['bar']), self.git.changes_in('HEAD^..HEAD')) self.assertEqual(set(['foo']), self.git.changes_in('HEAD^')) self.assertEqual(set(['foo']), self.git.changes_in('HEAD~1')) self.assertEqual(set(['foo', 'bar']), self.git.changes_in('HEAD^^..HEAD')) # New commit doesn't change results-by-sha self.assertEqual(set(['foo']), self.git.changes_in(c1)) # Files changed in multiple diffs within a range c3 = commit_contents_to_files('3', 'foo') self.assertEqual(set(['foo', 'bar']), self.git.changes_in('{}..{}'.format(c1, c3))) # Changes in a tag subprocess.check_call(['git', 'tag', 'v1']) self.assertEqual(set(['foo']), self.git.changes_in('v1')) # Introduce a new filename c4 = commit_contents_to_files('4', 'baz') self.assertEqual(set(['baz']), self.git.changes_in('HEAD')) # Tag-to-sha self.assertEqual(set(['baz']), self.git.changes_in('{}..{}'.format('v1', c4))) # We can get multiple changes from one ref c5 = commit_contents_to_files('5', 'foo', 'bar') self.assertEqual(set(['foo', 'bar']), self.git.changes_in('HEAD')) self.assertEqual(set(['foo', 'bar', 'baz']), self.git.changes_in('HEAD~4..HEAD')) self.assertEqual(set(['foo', 'bar', 'baz']), self.git.changes_in('{}..HEAD'.format(c1))) self.assertEqual(set(['foo', 'bar', 'baz']), self.git.changes_in('{}..{}'.format(c1, c4))) def test_refresh_with_conflict(self): with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): self.assertEqual(set(), self.git.changed_files()) self.assertEqual(set(['README']), self.git.changed_files(from_commit='HEAD^')) self.assertEqual(set(['README']), self.git.changes_in('HEAD')) # Create a change on this branch that is incompatible with the change to master with open(self.readme_file, 'w') as readme: readme.write('Conflict') subprocess.check_call(['git', 'commit', '-am', 'Conflict']) self.assertEquals( set([]), self.git.changed_files(include_untracked=True, from_commit='HEAD')) with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=False) # The repo is dirty self.assertEquals( set(['README']), self.git.changed_files(include_untracked=True, from_commit='HEAD')) with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree): subprocess.check_call(['git', 'reset', '--hard', 'HEAD']) # Now try with leave_clean with self.assertRaises(Scm.LocalException): self.git.refresh(leave_clean=True) # The repo is clean self.assertEquals( set([]), self.git.changed_files(include_untracked=True, from_commit='HEAD'))