Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,13 @@ build-backend = "uv_build"

[dependency-groups]
dev = ["pytest>=8", "pytest-cov>=5"]

[tool.pytest.ini_options]
addopts = "--cov --cov-report=term-missing"

[tool.coverage.run]
source = ["git_merge_onto"]

[tool.coverage.report]
fail_under = 100
show_missing = true
9 changes: 3 additions & 6 deletions src/git_merge_onto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

try:
__version__ = _pkg_version("git-merge-onto")
except PackageNotFoundError: # running from a source tree with no install
except PackageNotFoundError: # pragma: no cover - running from a source tree with no install
__version__ = "0+unknown"

# Point at a specific git binary (used by the test suite; "git" otherwise).
Expand Down Expand Up @@ -109,11 +109,8 @@ def git_dir() -> Path:
return Path(git("rev-parse", "--absolute-git-dir"))


def worktree_dirty(include_untracked: bool = True) -> bool:
args = ["status", "--porcelain"]
if not include_untracked:
args.append("--untracked-files=no")
return bool(git(*args))
def worktree_dirty() -> bool:
return bool(git("status", "--porcelain"))


def in_progress_merge() -> bool:
Expand Down
37 changes: 37 additions & 0 deletions tests/test_merge.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Behavioral tests against real git repos: the three re-parent regimes, conflict
handling, and the precondition guards."""

import runpy
import subprocess
import sys

import pytest

Expand Down Expand Up @@ -326,6 +328,41 @@ def test_resolve_commit_origin_fallback(repo):
assert gmo._resolve_commit("nope") is None


def test_verbose_echoes_command(capsys, monkeypatch):
# --quiet silences this; the default echoes each git command to stderr.
monkeypatch.setattr(gmo, "VERBOSE", True)
gmo._log_cmd(["git", "merge-recursive", "base"])
assert "Executing: git merge-recursive base" in capsys.readouterr().err


def test_git_raises_command_error_on_failure(repo):
# A failing git command with check=True surfaces as CommandError.
with pytest.raises(gmo.CommandError):
gmo.git("rev-parse", "--verify", "definitely-not-a-ref")


def test_main_reports_command_error(monkeypatch):
def boom(*_args):
raise gmo.CommandError(["git", "merge-recursive"], 3, "kaboom")

monkeypatch.setattr(gmo, "cmd_merge_onto", boom)
assert gmo.main(["--quiet", "new", "old"]) == 3


def test_dunder_main_entrypoint(repo, monkeypatch):
# `python -m git_merge_onto` runs __main__.py, which calls sys.exit(main()).
commit_file(repo, "f.txt", "x\n", "main")
sh(repo, "switch", "-q", "-c", "a")
commit_file(repo, "a.txt", "A\n", "a")
sh(repo, "switch", "-q", "-c", "b")
commit_file(repo, "b.txt", "B\n", "b")
monkeypatch.setattr(sys, "argv", ["git-merge-onto", "--quiet", "main", "a"])

with pytest.raises(SystemExit) as excinfo:
runpy.run_module("git_merge_onto.__main__", run_name="__main__")
assert excinfo.value.code == 0


def test_merge_abort_recovers_from_conflict(repo):
# The conflict message points the user at `git merge --abort`; it works only because
# the markers include ORIG_HEAD and MERGE_MODE, not just MERGE_HEAD/MERGE_MSG.
Expand Down