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
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 4.1.2 (unreleased)

- Fix #46: Git tags in branch option are now correctly detected and handled during updates. Previously, updating from one tag to another failed because tags were incorrectly treated as branches.
[jensens]

- Fix #53: Per-package target setting now correctly overrides default-target when constructing checkout paths.
[jensens]

Expand Down
34 changes: 31 additions & 3 deletions src/mxdev/vcs/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,25 @@ def git_switch_branch(
raise GitError(f"git checkout of branch '{branch}' failed.\n{stderr}")
return (stdout_in + stdout, stderr_in + stderr)

def git_is_tag(self, tag_name: str) -> bool:
"""Check if the given name is a git tag.

Returns True if tag_name exists as a tag in the repository.
"""
path = self.source["path"]
cmd = self.run_git(["tag", "-l", tag_name], cwd=path)
stdout, stderr = cmd.communicate()
if cmd.returncode != 0:
return False
# git tag -l returns the tag name if it exists, empty if not
return tag_name in stdout.strip().split("\n")

def git_update(self, **kwargs) -> typing.Union[str, None]:
name = self.source["name"]
path = self.source["path"]
self.output((logger.info, "Updated '%s' with git." % name))
# First we fetch. This should always be possible.
argv = ["fetch"]
argv = ["fetch", "--tags"] # Also fetch tags explicitly
update_git_submodules = self.source.get("submodules", kwargs["submodules"])
if update_git_submodules == "recursive":
argv.append("--recurse-submodules")
Expand All @@ -247,8 +260,23 @@ def git_update(self, **kwargs) -> typing.Union[str, None]:
if "rev" in self.source:
stdout, stderr = self.git_switch_branch(stdout, stderr)
elif "branch" in self.source:
stdout, stderr = self.git_switch_branch(stdout, stderr)
stdout, stderr = self.git_merge_rbranch(stdout, stderr)
# Check if 'branch' is actually a tag (#46)
branch_value = self.source["branch"]
if self.git_is_tag(branch_value):
# It's a tag - checkout directly without merge
cmd = self.run_git(["checkout", branch_value], cwd=path)
tag_stdout, tag_stderr = cmd.communicate()
if cmd.returncode != 0:
raise GitError(
f"git checkout of tag '{branch_value}' failed.\n{tag_stderr}"
)
stdout += tag_stdout
stderr += tag_stderr
self.output((logger.info, f"Switched to tag '{branch_value}'."))
else:
# It's a branch - use normal branch switch + merge
stdout, stderr = self.git_switch_branch(stdout, stderr)
stdout, stderr = self.git_merge_rbranch(stdout, stderr)
else:
# We may have specified a branch previously but not
# anymore. In that case, we want to revert to master.
Expand Down
57 changes: 57 additions & 0 deletions tests/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,60 @@ def test_update_verbose(mkgitrepo, src, capsys):
assert "Already up to date" in captured.out.replace("-", " ")
status = vcs_status(sources, verbose=True)
assert status == {"egg": ("clean", "## master...origin/master\n")}


def test_update_git_tag_to_new_tag(mkgitrepo, src):
"""Test that updating from one git tag to another works correctly.

This test reproduces issue #46: changing the branch option from one tag
to another tag should update the checkout to the new tag.

Regression in v4.x - worked in v2.x
"""
repository = mkgitrepo("repository")
# Create initial content and tag it as 1.0.0
repository.add_file("foo", msg="Initial")
repository("git tag 1.0.0", echo=False)

# Create more content and tag as 2.0.0
repository.add_file("bar", msg="Second")
repository("git tag 2.0.0", echo=False)

path = src / "egg"

# Initial checkout with tag 1.0.0
sources = {
"egg": dict(
vcs="git",
name="egg",
branch="1.0.0", # This is actually a tag, not a branch
url=str(repository.base),
path=str(path),
)
}
packages = ["egg"]
verbose = False

# Checkout at tag 1.0.0
vcs_checkout(sources, packages, verbose)
assert {x for x in path.iterdir()} == {path / ".git", path / "foo"}

# Verify we're at tag 1.0.0
result = repository.process.check_call(f"git -C {path} describe --tags", echo=False)
current_tag = result[0].decode("utf8").strip()
assert current_tag == "1.0.0"

# Now update the sources to use tag 2.0.0
sources["egg"]["branch"] = "2.0.0"

# Update should switch to tag 2.0.0
# BUG: This will fail because the code treats tags as branches
vcs_update(sources, packages, verbose)

# After update, we should have both foo and bar (tag 2.0.0)
assert {x for x in path.iterdir()} == {path / ".git", path / "foo", path / "bar"}

# Verify we're now at tag 2.0.0
result = repository.process.check_call(f"git -C {path} describe --tags", echo=False)
current_tag = result[0].decode("utf8").strip()
assert current_tag == "2.0.0"