Ejemplo n.º 1
0
 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))
Ejemplo n.º 2
0
 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))
Ejemplo n.º 3
0
    def changed_files(self, git: Git) -> list[str]:
        """Determines the files changed according to SCM/workspace and options."""
        if self.diffspec:
            return cast(List[str], git.changes_in(self.diffspec, relative_to=get_buildroot()))

        changes_since = self.since or git.current_rev_identifier
        return cast(
            List[str],
            git.changed_files(
                from_commit=changes_since, include_untracked=True, relative_to=get_buildroot()
            ),
        )
Ejemplo n.º 4
0
def get_git() -> Optional[Git]:
    """Returns Git, if available."""
    global _Git
    if _Git:
        return _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}")
            _Git = git
        except GitException as e:
            logger.info(f"Failed to load git repository at {worktree}: {e!r}")
    return _Git
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)
Ejemplo n.º 6
0
 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)
Ejemplo n.º 7
0
 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)
Ejemplo n.º 8
0
def get_git() -> Git | None:
    """Returns Git, if available."""
    global _Git
    if _Git is _GitIinitialized.NO:
        # We know about Git, so attempt an auto-configure
        try:
            git = Git.mount()
            logger.debug(
                f"Detected git repository at {git.worktree} on branch {git.branch_name}"
            )
            _Git = git
        except GitException as e:
            logger.info(f"No git repository at {os.getcwd()}: {e!r}")
            _Git = None
    return _Git
Ejemplo n.º 9
0
 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))
Ejemplo n.º 10
0
 def test_detect_worktree_no_git(self):
     with self.empty_path():
         self.assertIsNone(Git.detect_worktree())
Ejemplo n.º 11
0
    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)
Ejemplo n.º 12
0
class GitTest(unittest.TestCase):
    @staticmethod
    def init_repo(remote_name, remote):
        # TODO (peiyu) clean this up, use `git_util.initialize_repo`.
        subprocess.check_call(["git", "init"])
        subprocess.check_call(
            ["git", "config", "user.email", "*****@*****.**"])
        subprocess.check_call(["git", "config", "user.name", "Your Name"])
        subprocess.check_call(["git", "config", "commit.gpgSign", "false"])
        subprocess.check_call(["git", "remote", "add", remote_name, remote])

    def setUp(self):
        self.origin = safe_mkdtemp()
        with pushd(self.origin):
            subprocess.check_call(["git", "init", "--bare"])

        self.gitdir = safe_mkdtemp()
        self.worktree = safe_mkdtemp()

        self.readme_file = os.path.join(self.worktree, "README")

        with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree):
            self.init_repo("depot", self.origin)

            touch(self.readme_file)
            subprocess.check_call(["git", "add", "README"])
            safe_mkdir(os.path.join(self.worktree, "dir"))
            with open(os.path.join(self.worktree, "dir", "f"), "w") as f:
                f.write("file in subdir")

            # Make some symlinks
            os.symlink("f",
                       os.path.join(self.worktree, "dir", "relative-symlink"))
            os.symlink(
                "no-such-file",
                os.path.join(self.worktree, "dir", "relative-nonexistent"))
            os.symlink(
                "dir/f",
                os.path.join(self.worktree, "dir", "not-absolute\u2764"))
            os.symlink("../README",
                       os.path.join(self.worktree, "dir", "relative-dotdot"))
            os.symlink("dir", os.path.join(self.worktree, "link-to-dir"))
            os.symlink("README/f", os.path.join(self.worktree, "not-a-dir"))
            os.symlink("loop1", os.path.join(self.worktree, "loop2"))
            os.symlink("loop2", os.path.join(self.worktree, "loop1"))

            subprocess.check_call([
                "git", "add", "README", "dir", "loop1", "loop2", "link-to-dir",
                "not-a-dir"
            ])
            subprocess.check_call([
                "git", "commit", "-am", "initial commit with decode -> \x81b"
            ])
            self.initial_rev = subprocess.check_output(
                ["git", "rev-parse", "HEAD"]).strip()
            subprocess.check_call(["git", "tag", "first"])
            subprocess.check_call(["git", "push", "--tags", "depot", "master"])
            subprocess.check_call(
                ["git", "branch", "--set-upstream-to", "depot/master"])

            with safe_open(self.readme_file, "wb") as readme:
                readme.write("Hello World.\u2764".encode())
            subprocess.check_call(["git", "commit", "-am", "Update README."])

            self.current_rev = subprocess.check_output(
                ["git", "rev-parse", "HEAD"]).strip()

        self.clone2 = safe_mkdtemp()
        with pushd(self.clone2):
            self.init_repo("origin", self.origin)
            subprocess.check_call(
                ["git", "pull", "--tags", "origin", "master:master"])

            with safe_open(os.path.realpath("README"), "a") as readme:
                readme.write("--")
            subprocess.check_call(["git", "commit", "-am", "Update README 2."])
            subprocess.check_call(
                ["git", "push", "--tags", "origin", "master"])

        self.git = Git(gitdir=self.gitdir, worktree=self.worktree)

    def tearDown(self):
        safe_rmtree(self.origin)
        safe_rmtree(self.gitdir)
        safe_rmtree(self.worktree)
        safe_rmtree(self.clone2)

    def test_integration(self):
        self.assertEqual(set(), self.git.changed_files())
        self.assertEqual({"README"},
                         self.git.changed_files(from_commit="HEAD^"))

        tip_sha = self.git.commit_id
        self.assertTrue(tip_sha)
        self.assertEqual("master", self.git.branch_name)

        def edit_readme():
            with open(self.readme_file, "a") as fp:
                fp.write("More data.")

        edit_readme()
        with open(os.path.join(self.worktree, "INSTALL"), "w") as untracked:
            untracked.write("make install")
        self.assertEqual({"README"}, self.git.changed_files())
        self.assertEqual({"README", "INSTALL"},
                         self.git.changed_files(include_untracked=True))

        # Confirm that files outside of a given relative_to path are ignored
        self.assertEqual(set(),
                         self.git.changed_files(relative_to="non-existent"))

    def test_detect_worktree(self):
        with temporary_dir() as _clone:
            with pushd(_clone):
                clone = os.path.realpath(_clone)

                self.init_repo("origin", self.origin)
                subprocess.check_call(
                    ["git", "pull", "--tags", "origin", "master:master"])

                def worktree_relative_to(cwd, expected):
                    # Given a cwd relative to the worktree, tests that the worktree is detected as 'expected'.
                    orig_cwd = os.getcwd()
                    try:
                        abs_cwd = os.path.join(clone, cwd)
                        if not os.path.isdir(abs_cwd):
                            os.mkdir(abs_cwd)
                        os.chdir(abs_cwd)
                        actual = Git.detect_worktree()
                        self.assertEqual(expected, actual)
                    finally:
                        os.chdir(orig_cwd)

                worktree_relative_to("..", None)
                worktree_relative_to(".", clone)
                worktree_relative_to("is", clone)
                worktree_relative_to("is/a", clone)
                worktree_relative_to("is/a/dir", clone)

    def test_detect_worktree_no_cwd(self):
        with temporary_dir() as _clone:
            with pushd(_clone):
                clone = os.path.realpath(_clone)

                self.init_repo("origin", self.origin)
                subprocess.check_call(
                    ["git", "pull", "--tags", "origin", "master:master"])

                def worktree_relative_to(some_dir, expected):
                    # Given a directory relative to the worktree, tests that the worktree is detected as 'expected'.
                    subdir = os.path.join(clone, some_dir)
                    if not os.path.isdir(subdir):
                        os.mkdir(subdir)
                    actual = Git.detect_worktree(subdir=subdir)
                    self.assertEqual(expected, actual)

                worktree_relative_to("..", None)
                worktree_relative_to(".", clone)
                worktree_relative_to("is", clone)
                worktree_relative_to("is/a", clone)
                worktree_relative_to("is/a/dir", clone)

    @property
    def test_changes_in(self):
        """Test finding changes in a diffspecs.

        To some extent this is just testing functionality of git not pants, since all pants says is
        that it will pass the diffspec to git diff-tree, but this should serve to at least document
        the functionality we believe works.
        """
        with environment_as(GIT_DIR=self.gitdir, GIT_WORK_TREE=self.worktree):

            def commit_contents_to_files(content, *files):
                for path in files:
                    with safe_open(os.path.join(self.worktree, path),
                                   "w") as fp:
                        fp.write(content)
                subprocess.check_call(["git", "add", "."])
                subprocess.check_call(
                    ["git", "commit", "-m", f"change {files}"])
                return subprocess.check_output(["git", "rev-parse",
                                                "HEAD"]).strip()

            # We can get changes in HEAD or by SHA
            c1 = commit_contents_to_files("1", "foo")
            self.assertEqual({"foo"}, self.git.changes_in("HEAD"))
            self.assertEqual({"foo"}, self.git.changes_in(c1))

            # Changes in new HEAD, from old-to-new HEAD, in old HEAD, or from old-old-head to new.
            commit_contents_to_files("2", "bar")
            self.assertEqual({"bar"}, self.git.changes_in("HEAD"))
            self.assertEqual({"bar"}, self.git.changes_in("HEAD^..HEAD"))
            self.assertEqual({"foo"}, self.git.changes_in("HEAD^"))
            self.assertEqual({"foo"}, self.git.changes_in("HEAD~1"))
            self.assertEqual({"foo", "bar"},
                             self.git.changes_in("HEAD^^..HEAD"))

            # New commit doesn't change results-by-sha
            self.assertEqual({"foo"}, self.git.changes_in(c1))

            # Files changed in multiple diffs within a range
            c3 = commit_contents_to_files("3", "foo")
            self.assertEqual({"foo", "bar"},
                             self.git.changes_in(f"{c1}..{c3}"))

            # Changes in a tag
            subprocess.check_call(["git", "tag", "v1"])
            self.assertEqual({"foo"}, self.git.changes_in("v1"))

            # Introduce a new filename
            c4 = commit_contents_to_files("4", "baz")
            self.assertEqual({"baz"}, self.git.changes_in("HEAD"))

            # Tag-to-sha
            self.assertEqual({"baz"}, self.git.changes_in(f"v1..{c4}"))

            # We can get multiple changes from one ref
            commit_contents_to_files("5", "foo", "bar")
            self.assertEqual({"foo", "bar"}, self.git.changes_in("HEAD"))
            self.assertEqual({"foo", "bar", "baz"},
                             self.git.changes_in("HEAD~4..HEAD"))
            self.assertEqual({"foo", "bar", "baz"},
                             self.git.changes_in(f"{c1}..HEAD"))
            self.assertEqual({"foo", "bar", "baz"},
                             self.git.changes_in(f"{c1}..{c4}"))

    def test_commit_with_new_untracked_file_adds_file(self):
        new_file = os.path.join(self.worktree, "untracked_file")

        touch(new_file)

        self.assertEqual({"untracked_file"},
                         self.git.changed_files(include_untracked=True))

        self.git.add(new_file)

        self.assertEqual({"untracked_file"}, self.git.changed_files())

        self.git.commit("API Changes.")

        self.assertEqual(set(), self.git.changed_files(include_untracked=True))