From ab81dff67f2d3e63f853ddc59babc6a4a5898515 Mon Sep 17 00:00:00 2001 From: Jhon Pedroza Date: Sun, 25 Sep 2022 17:16:32 -0500 Subject: [PATCH] fix(git): Fix update_repo when there are untracked files --- CHANGES | 1 + src/libvcs/sync/git.py | 4 +-- tests/sync/test_git.py | 65 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 3242cd546..6cba649fa 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,7 @@ URL renamings (#417): - `URLProtocol`: Fix `is_valid` to use `classmethod` - All: Fix `is_valid` to use default of `None` to avoid implicitly filtering - Reduce duplicated code in methods by using `super()` + - `git`: Fix `update_repo` when there are only untracked files ### Packaging diff --git a/src/libvcs/sync/git.py b/src/libvcs/sync/git.py index c54a70d63..3c019e2ad 100644 --- a/src/libvcs/sync/git.py +++ b/src/libvcs/sync/git.py @@ -408,7 +408,7 @@ def update_repo(self, set_remotes: bool = False, *args: Any, **kwargs: Any) -> N if is_remote_ref: # Check if stash is needed try: - process = self.run(["status", "--porcelain"]) + process = self.run(["status", "--porcelain", "--untracked-files=no"]) except exc.CommandError: self.log.error("Failed to get the status") return @@ -435,7 +435,7 @@ def update_repo(self, set_remotes: bool = False, *args: Any, **kwargs: Any) -> N try: process = self.run(["rebase", git_remote_name + "/" + git_tag]) except exc.CommandError as e: - if "invalid_upstream" in str(e): + if any(msg in str(e) for msg in ["invalid_upstream", "Aborting"]): self.log.error(e) else: # Rebase failed: Restore previous state. diff --git a/tests/sync/test_git.py b/tests/sync/test_git.py index 7c1fb4cbe..fe2597683 100644 --- a/tests/sync/test_git.py +++ b/tests/sync/test_git.py @@ -2,6 +2,7 @@ import datetime import os import pathlib +import random import shutil import textwrap from typing import Callable, TypedDict @@ -141,6 +142,7 @@ def test_repo_update_handle_cases( ) -> None: git_repo: GitSync = constructor(**lazy_constructor_options(**locals())) git_repo.obtain() # clone initial repo + mocka = mocker.spy(git_repo, "run") git_repo.update_repo() @@ -154,6 +156,69 @@ def test_repo_update_handle_cases( assert mocker.call(["symbolic-ref", "--short", "HEAD"]) not in mocka.mock_calls +@pytest.mark.parametrize( + "has_untracked_files,needs_stash,has_remote_changes", + [ + [True, True, True], + [True, True, False], + [True, False, True], + [True, False, False], + [False, True, True], + [False, True, False], + [False, False, True], + [False, False, False], + ], +) +def test_repo_update_stash_cases( + tmp_path: pathlib.Path, + create_git_remote_repo: CreateProjectCallbackFixtureProtocol, + mocker: MockerFixture, + has_untracked_files: bool, + needs_stash: bool, + has_remote_changes: bool, +) -> None: + git_remote_repo = create_git_remote_repo() + + git_repo: GitSync = GitSync( + url=f"file://{git_remote_repo}", + dir=tmp_path / "myrepo", + vcs="git", + ) + git_repo.obtain() # clone initial repo + + def make_file(filename: str) -> pathlib.Path: + some_file = git_repo.dir.joinpath(filename) + with open(some_file, "w") as file: + file.write("some content: " + str(random.random())) + + return some_file + + # Make an initial commit so we can reset + some_file = make_file("initial_file") + git_repo.run(["add", some_file]) + git_repo.run(["commit", "-m", "a commit"]) + git_repo.run(["push"]) + + if has_remote_changes: + some_file = make_file("some_file") + git_repo.run(["add", some_file]) + git_repo.run(["commit", "-m", "a commit"]) + git_repo.run(["push"]) + git_repo.run(["reset", "--hard", "HEAD^"]) + + if has_untracked_files: + make_file("some_file") + + if needs_stash: + some_file = make_file("some_stashed_file") + git_repo.run(["add", some_file]) + + mocka = mocker.spy(git_repo, "run") + git_repo.update_repo() + + mocka.assert_any_call(["symbolic-ref", "--short", "HEAD"]) + + @pytest.mark.parametrize( # Postpone evaluation of options so fixture variables can interpolate "constructor,lazy_constructor_options",