From 3deed8649bd23cca293eaa44b185407e0f5ede78 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Thu, 12 Feb 2026 16:12:10 +0100 Subject: [PATCH 1/4] update messages in status --- src/subcommand/status_subcommand.cpp | 80 +++++++- src/wrapper/repository_wrapper.cpp | 48 ++++- src/wrapper/repository_wrapper.hpp | 12 +- src/wrapper/status_wrapper.cpp | 11 +- test/test_status.py | 268 ++++++++++++++++++++++++++- 5 files changed, 401 insertions(+), 18 deletions(-) diff --git a/src/subcommand/status_subcommand.cpp b/src/subcommand/status_subcommand.cpp index 9e976eb..158223e 100644 --- a/src/subcommand/status_subcommand.cpp +++ b/src/subcommand/status_subcommand.cpp @@ -28,10 +28,10 @@ status_subcommand::status_subcommand(const libgit2_object&, CLI::App& app) const std::string untracked_header = "Untracked files:\n (use \"git add ...\" to include in what will be committed)\n"; const std::string tobecommited_header = "Changes to be committed:\n (use \"git reset HEAD ...\" to unstage)\n"; const std::string ignored_header = "Ignored files:\n (use \"git add -f ...\" to include in what will be committed)\n"; -const std::string notstagged_header = "Changes not staged for commit:\n"; -// "Changes not staged for commit:\n (use \"git add%s ...\" to update what will be committed)\n (use \"git checkout -- ...\" to discard changes in working directory)\n" +const std::string notstagged_header = "Changes not staged for commit:\n (use \"git add ...\" to update what will be committed)\n"; +// TODO: add the following ot notstagged_header after "checkout " is implemented: (use \"git checkout -- ...\" to discard changes in working directory)\n"; const std::string unmerged_header = "Unmerged paths:\n (use \"git add ...\" to mark resolution)\n"; -// const std::string nothingtocommit_message = "No changes added to commit (use \"git add\" and/or \"git commit -a\")"; +const std::string nothingtocommit_message = "no changes added to commit (use \"git add\" and/or \"git commit -a\")"; const std::string treeclean_message = "Nothing to commit, working tree clean"; enum class output_format @@ -172,6 +172,7 @@ void status_run(status_subcommand_options options) auto repo = repository_wrapper::open(directory); auto sl = status_list_wrapper::status_list(repo); auto branch_name = repo.head_short_name(); + auto tracking_info = repo.get_tracking_info(); std::set tracked_dir_set{}; std::set untracked_dir_set{}; @@ -196,11 +197,44 @@ void status_run(status_subcommand_options options) is_long = ((of == output_format::DEFAULT) || (of == output_format::LONG)); if (is_long) { - std::cout << "On branch " << branch_name << "\n" << std::endl; + std::cout << "On branch " << branch_name << std::endl; + + if (tracking_info.has_upstream) + { + if(tracking_info.ahead > 0 && tracking_info.behind == 0) + { + std::cout << "Your branch is ahead of '" << tracking_info.upstream_name << "' by " + << tracking_info.ahead << " commit" + << (tracking_info.ahead > 1 ? "s" : "") << "." << std::endl; + std::cout << " (use \"git push\" to publish your local commits)" << std::endl; + } + else if (tracking_info.ahead == 0 && tracking_info.behind > 0) + { + std::cout << "Your branch is behind '" << tracking_info.upstream_name << "' by " + << tracking_info.behind << " commit" + << (tracking_info.behind > 1 ? "s" : "") << "." << std::endl; + std::cout << " (use \"git pull\" to update your local branch)" << std::endl; + } + else if (tracking_info.ahead > 0 && tracking_info.behind > 0) + { + std::cout << "Your branch and '" << tracking_info.upstream_name + << "' have diverged," << std::endl; + std::cout << "and have " << tracking_info.ahead << " and " + << tracking_info.behind << " different commit" + << ((tracking_info.ahead + tracking_info.behind) > 2 ? "s" : "") + << " each, respectively." << std::endl; + std::cout << " (use \"git pull\" to merge the remote branch into yours)" << std::endl; + } + else // ahead == 0 && behind == 0 + { + std::cout << "Your branch is up to date with '" << tracking_info.upstream_name << "'." << std::endl; + } + std::cout << std::endl; + } if (repo.is_head_unborn()) { - std::cout << "No commits yet\n" << std::endl; + std::cout << "No commit yet\n" << std::endl; } if (sl.has_unmerged_header()) @@ -214,6 +248,30 @@ void status_run(status_subcommand_options options) { std::cout << "## " << branch_name << std::endl; } + + if (tracking_info.has_upstream) + { + std::cout << "..." << tracking_info.upstream_name; + + if (tracking_info.ahead > 0 || tracking_info.behind > 0) + { + std::cout << " ["; + if (tracking_info.ahead > 0) + { + std::cout << "ahead " << tracking_info.ahead; + } + if (tracking_info.behind > 0) + { + if (tracking_info.ahead > 0) + { + std::cout << ", "; + } + std::cout << "behind " << tracking_info.behind; + } + std::cout << "]"; + } + std::cout << std::endl; + } } if (sl.has_tobecommited_header()) @@ -281,11 +339,13 @@ void status_run(status_subcommand_options options) } // TODO: check if this message should be displayed even if there are untracked files - if (!(sl.has_tobecommited_header() | sl.has_notstagged_header() | sl.has_unmerged_header() | sl.has_untracked_header())) + if (is_long & (!(sl.has_tobecommited_header() | sl.has_notstagged_header() | sl.has_unmerged_header() | sl.has_untracked_header()))) { - if (is_long) - { - std::cout << treeclean_message << std::endl; - } + std::cout << treeclean_message << std::endl; + } + + if (is_long & !sl.has_tobecommited_header() & (sl.has_notstagged_header() | sl.has_untracked_header())) + { + std::cout << nothingtocommit_message << std::endl; } } diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 2050ffe..0e9c05b 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -168,6 +168,53 @@ branch_iterator repository_wrapper::iterate_branches(git_branch_t type) const return branch_iterator(iter); } +std::optional repository_wrapper::upstream() const +{ + git_reference* ref; + reference_wrapper head = this->head(); + int error = git_branch_upstream(&ref, head); + if (error == 0) + { + return reference_wrapper(ref); + } + else + { + return std::nullopt; + } +} + +branch_tracking_info repository_wrapper::get_tracking_info() const +{ + branch_tracking_info info; + info.has_upstream = false; + info.ahead = 0; + info.behind = 0; + info.upstream_name = ""; + + if (this->is_head_unborn()) + { + return info; + } + + reference_wrapper head = this->head(); + std::optional upstream = this->upstream(); + + if (upstream) + { + info.has_upstream = true; + info.upstream_name = upstream.value().short_name(); + + auto local_oid = head.target(); + auto upstream_oid = upstream.value().target(); + + if (local_oid && upstream_oid) + { + git_graph_ahead_behind(&info.ahead, &info.behind, *this, local_oid, upstream_oid); + } + } + return info; +} + // Commits commit_wrapper repository_wrapper::find_commit(std::string_view ref_name) const @@ -458,7 +505,6 @@ config_wrapper repository_wrapper::get_config() return config_wrapper(cfg); } - // Diff diff_wrapper repository_wrapper::diff_tree_to_index(tree_wrapper old_tree, std::optional index, git_diff_options* diffopts) diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index 428f64f..25e922b 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -22,6 +22,14 @@ #include "../wrapper/tree_wrapper.hpp" #include "../wrapper/wrapper_base.hpp" +struct branch_tracking_info +{ + bool has_upstream; + std::string upstream_name; + size_t ahead; + size_t behind; +}; + class repository_wrapper : public wrapper_base { public: @@ -62,10 +70,10 @@ class repository_wrapper : public wrapper_base branch_wrapper create_branch(std::string_view name, bool force); branch_wrapper create_branch(std::string_view name, const commit_wrapper& commit, bool force); branch_wrapper create_branch(std::string_view name, const annotated_commit_wrapper& commit, bool force); - branch_wrapper find_branch(std::string_view name) const; - branch_iterator iterate_branches(git_branch_t type) const; + std::optional upstream() const; + branch_tracking_info get_tracking_info() const; // Commits commit_wrapper find_commit(std::string_view ref_name = "HEAD") const; diff --git a/src/wrapper/status_wrapper.cpp b/src/wrapper/status_wrapper.cpp index b45395e..8f68e1c 100644 --- a/src/wrapper/status_wrapper.cpp +++ b/src/wrapper/status_wrapper.cpp @@ -9,8 +9,16 @@ status_list_wrapper::~status_list_wrapper() status_list_wrapper status_list_wrapper::status_list(const repository_wrapper& rw) { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + opts.rename_threshold = 50; + status_list_wrapper res; - throw_if_error(git_status_list_new(&(res.p_resource), rw, nullptr)); + throw_if_error(git_status_list_new(&(res.p_resource), rw, &opts)); std::size_t status_list_size = git_status_list_entrycount(res.p_resource); for (std::size_t i = 0; i < status_list_size; ++i) @@ -83,4 +91,3 @@ auto status_list_wrapper::get_entry_list(git_status_t status) const -> const sta return m_empty; } } - diff --git a/test/test_status.py b/test/test_status.py index 7d4bb91..f3f5e60 100644 --- a/test/test_status.py +++ b/test/test_status.py @@ -25,7 +25,7 @@ def test_status_new_file(xtl_clone, git2cpp_path, tmp_path, short_flag, long_fla cmd.append(short_flag) if long_flag != "": cmd.append(long_flag) - p = subprocess.run(cmd, capture_output=True, cwd=xtl_path, text=True) + p = subprocess.run(cmd, capture_output=True, cwd=xtl_path, text=True, check=True) if (long_flag == "--long") or ((long_flag == "") & (short_flag == "")): assert "On branch master" in p.stdout @@ -92,6 +92,268 @@ def test_status_new_repo(git2cpp_path, tmp_path, run_in_tmp_path): p = subprocess.run(cmd, cwd=tmp_path) assert p.returncode == 0 - status_cmd = [git2cpp_path, "status"] - p_status = subprocess.run(status_cmd, cwd=tmp_path) + cmd_status = [git2cpp_path, "status"] + p_status = subprocess.run(cmd_status, capture_output=True, cwd=tmp_path, text=True) assert p_status.returncode == 0 + assert "On branch ma" in p_status.stdout # "main" locally, but "master" in the CI + assert "No commit yet" in p_status.stdout + assert "Nothing to commit, working tree clean" in p_status.stdout + + +def test_status_clean_tree(xtl_clone, git2cpp_path, tmp_path): + """Test 'Nothing to commit, working tree clean' message""" + xtl_path = tmp_path / "xtl" + + cmd = [git2cpp_path, "status"] + p = subprocess.run(cmd, capture_output=True, cwd=xtl_path, text=True) + + assert p.returncode == 0 + assert "On branch master" in p.stdout + assert "Nothing to commit, working tree clean" in p.stdout + + +@pytest.mark.parametrize("short_flag", ["", "-s"]) +def test_status_rename_detection(xtl_clone, git2cpp_path, tmp_path, short_flag): + """Test that renamed files are detected correctly""" + xtl_path = tmp_path / "xtl" + + # Rename a file using git mv or by moving and staging + old_readme = xtl_path / "README.md" + new_readme = xtl_path / "README_renamed.md" + + # Move the README file + os.rename(old_readme, new_readme) + + # Move/rename the LICENCE file using mv + cmd_mv = [git2cpp_path, "mv", "LICENSE", "LICENSE_renamed"] + subprocess.run(cmd_mv, capture_output=True, cwd=xtl_path, check=True) + + # Stage both the deletion and addition + cmd_add = [git2cpp_path, "add", "--all"] + subprocess.run(cmd_add, capture_output=True, cwd=xtl_path, check=True) + + # Check status + cmd_status = [git2cpp_path, "status"] + if short_flag == "-s": + cmd_status.append(short_flag) + p = subprocess.run(cmd_status, capture_output=True, cwd=xtl_path, text=True) + assert p.returncode == 0 + + # Should show as renamed, not as deleted + new file + assert "README.md -> README_renamed.md" in p.stdout + assert "LICENSE -> LICENSE_renamed" in p.stdout + if short_flag == "-s": + assert "R " in p.stdout + else: + assert "renamed:" in p.stdout + + +@pytest.mark.parametrize("short_flag", ["", "-s"]) +def test_status_mixed_changes(xtl_clone, git2cpp_path, tmp_path, short_flag): + """Test status with both staged and unstaged changes""" + xtl_path = tmp_path / "xtl" + + # Create a new file and stage it + staged_file = xtl_path / "staged.txt" + staged_file.write_text("staged content") + + # Deleted a file staged + del_file = xtl_path / "README.md" + os.remove(del_file) + + # Stage the two previous files + subprocess.run([git2cpp_path, "add", "staged.txt", "README.md"], cwd=xtl_path) + + # Modify an existing file without staging + unstaged_file = xtl_path / "CMakeLists.txt" + unstaged_file.write_text("unstaged changes") + + # Create an untracked file + untracked_file = xtl_path / "untracked.txt" + untracked_file.write_text("untracked") + + cmd_status = [git2cpp_path, "status"] + if short_flag == "-s": + cmd_status.append(short_flag) + p = subprocess.run(cmd_status, capture_output=True, cwd=xtl_path, text=True) + + assert p.returncode == 0 + if short_flag == "-s": + assert "A staged.txt" in p.stdout + assert "D README.md" in p.stdout + assert " M CMakeLists.txt" in p.stdout + assert "?? untracked.txt" in p.stdout + else: + assert "Changes to be committed" in p.stdout + assert "new file: staged.txt" in p.stdout + assert "deleted: README.md" in p.stdout + assert "Changes not staged for commit" in p.stdout + assert "modified: CMakeLists.txt" in p.stdout + assert "Untracked files" in p.stdout + assert "untracked.txt" in p.stdout + + +@pytest.mark.parametrize("short_flag", ["", "-s"]) +def test_status_typechange(xtl_clone, git2cpp_path, tmp_path, short_flag): + """Test status shows typechange (file to symlink or vice versa)""" + # Note: This test may need to be skipped on Windows + if os.name == 'nt': + pytest.skip("Symlink test not reliable on Windows") + + xtl_path = tmp_path / "xtl" + + # Remove a file and replace with a symlink + test_file = xtl_path / "README.md" + os.remove(test_file) + os.symlink("CMakeLists.txt", test_file) + + cmd_status = [git2cpp_path, "status"] + if short_flag == "-s": + cmd_status.append(short_flag) + p = subprocess.run(cmd_status, capture_output=True, cwd=xtl_path, text=True) + + assert p.returncode == 0 + # Should show typechange in unstaged changes + if short_flag == "-s": + assert " T " in p.stdout + else: + assert "Changes not staged for commit" in p.stdout + + +@pytest.mark.parametrize("short_flag", ["", "-s"]) +def test_status_untracked_directory(xtl_clone, git2cpp_path, tmp_path, short_flag): + """Test that untracked directories are shown with trailing slash""" + xtl_path = tmp_path / "xtl" + + # Create a directory with files + new_dir = xtl_path / "new_directory" + new_dir.mkdir() + (new_dir / "file1.txt").write_text("content1") + (new_dir / "file2.txt").write_text("content2") + + cmd_status = [git2cpp_path, "status"] + if short_flag == "-s": + cmd_status.append(short_flag) + p = subprocess.run(cmd_status, capture_output=True, cwd=xtl_path, text=True) + + assert p.returncode == 0 + if short_flag == "-s": + assert "?? " in p.stdout + else: + assert "Untracked files" in p.stdout + # Directory should be shown with trailing slash, not individual files + assert "new_directory/" in p.stdout + assert "file1.txt" not in p.stdout + assert "file2.txt" not in p.stdout + + +@pytest.mark.parametrize("short_flag", ["", "-s"]) +def test_status_ahead_of_upstream(commit_env_config, git2cpp_path, tmp_path, short_flag): + """Test status when local branch is ahead of upstream""" + # Create a repository with remote tracking + repo_path = tmp_path / "repo" + repo_path.mkdir() + + # Initialize repo + subprocess.run([git2cpp_path, "init"], cwd=repo_path) + + # Create initial commit + test_file = repo_path / "file.txt" + test_file.write_text("initial") + subprocess.run([git2cpp_path, "add", "file.txt"], cwd=repo_path, check=True) + subprocess.run([git2cpp_path, "commit", "-m", "initial"], cwd=repo_path, check=True) + + # Clone it to create remote tracking + clone_path = tmp_path / "clone" + subprocess.run(["git", "clone", str(repo_path), str(clone_path)], check=True) + + # Make a commit in clone + clone_file = clone_path / "file2.txt" + clone_file.write_text("new file") + subprocess.run([git2cpp_path, "add", "file2.txt"], cwd=clone_path, check=True) + subprocess.run([git2cpp_path, "commit", "-m", "second commit"], cwd=clone_path, check=True) + + # Check status + cmd_status = [git2cpp_path, "status"] + if (short_flag == "-s"): + cmd_status.append(short_flag) + p = subprocess.run(cmd_status, capture_output=True, cwd=clone_path, text=True) + + assert p.returncode == 0 + if short_flag == "-s": + assert "...origin/ma" in p.stdout # "main" locally, but "master" in the CI + assert "[ahead 1]" in p.stdout + else: + assert "Your branch is ahead of" in p.stdout + assert "by 1 commit" in p.stdout + assert 'use "git push"' in p.stdout + + +@pytest.mark.parametrize("short_flag", ["", "-s"]) +@pytest.mark.parametrize("branch_flag", ["-b", "--branch"]) +def test_status_with_branch_and_tracking(commit_env_config, git2cpp_path, tmp_path, short_flag, branch_flag): + """Test short format with branch flag shows tracking info""" + # Create a repository with remote tracking + repo_path = tmp_path / "repo" + repo_path.mkdir() + + subprocess.run([git2cpp_path, "init"], cwd=repo_path) + test_file = repo_path / "file.txt" + test_file.write_text("initial") + subprocess.run([git2cpp_path, "add", "file.txt"], cwd=repo_path, check=True) + subprocess.run([git2cpp_path, "commit", "-m", "initial"], cwd=repo_path, check=True) + + # Clone it + clone_path = tmp_path / "clone" + subprocess.run(["git", "clone", str(repo_path), str(clone_path)]) + + # Make a commit + clone_file = clone_path / "file2.txt" + clone_file.write_text("new") + subprocess.run([git2cpp_path, "add", "file2.txt"], cwd=clone_path, check=True) + subprocess.run([git2cpp_path, "commit", "-m", "second"], cwd=clone_path, check=True) + + # Check short status with branch flag + cmd_status = [git2cpp_path, "status", branch_flag] + if short_flag == "-s": + cmd_status.append(short_flag) + p = subprocess.run(cmd_status, capture_output=True, cwd=clone_path, text=True) + + assert p.returncode == 0 + if short_flag == "-s": + assert "## ma" in p.stdout # "main" locally, but "master" in the CI + assert "[ahead 1]" in p.stdout + else: + assert "On branch ma" in p.stdout # "main" locally, but "master" in the CI + assert "Your branch is ahead of 'origin/ma" in p.stdout # "main" locally, but "master" in the CI + assert "1 commit." in p.stdout + + +def test_status_all_headers_shown(xtl_clone, git2cpp_path, tmp_path): + """Test that all status headers can be shown together""" + xtl_path = tmp_path / "xtl" + + # Changes to be committed + staged = xtl_path / "staged.txt" + staged.write_text("staged") + subprocess.run([git2cpp_path, "add", "staged.txt"], cwd=xtl_path) + + # Changes not staged + modified = xtl_path / "CMakeLists.txt" + modified.write_text("modified") + + # Untracked + untracked = xtl_path / "untracked.txt" + untracked.write_text("untracked") + + cmd_status = [git2cpp_path, "status"] + p = subprocess.run(cmd_status, capture_output=True, cwd=xtl_path, text=True) + + assert p.returncode == 0 + assert "On branch master" in p.stdout + assert "Changes to be committed:" in p.stdout + assert 'use "git reset HEAD ..." to unstage' in p.stdout + assert "Changes not staged for commit:" in p.stdout + assert 'use "git add ..." to update what will be committed' in p.stdout + assert "Untracked files:" in p.stdout + assert 'use "git add ..." to include in what will be committed' in p.stdout From fee467e1c5c522b39ab23f7108cc7196873625b2 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 18 Feb 2026 14:23:29 +0100 Subject: [PATCH 2/4] address review comments --- src/subcommand/status_subcommand.cpp | 2 +- test/test_status.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/subcommand/status_subcommand.cpp b/src/subcommand/status_subcommand.cpp index 158223e..87bc195 100644 --- a/src/subcommand/status_subcommand.cpp +++ b/src/subcommand/status_subcommand.cpp @@ -339,7 +339,7 @@ void status_run(status_subcommand_options options) } // TODO: check if this message should be displayed even if there are untracked files - if (is_long & (!(sl.has_tobecommited_header() | sl.has_notstagged_header() | sl.has_unmerged_header() | sl.has_untracked_header()))) + if (is_long & !(sl.has_tobecommited_header() | sl.has_notstagged_header() | sl.has_unmerged_header() | sl.has_untracked_header())) { std::cout << treeclean_message << std::endl; } diff --git a/test/test_status.py b/test/test_status.py index f3f5e60..83cbc1a 100644 --- a/test/test_status.py +++ b/test/test_status.py @@ -162,7 +162,7 @@ def test_status_mixed_changes(xtl_clone, git2cpp_path, tmp_path, short_flag): os.remove(del_file) # Stage the two previous files - subprocess.run([git2cpp_path, "add", "staged.txt", "README.md"], cwd=xtl_path) + subprocess.run([git2cpp_path, "add", "staged.txt", "README.md"], cwd=xtl_path, check=True) # Modify an existing file without staging unstaged_file = xtl_path / "CMakeLists.txt" @@ -196,7 +196,6 @@ def test_status_mixed_changes(xtl_clone, git2cpp_path, tmp_path, short_flag): @pytest.mark.parametrize("short_flag", ["", "-s"]) def test_status_typechange(xtl_clone, git2cpp_path, tmp_path, short_flag): """Test status shows typechange (file to symlink or vice versa)""" - # Note: This test may need to be skipped on Windows if os.name == 'nt': pytest.skip("Symlink test not reliable on Windows") @@ -255,7 +254,7 @@ def test_status_ahead_of_upstream(commit_env_config, git2cpp_path, tmp_path, sho repo_path.mkdir() # Initialize repo - subprocess.run([git2cpp_path, "init"], cwd=repo_path) + subprocess.run([git2cpp_path, "init"], cwd=repo_path, check=True) # Create initial commit test_file = repo_path / "file.txt" @@ -305,7 +304,7 @@ def test_status_with_branch_and_tracking(commit_env_config, git2cpp_path, tmp_pa # Clone it clone_path = tmp_path / "clone" - subprocess.run(["git", "clone", str(repo_path), str(clone_path)]) + subprocess.run(["git", "clone", str(repo_path), str(clone_path)], check=True) # Make a commit clone_file = clone_path / "file2.txt" @@ -336,7 +335,7 @@ def test_status_all_headers_shown(xtl_clone, git2cpp_path, tmp_path): # Changes to be committed staged = xtl_path / "staged.txt" staged.write_text("staged") - subprocess.run([git2cpp_path, "add", "staged.txt"], cwd=xtl_path) + subprocess.run([git2cpp_path, "add", "staged.txt"], cwd=xtl_path, check=True) # Changes not staged modified = xtl_path / "CMakeLists.txt" From 4ae725ed91941b78817d4f0c67d87f04b24914c4 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 18 Feb 2026 15:49:08 +0100 Subject: [PATCH 3/4] split run into smaller functions --- src/subcommand/status_subcommand.cpp | 184 +++++++++++++++------------ 1 file changed, 105 insertions(+), 79 deletions(-) diff --git a/src/subcommand/status_subcommand.cpp b/src/subcommand/status_subcommand.cpp index 87bc195..8bca1e5 100644 --- a/src/subcommand/status_subcommand.cpp +++ b/src/subcommand/status_subcommand.cpp @@ -161,40 +161,11 @@ void print_not_tracked(const std::vector& entries_to_print, const s print_entries(not_tracked_entries_to_print, is_long, colour); } -void status_subcommand::run() -{ - status_run(m_options); -} - -void status_run(status_subcommand_options options) +void print_tracking_info(repository_wrapper& repo, status_list_wrapper& sl, status_subcommand_options options, bool is_long) { - auto directory = get_current_git_path(); - auto repo = repository_wrapper::open(directory); - auto sl = status_list_wrapper::status_list(repo); auto branch_name = repo.head_short_name(); auto tracking_info = repo.get_tracking_info(); - std::set tracked_dir_set{}; - std::set untracked_dir_set{}; - std::vector untracked_to_print{}; - std::vector ignored_to_print{}; - - output_format of = output_format::DEFAULT; - if (options.m_short_flag) - { - of = output_format::SHORT; - } - if (options.m_long_flag) - { - of = output_format::LONG; - } - // else if (porcelain_format) - // { - // output_format = 3; - // } - - bool is_long; - is_long = ((of == output_format::DEFAULT) || (of == output_format::LONG)); if (is_long) { std::cout << "On branch " << branch_name << std::endl; @@ -273,78 +244,133 @@ void status_run(status_subcommand_options options) std::cout << std::endl; } } +} + +void print_tobecommited(status_list_wrapper& sl, output_format of, std::set tracked_dir_set, bool is_long) +{ + stream_colour_fn colour = termcolor::green; + if (is_long) + { + std::cout << tobecommited_header; + } + print_entries(get_entries_to_print(GIT_STATUS_INDEX_NEW, sl, true, of, &tracked_dir_set), is_long, colour); + print_entries(get_entries_to_print(GIT_STATUS_INDEX_MODIFIED, sl, true, of, &tracked_dir_set), is_long, colour); + print_entries(get_entries_to_print(GIT_STATUS_INDEX_DELETED, sl, true, of, &tracked_dir_set), is_long, colour); + print_entries(get_entries_to_print(GIT_STATUS_INDEX_RENAMED, sl, true, of, &tracked_dir_set), is_long, colour); + print_entries(get_entries_to_print(GIT_STATUS_INDEX_TYPECHANGE, sl, true, of, &tracked_dir_set), is_long, colour); + if (is_long) + { + std::cout << std::endl; + } +} + +void print_notstagged(status_list_wrapper& sl, output_format of, std::set tracked_dir_set, bool is_long) +{ + stream_colour_fn colour = termcolor::red; + if (is_long) + { + std::cout << notstagged_header; + } + print_entries(get_entries_to_print(GIT_STATUS_WT_MODIFIED, sl, false, of, &tracked_dir_set), is_long, colour); + print_entries(get_entries_to_print(GIT_STATUS_WT_DELETED, sl, false, of, &tracked_dir_set), is_long, colour); + print_entries(get_entries_to_print(GIT_STATUS_WT_TYPECHANGE, sl, false, of, &tracked_dir_set), is_long, colour); + print_entries(get_entries_to_print(GIT_STATUS_WT_RENAMED, sl, false, of, &tracked_dir_set), is_long, colour); + if (is_long) + { + std::cout << std::endl; + } +} + +void print_unmerged(status_list_wrapper& sl, output_format of, std::set tracked_dir_set, std::set untracked_dir_set, bool is_long) +{ + stream_colour_fn colour = termcolor::red; + if (is_long) + { + std::cout << unmerged_header; + } + print_not_tracked(get_entries_to_print(GIT_STATUS_CONFLICTED, sl, false, of), tracked_dir_set, untracked_dir_set, is_long, colour); + if (is_long) + { + std::cout << std::endl; + } +} + +void print_untracked(status_list_wrapper& sl, output_format of, std::set tracked_dir_set, std::set untracked_dir_set, bool is_long) +{ + stream_colour_fn colour = termcolor::red; + if (is_long) + { + std::cout << untracked_header; + } + print_not_tracked(get_entries_to_print(GIT_STATUS_WT_NEW, sl, false, of), tracked_dir_set, untracked_dir_set, is_long, colour); + if (is_long) + { + std::cout << std::endl; + } +} + +void status_subcommand::run() +{ + status_run(m_options); +} + +void status_run(status_subcommand_options options) +{ + auto directory = get_current_git_path(); + auto repo = repository_wrapper::open(directory); + auto sl = status_list_wrapper::status_list(repo); + + std::set tracked_dir_set{}; + std::set untracked_dir_set{}; + std::vector untracked_to_print{}; + std::vector ignored_to_print{}; + + output_format of = output_format::DEFAULT; + if (options.m_short_flag) + { + of = output_format::SHORT; + } + if (options.m_long_flag) + { + of = output_format::LONG; + } + // else if (porcelain_format) + // { + // output_format = 3; + // } + + bool is_long; + is_long = ((of == output_format::DEFAULT) || (of == output_format::LONG)); + print_tracking_info(repo, sl, options, is_long); if (sl.has_tobecommited_header()) { - stream_colour_fn colour = termcolor::green; - if (is_long) - { - std::cout << tobecommited_header; - } - print_entries(get_entries_to_print(GIT_STATUS_INDEX_NEW, sl, true, of, &tracked_dir_set), is_long, colour); - print_entries(get_entries_to_print(GIT_STATUS_INDEX_MODIFIED, sl, true, of, &tracked_dir_set), is_long, colour); - print_entries(get_entries_to_print(GIT_STATUS_INDEX_DELETED, sl, true, of, &tracked_dir_set), is_long, colour); - print_entries(get_entries_to_print(GIT_STATUS_INDEX_RENAMED, sl, true, of, &tracked_dir_set), is_long, colour); - print_entries(get_entries_to_print(GIT_STATUS_INDEX_TYPECHANGE, sl, true, of, &tracked_dir_set), is_long, colour); - if (is_long) - { - std::cout << std::endl; - } + print_tobecommited(sl, of, tracked_dir_set,is_long); } if (sl.has_notstagged_header()) { - stream_colour_fn colour = termcolor::red; - if (is_long) - { - std::cout << notstagged_header; - } - print_entries(get_entries_to_print(GIT_STATUS_WT_MODIFIED, sl, false, of, &tracked_dir_set), is_long, colour); - print_entries(get_entries_to_print(GIT_STATUS_WT_DELETED, sl, false, of, &tracked_dir_set), is_long, colour); - print_entries(get_entries_to_print(GIT_STATUS_WT_TYPECHANGE, sl, false, of, &tracked_dir_set), is_long, colour); - print_entries(get_entries_to_print(GIT_STATUS_WT_RENAMED, sl, false, of, &tracked_dir_set), is_long, colour); - if (is_long) - { - std::cout << std::endl; - } + print_notstagged(sl, of, tracked_dir_set, is_long); } // TODO: check if should be printed before "not stagged" files if (sl.has_unmerged_header()) { - stream_colour_fn colour = termcolor::red; - if (is_long) - { - std::cout << unmerged_header; - } - print_not_tracked(get_entries_to_print(GIT_STATUS_CONFLICTED, sl, false, of), tracked_dir_set, untracked_dir_set, is_long, colour); - if (is_long) - { - std::cout << std::endl; - } + print_unmerged(sl, of, tracked_dir_set, untracked_dir_set, is_long); } if (sl.has_untracked_header()) { - stream_colour_fn colour = termcolor::red; - if (is_long) - { - std::cout << untracked_header; - } - print_not_tracked(get_entries_to_print(GIT_STATUS_WT_NEW, sl, false, of), tracked_dir_set, untracked_dir_set, is_long, colour); - if (is_long) - { - std::cout << std::endl; - } + print_untracked(sl, of, tracked_dir_set, untracked_dir_set, is_long); } // TODO: check if this message should be displayed even if there are untracked files - if (is_long & !(sl.has_tobecommited_header() | sl.has_notstagged_header() | sl.has_unmerged_header() | sl.has_untracked_header())) + if (is_long && !(sl.has_tobecommited_header() || sl.has_notstagged_header() || sl.has_unmerged_header() || sl.has_untracked_header())) { std::cout << treeclean_message << std::endl; } - if (is_long & !sl.has_tobecommited_header() & (sl.has_notstagged_header() | sl.has_untracked_header())) + if (is_long & !sl.has_tobecommited_header() && (sl.has_notstagged_header() || sl.has_untracked_header())) { std::cout << nothingtocommit_message << std::endl; } From 37e7533a3d2a19949c91a0353317797927cb4ac5 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 18 Feb 2026 17:49:06 +0100 Subject: [PATCH 4/4] address review comment --- test/test_status.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/test_status.py b/test/test_status.py index 83cbc1a..6a4f987 100644 --- a/test/test_status.py +++ b/test/test_status.py @@ -196,9 +196,6 @@ def test_status_mixed_changes(xtl_clone, git2cpp_path, tmp_path, short_flag): @pytest.mark.parametrize("short_flag", ["", "-s"]) def test_status_typechange(xtl_clone, git2cpp_path, tmp_path, short_flag): """Test status shows typechange (file to symlink or vice versa)""" - if os.name == 'nt': - pytest.skip("Symlink test not reliable on Windows") - xtl_path = tmp_path / "xtl" # Remove a file and replace with a symlink