def test_detect_worktree_failing_git(self): with self.executable_git() as git: with open(git, 'w') as fp: fp.write('#!/bin/sh\n') fp.write('exit 1') self.assertIsNone(Git.detect_worktree()) self.assertIsNone(Git.detect_worktree(git))
def test_detect_worktree_failing_git(self): with self.executable_git() as git: with open(git, "w") as fp: fp.write("#!/bin/sh\n") fp.write("exit 1") self.assertIsNone(Git.detect_worktree()) self.assertIsNone(Git.detect_worktree(git))
def test_detect_worktree_working_git(self): expected_worktree_dir = '/a/fake/worktree/dir' with self.executable_git() as git: with open(git, 'w') as fp: fp.write('#!/bin/sh\n') fp.write('echo ' + expected_worktree_dir) self.assertEqual(expected_worktree_dir, Git.detect_worktree()) self.assertEqual(expected_worktree_dir, Git.detect_worktree(binary=git))
def test_detect_worktree_somewhere_else(self): with temporary_dir() as somewhere_else: with pushd(somewhere_else): loc = Git.detect_worktree(dir=somewhere_else) self.assertEquals(None, loc) subprocess.check_call(['git', 'init']) loc = Git.detect_worktree(dir=somewhere_else) self.assertEquals(os.path.realpath(somewhere_else), loc)
def test_detect_worktree_working_git(self): expected_worktree_dir = "/a/fake/worktree/dir" with self.executable_git() as git: with open(git, "w") as fp: fp.write("#!/bin/sh\n") fp.write("echo " + expected_worktree_dir) self.assertEqual(expected_worktree_dir, Git.detect_worktree()) self.assertEqual(expected_worktree_dir, Git.detect_worktree(binary=git))
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)
def __call__(self, manifest_entries=None): """Returns a dict suitable for passing to 'manifest_entries' in a 'jvm_binary() definition""" manifest_entries = manifest_entries or {} buildroot = get_buildroot() worktree = Git.detect_worktree( subdir=os.path.join(buildroot, self._parse_context.rel_path)) if worktree: git = Git(worktree=worktree) manifest_entries['Implementation-Version'] = git.commit_id manifest_entries['Built-By'] = pwd.getpwuid(os.getuid()).pw_name return manifest_entries
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 initialize_repo(worktree, gitdir=None): """Initialize a git repository for the given `worktree`. NB: The given `worktree` must contain at least one file which will be committed to form an initial commit. :param string worktree: The path to the git work tree. :param string gitdir: An optional path to the `.git` dir to use. :returns: A `Git` repository object that can be used to interact with the repo. :rtype: :class:`pants.scm.git.Git` """ @contextmanager def use_gitdir(): if gitdir: yield gitdir else: with temporary_dir() as d: yield d with use_gitdir() as git_dir, environment_as(GIT_DIR=git_dir, GIT_WORK_TREE=worktree): 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', 'add', '.']) subprocess.check_call(['git', 'commit', '-am', 'Add project files.']) yield Git(gitdir=git_dir, worktree=worktree)
def project_template(self): target_levels = {Revision.lenient(platform['target_level']) for platform in self.blob['jvm_platforms']['platforms'].values()} lang_level = max(target_levels) if target_levels else Revision(1, 8) configured_project = TemplateData( root_dir=get_buildroot(), outdir=self.output_directory, git_root=Git.detect_worktree(), modules=self.module_templates_by_filename.values(), java=TemplateData( encoding=self.java_encoding, maximum_heap_size=self.java_maximum_heap_size, jdk='{0}.{1}'.format(*lang_level.components[:2]), language_level='JDK_{0}_{1}'.format(*lang_level.components[:2]), ), resource_extensions=[], scala=None, checkstyle_classpath=';'.join([]), debug_port=self.debug_port, annotation_processing=self.annotation_processing_template, extra_components=[], junit_tests=self._junit_tests_config(), global_junit_vm_parameters=' '.join(self.global_junit_jvm_options), ) return configured_project
def select(self, context): self.get_options() workdir = os.path.join(self.get_options().pants_workdir, self.options_scope, 'versions', self.get_options().version) tool_path = os.path.join(workdir, 'bin/protoc-gen-go') if not os.path.exists(tool_path): safe_mkdir(workdir, clean=True) # Checkout the git repo at a given version. `go get` always gets master. repo = Git.clone('https://github.com/golang/protobuf.git', os.path.join(workdir, 'src/github.com/golang/protobuf')) repo.set_state(self.get_options().version) go = GoDistribution.global_instance() result, go_cmd = go.execute_go_cmd( cmd='install', gopath=workdir, args=['github.com/golang/protobuf/protoc-gen-go'], workunit_factory=context.new_workunit, workunit_labels=[WorkUnitLabel.BOOTSTRAP], ) if result != 0: raise SubsystemError('{} failed with exit code {}'.format(go_cmd, result)) logger.info('Selected {} binary bootstrapped to: {}'.format(self.options_scope, tool_path)) return tool_path
def initialize_repo(worktree: str, *, gitdir: Optional[str] = None) -> Iterator[Git]: """Initialize a git repository for the given `worktree`. NB: The given `worktree` must contain at least one file which will be committed to form an initial commit. :param worktree: The path to the git work tree. :param gitdir: An optional path to the `.git` dir to use. :returns: A `Git` repository object that can be used to interact with the repo. """ @contextmanager def use_gitdir() -> Iterator[str]: if gitdir: yield gitdir else: with temporary_dir() as d: yield d with use_gitdir() as git_dir, environment_as(GIT_DIR=git_dir, GIT_WORK_TREE=worktree): subprocess.run(["git", "init"], check=True) subprocess.run(["git", "config", "user.email", "*****@*****.**"], check=True) # TODO: This method inherits the global git settings, so if a developer has gpg signing on, this # will turn that off. We should probably just disable reading from the global config somehow: # https://git-scm.com/docs/git-config. subprocess.run(["git", "config", "commit.gpgSign", "false"], check=True) subprocess.run(["git", "config", "user.name", "Your Name"], check=True) subprocess.run(["git", "add", "."], check=True) subprocess.run(["git", "commit", "-am", "Add project files."], check=True) yield Git(gitdir=git_dir, worktree=worktree)
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)
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)
def setUpClass(cls): cls.origin = safe_mkdtemp() with pushd(cls.origin): subprocess.check_call(['git', 'init', '--bare']) cls.gitdir = safe_mkdtemp() cls.worktree = safe_mkdtemp() cls.readme_file = os.path.join(cls.worktree, 'README') with environment_as(GIT_DIR=cls.gitdir, GIT_WORK_TREE=cls.worktree): cls.init_repo('depot', cls.origin) touch(cls.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(cls.readme_file, 'w') as readme: readme.write('Hello World.') subprocess.check_call(['git', 'commit', '-am', 'Update README.']) cls.clone2 = safe_mkdtemp() with pushd(cls.clone2): cls.init_repo('origin', cls.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']) cls.git = Git(gitdir=cls.gitdir, worktree=cls.worktree)
def initialize_repo(worktree, gitdir=None): """Initialize a git repository for the given `worktree`. NB: The given `worktree` must contain at least one file which will be committed to form an initial commit. :param string worktree: The path to the git work tree. :param string gitdir: An optional path to the `.git` dir to use. :returns: A `Git` repository object that can be used to interact with the repo. :rtype: :class:`pants.scm.git.Git` """ @contextmanager def use_gitdir(): if gitdir: yield gitdir else: with temporary_dir() as d: yield d with use_gitdir() as git_dir, environment_as(GIT_DIR=git_dir, GIT_WORK_TREE=worktree): subprocess.check_call(['git', 'init']) subprocess.check_call( ['git', 'config', 'user.email', '*****@*****.**']) # TODO: This method inherits the global git settings, so if a developer has gpg signing on, this # will turn that off. We should probably just disable reading from the global config somehow: # https://git-scm.com/docs/git-config. subprocess.check_call(['git', 'config', 'commit.gpgSign', 'false']) subprocess.check_call(['git', 'config', 'user.name', 'Your Name']) subprocess.check_call(['git', 'add', '.']) subprocess.check_call(['git', 'commit', '-am', 'Add project files.']) yield Git(gitdir=git_dir, worktree=worktree)
def get_scm(): """Returns the pants Scm if any.""" # TODO(John Sirois): Extract a module/class to carry the bootstrap logic. global _SCM if not _SCM: from pants.scm.git import Git # We know about git, so attempt an auto-configure worktree = Git.detect_worktree() if worktree and os.path.isdir(worktree): git = Git(worktree=worktree) try: log.info('Detected git repository at %s on branch %s' % (worktree, git.branch_name)) set_scm(git) except git.LocalException as e: log.info('Failed to load git repository at %s: %s' % (worktree, e)) return _SCM
def _do_clone_and_checkout(self, download_path): desired_sha = self.version() try: checkout = Git.clone(self._GIT_CLONE_HTTPS_URL, download_path) checkout.set_state(desired_sha) except Scm.ScmException as e: raise self.RakudoBrewBootstrapError( "Error checking out revision '{}' from {}: {}".format( desired_sha, self._GIT_CLONE_HTTPS_URL, e), e)
def __call__(self, manifest_entries=None): """Returns a dict suitable for passing to 'manifest_entries' in a 'jvm_binary() definition""" manifest_entries = manifest_entries or {} buildroot = get_buildroot() worktree = Git.detect_worktree(subdir=os.path.join(buildroot, self._parse_context.rel_path)) if worktree: git = Git(worktree=worktree) manifest_entries['Implementation-Version'] = git.commit_id manifest_entries['Built-By'] = pwd.getpwuid(os.getuid()).pw_name return manifest_entries
def get_scm() -> Optional[Scm]: """Returns the pants Scm if any. :API: public """ # TODO(John Sirois): Extract a module/class to carry the bootstrap logic. global _SCM if _SCM: return _SCM from pants.scm.git import Git # We know about git, so attempt an auto-configure worktree = Git.detect_worktree() if worktree and os.path.isdir(worktree): git = Git(worktree=worktree) try: logger.debug(f'Detected git repository at {worktree} on branch {git.branch_name}') set_scm(git) except git.LocalException as e: logger.info(f'Failed to load git repository at {worktree}: {e!r}') return _SCM
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)
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)
def test_build_file_rev(self): # Test that the build_file_rev global option works. Because the # test framework does not yet support bootstrap options, this test # in fact just directly calls ScmBuildFile.set_rev. with pushd(self.root_dir): 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', 'add', '.']) subprocess.check_call(['git', 'commit', '-m' 'initial commit']) subprocess.check_call([ 'rm', '-rf', 'path-that-does-exist', 'grandparent', 'BUILD', 'BUILD.twitter' ]) self._project_tree = ScmProjectTree(self.root_dir, Git(worktree=self.root_dir), 'HEAD') my_buildfile = self.create_buildfile('grandparent/parent/BUILD') buildfile = self.create_buildfile( 'grandparent/parent/BUILD.twitter') self.assertEquals( OrderedSet([my_buildfile, buildfile]), OrderedSet(self.get_build_files_family('grandparent/parent'))) self.assertEquals( OrderedSet([ self.create_buildfile( 'grandparent/parent/child2/child3/BUILD') ]), OrderedSet( self.get_build_files_family( 'grandparent/parent/child2/child3'))) buildfiles = self.scan_buildfiles('grandparent') self.assertEquals( OrderedSet([ self.create_buildfile('grandparent/parent/BUILD'), self.create_buildfile('grandparent/parent/BUILD.twitter'), self.create_buildfile('grandparent/parent/child1/BUILD'), self.create_buildfile( 'grandparent/parent/child1/BUILD.twitter'), self.create_buildfile( 'grandparent/parent/child2/child3/BUILD'), self.create_buildfile('grandparent/parent/child5/BUILD'), ]), buildfiles)
def fetch(self, dest, rev=None): imported_repo = self._meta_tag_reader.get_imported_repo(self.import_path) if not imported_repo: raise FetchError('No <meta name="go-import"> tag found, so cannot fetch repo ' 'at {}'.format(self.import_path)) if imported_repo.vcs != 'git': # TODO: Support other vcs systems as needed. raise FetchError("Don't know how to fetch for vcs type {}.".format(imported_repo.vcs)) # TODO: Do this in a workunit (see https://github.com/pantsbuild/pants/issues/3502). logger.info('Cloning {} into {}'.format(imported_repo.url, dest)) repo = Git.clone(imported_repo.url, dest) if rev: repo.set_state(rev)
def initialize_repo(worktree): """Initialize git repository for the given worktree.""" gitdir = safe_mkdtemp() with environment_as(GIT_DIR=gitdir, GIT_WORK_TREE=worktree): 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', 'add', '.']) subprocess.check_call(['git', 'commit', '-am', 'Add project files.']) yield Git(gitdir=gitdir, worktree=worktree) safe_rmtree(gitdir)
def fetch(self, dest, rev=None): imported_repo = self._meta_tag_reader.get_imported_repo( self.import_path) if not imported_repo: raise FetchError( 'No <meta name="go-import"> tag found, so cannot fetch repo ' 'at {}'.format(self.import_path)) if imported_repo.vcs != 'git': # TODO: Support other vcs systems as needed. raise FetchError("Don't know how to fetch for vcs type {}.".format( imported_repo.vcs)) # TODO: Do this in a workunit (see https://github.com/pantsbuild/pants/issues/3502). logger.info('Cloning {} into {}'.format(imported_repo.url, dest)) repo = Git.clone(imported_repo.url, dest) if rev: repo.set_state(rev)
def get_scm(): """Returns the pants Scm if any.""" # TODO(John Sirois): Extract a module/class to carry the bootstrap logic. global _SCM if not _SCM: # We know about git, so attempt an auto-configure git_dir = os.path.join(get_buildroot(), '.git') if os.path.isdir(git_dir): from pants.scm.git import Git git = Git(worktree=get_buildroot()) try: log.info('Detected git repository on branch %s' % git.branch_name) set_scm(git) except git.LocalException: pass return _SCM
def get_scm(): """Returns the pants Scm if any.""" # TODO(John Sirois): Extract a module/class to carry the bootstrap logic. global _SCM if not _SCM: from pants.scm.git import Git # We know about git, so attempt an auto-configure worktree = Git.detect_worktree() if worktree and os.path.isdir(worktree): git = Git(worktree=worktree) try: logger.info('Detected git repository at {} on branch {}'.format(worktree, git.branch_name)) set_scm(git) except git.LocalException as e: logger.info('Failed to load git repository at {}: {}'.format(worktree, e)) return _SCM
def select(self, context): self.get_options() workdir = os.path.join( self.get_options().pants_workdir, self.options_scope, "versions", self.get_options().version, ) tool_path = os.path.join(workdir, "bin/protoc-gen-go") if not os.path.exists(tool_path): safe_mkdir(workdir, clean=True) # Checkout the git repo at a given version. `go get` always gets master. repo = Git.clone( "https://github.com/golang/protobuf.git", os.path.join(workdir, "src/github.com/golang/protobuf"), ) repo.set_state(self.get_options().version) go = GoDistribution.global_instance() result, go_cmd = go.execute_go_cmd( cmd="install", gopath=workdir, args=["github.com/golang/protobuf/protoc-gen-go"], workunit_factory=context.new_workunit, workunit_labels=[WorkUnitLabel.BOOTSTRAP], ) if result != 0: raise SubsystemError( f"{go_cmd} failed with exit code {result}") logger.info( f"Selected {self.options_scope} binary bootstrapped to: {tool_path}" ) return tool_path
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))
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): 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'))
def test_detect_worktree_invalid_executable_git(self): with self.executable_git() as git: self.assertIsNone(Git.detect_worktree()) self.assertIsNone(Git.detect_worktree(binary=git))
def setUp(self): super(ScmBuildFileTest, self).setUp() ScmBuildFile.set_rev('HEAD') ScmBuildFile.set_scm(Git(worktree=self.root_dir))
def test_detect_worktree_no_git(self): with self.empty_path(): self.assertIsNone(Git.detect_worktree())
def generate_project(self, project): def create_content_root(source_set): root_relative_path = os.path.join(source_set.source_base, source_set.path) \ if source_set.path else source_set.source_base if self.get_options().infer_test_from_siblings: is_test = IdeaGen._sibling_is_test(source_set) else: is_test = source_set.is_test if source_set.resources_only: if source_set.is_test: content_type = 'java-test-resource' else: content_type = 'java-resource' else: content_type = '' sources = TemplateData( path=root_relative_path, package_prefix=source_set.path.replace('/', '.') if source_set.path else None, is_test=is_test, content_type=content_type ) return TemplateData( path=root_relative_path, sources=[sources], exclude_paths=[os.path.join(source_set.source_base, x) for x in source_set.excludes], ) content_roots = [create_content_root(source_set) for source_set in project.sources] if project.has_python: content_roots.extend(create_content_root(source_set) for source_set in project.py_sources) scala = None if project.has_scala: scala = TemplateData( language_level=self.scala_language_level, maximum_heap_size=self.scala_maximum_heap_size, fsc=self.fsc, compiler_classpath=project.scala_compiler_classpath ) exclude_folders = [] if self.get_options().exclude_maven_target: exclude_folders += IdeaGen._maven_targets_excludes(get_buildroot()) exclude_folders += self.get_options().exclude_folders java_language_level = None for target in project.targets: if isinstance(target, JvmTarget): if java_language_level is None or java_language_level < target.platform.source_level: java_language_level = target.platform.source_level if java_language_level is not None: java_language_level = 'JDK_{0}_{1}'.format(*java_language_level.components[:2]) configured_module = TemplateData( root_dir=get_buildroot(), path=self.module_filename, content_roots=content_roots, bash=self.bash, python=project.has_python, scala=scala, internal_jars=[cp_entry.jar for cp_entry in project.internal_jars], internal_source_jars=[cp_entry.source_jar for cp_entry in project.internal_jars if cp_entry.source_jar], external_jars=[cp_entry.jar for cp_entry in project.external_jars], external_javadoc_jars=[cp_entry.javadoc_jar for cp_entry in project.external_jars if cp_entry.javadoc_jar], external_source_jars=[cp_entry.source_jar for cp_entry in project.external_jars if cp_entry.source_jar], annotation_processing=self.annotation_processing_template, extra_components=[], exclude_folders=exclude_folders, java_language_level=java_language_level, ) outdir = os.path.abspath(self.intellij_output_dir) if not os.path.exists(outdir): os.makedirs(outdir) configured_project = TemplateData( root_dir=get_buildroot(), outdir=outdir, git_root=Git.detect_worktree(), modules=[configured_module], java=TemplateData( encoding=self.java_encoding, maximum_heap_size=self.java_maximum_heap_size, jdk=self.java_jdk, language_level='JDK_1_{}'.format(self.java_language_level) ), resource_extensions=list(project.resource_extensions), scala=scala, checkstyle_classpath=';'.join(project.checkstyle_classpath), debug_port=project.debug_port, annotation_processing=self.annotation_processing_template, extra_components=[], ) existing_project_components = None existing_module_components = None if not self.nomerge: # Grab the existing components, which may include customized ones. existing_project_components = self._parse_xml_component_elements(self.project_filename) existing_module_components = self._parse_xml_component_elements(self.module_filename) # Generate (without merging in any extra components). safe_mkdir(os.path.abspath(self.intellij_output_dir)) ipr = self._generate_to_tempfile( Generator(pkgutil.get_data(__name__, self.project_template), project=configured_project)) iml = self._generate_to_tempfile( Generator(pkgutil.get_data(__name__, self.module_template), module=configured_module)) if not self.nomerge: # Get the names of the components we generated, and then delete the # generated files. Clunky, but performance is not an issue, and this # is an easy way to get those component names from the templates. extra_project_components = self._get_components_to_merge(existing_project_components, ipr) extra_module_components = self._get_components_to_merge(existing_module_components, iml) os.remove(ipr) os.remove(iml) # Generate again, with the extra components. ipr = self._generate_to_tempfile(Generator(pkgutil.get_data(__name__, self.project_template), project=configured_project.extend(extra_components=extra_project_components))) iml = self._generate_to_tempfile(Generator(pkgutil.get_data(__name__, self.module_template), module=configured_module.extend(extra_components=extra_module_components))) self.context.log.info('Generated IntelliJ project in {directory}' .format(directory=self.gen_project_workdir)) shutil.move(ipr, self.project_filename) shutil.move(iml, self.module_filename) return self.project_filename if self.open else None
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 mk_project_tree(self, build_root, ignore_patterns=None): return ScmProjectTree(build_root, Git(worktree=build_root), 'HEAD', ignore_patterns)
def generate_project(self, project): def create_content_root(source_set): root_relative_path = os.path.join(source_set.source_base, source_set.path) \ if source_set.path else source_set.source_base if self.get_options().infer_test_from_siblings: is_test = IdeaGen._sibling_is_test(source_set) else: is_test = source_set.is_test sources = TemplateData( path=root_relative_path, package_prefix=source_set.path.replace('/', '.') if source_set.path else None, is_test=is_test ) return TemplateData( path=root_relative_path, sources=[sources], exclude_paths=[os.path.join(source_set.source_base, x) for x in source_set.excludes], ) content_roots = [create_content_root(source_set) for source_set in project.sources] if project.has_python: content_roots.extend(create_content_root(source_set) for source_set in project.py_sources) scala = None if project.has_scala: scala = TemplateData( language_level=self.scala_language_level, maximum_heap_size=self.scala_maximum_heap_size, fsc=self.fsc, compiler_classpath=project.scala_compiler_classpath ) exclude_folders = [] if self.get_options().exclude_maven_target: exclude_folders += IdeaGen._maven_targets_excludes(get_buildroot()) exclude_folders += self.get_options().exclude_folders configured_module = TemplateData( root_dir=get_buildroot(), path=self.module_filename, content_roots=content_roots, bash=self.bash, python=project.has_python, scala=scala, internal_jars=[cp_entry.jar for cp_entry in project.internal_jars], internal_source_jars=[cp_entry.source_jar for cp_entry in project.internal_jars if cp_entry.source_jar], external_jars=[cp_entry.jar for cp_entry in project.external_jars], external_javadoc_jars=[cp_entry.javadoc_jar for cp_entry in project.external_jars if cp_entry.javadoc_jar], external_source_jars=[cp_entry.source_jar for cp_entry in project.external_jars if cp_entry.source_jar], extra_components=[], exclude_folders=exclude_folders, ) outdir = os.path.abspath(self.intellij_output_dir) if not os.path.exists(outdir): os.makedirs(outdir) configured_project = TemplateData( root_dir=get_buildroot(), outdir=outdir, git_root=Git.detect_worktree(), modules=[ configured_module ], java=TemplateData( encoding=self.java_encoding, maximum_heap_size=self.java_maximum_heap_size, jdk=self.java_jdk, language_level = 'JDK_1_%d' % self.java_language_level ), resource_extensions=list(project.resource_extensions), scala=scala, checkstyle_classpath=';'.join(project.checkstyle_classpath), debug_port=project.debug_port, extra_components=[], ) existing_project_components = None existing_module_components = None if not self.nomerge: # Grab the existing components, which may include customized ones. existing_project_components = self._parse_xml_component_elements(self.project_filename) existing_module_components = self._parse_xml_component_elements(self.module_filename) # Generate (without merging in any extra components). safe_mkdir(os.path.abspath(self.intellij_output_dir)) ipr = self._generate_to_tempfile( Generator(pkgutil.get_data(__name__, self.project_template), project = configured_project)) iml = self._generate_to_tempfile( Generator(pkgutil.get_data(__name__, self.module_template), module = configured_module)) if not self.nomerge: # Get the names of the components we generated, and then delete the # generated files. Clunky, but performance is not an issue, and this # is an easy way to get those component names from the templates. extra_project_components = self._get_components_to_merge(existing_project_components, ipr) extra_module_components = self._get_components_to_merge(existing_module_components, iml) os.remove(ipr) os.remove(iml) # Generate again, with the extra components. ipr = self._generate_to_tempfile(Generator(pkgutil.get_data(__name__, self.project_template), project = configured_project.extend(extra_components = extra_project_components))) iml = self._generate_to_tempfile(Generator(pkgutil.get_data(__name__, self.module_template), module = configured_module.extend(extra_components = extra_module_components))) self.context.log.info('Generated IntelliJ project in {directory}' .format(directory=self.gen_project_workdir)) shutil.move(ipr, self.project_filename) shutil.move(iml, self.module_filename) return self.project_filename if self.open else None
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)