Add support for Fossil SCM branch names in vc.py. (#5046)

Add support for Fossil VCS in vc.py.

The prompt now shows the currently active Fossil branch, while inside a Fossil checkout.
This commit is contained in:
Thomas Hess 2023-03-02 08:05:22 +01:00 committed by GitHub
parent 237e231e7f
commit cb95f0e487
Failed to generate hash of commit
3 changed files with 108 additions and 20 deletions

24
news/vc.rst Normal file
View file

@ -0,0 +1,24 @@
**Added:**
* Display the current branch of Fossil VCS checkouts in the prompt,
similar to git and hg.
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -1,5 +1,7 @@
import os
import subprocess as sp
import textwrap
from typing import Dict, List
from pathlib import Path
from unittest.mock import Mock
@ -8,7 +10,19 @@ import pytest
from xonsh.prompt import vc
# Xonsh interaction with version control systems.
VC_BRANCH = {"git": {"master", "main"}, "hg": {"default"}}
VC_BRANCH = {
"git": {"master", "main"},
"hg": {"default"},
"fossil": {"trunk"},
}
VC_INIT: Dict[str, List[List[str]]] = {
# A sequence of commands required to initialize a repository
"git": [["init"]],
"hg": [["init"]],
# Fossil "init" creates a central repository file with the given name,
# "open" creates the working directory at another, arbitrary location.
"fossil": [["init", "test.fossil"], ["open", "test.fossil"]]
}
@pytest.fixture(params=VC_BRANCH.keys())
@ -20,28 +34,32 @@ def repo(request, tmpdir_factory):
temp_dir = Path(tmpdir_factory.mktemp("dir"))
os.chdir(temp_dir)
try:
sp.call([vc, "init"])
for init_command in VC_INIT[vc]:
sp.call([vc] + init_command)
except FileNotFoundError:
pytest.skip(f"cannot find {vc} executable")
if vc == "git":
git_config = temp_dir / ".git/config"
git_config.write_text(
"""
[user]
name = me
email = my@email.address
[init]
defaultBranch = main
"""
)
# git needs at least one commit
Path("test-file").touch()
sp.call(["git", "add", "test-file"])
sp.call(["git", "commit", "-m", "test commit"])
_init_git_repository(temp_dir)
return {"vc": vc, "dir": temp_dir}
def _init_git_repository(temp_dir):
git_config = temp_dir / ".git/config"
git_config.write_text(textwrap.dedent(
"""\
[user]
name = me
email = my@email.address
[init]
defaultBranch = main
"""
))
# git needs at least one commit
Path("test-file").touch()
sp.call(["git", "add", "test-file"])
sp.call(["git", "commit", "-m", "test commit"])
@pytest.fixture
def set_xenv(xession, monkeypatch):
def _wrapper(path):
@ -52,8 +70,15 @@ def set_xenv(xession, monkeypatch):
def test_test_repo(repo):
test_vc_dir = repo["dir"] / ".{}".format(repo["vc"])
assert test_vc_dir.is_dir()
if repo["vc"] == "fossil":
# Fossil stores the check-out meta-data in a special file within the open check-out.
# At least one of these below must exist
metadata_file_names = {".fslckout", "_FOSSIL_"} # .fslckout on Unix, _FOSSIL_ on Windows
existing_files = set(file.name for file in repo["dir"].iterdir())
assert existing_files.intersection(metadata_file_names)
else:
test_vc_dir = repo["dir"] / ".{}".format(repo["vc"])
assert test_vc_dir.is_dir()
if repo["vc"] == "git":
test_file = repo["dir"] / "test-file"
assert test_file.exists()

View file

@ -124,6 +124,27 @@ def get_hg_branch(root=None):
return branch
def _run_fossil_cmd(cmd):
timeout = XSH.env.get("VC_BRANCH_TIMEOUT")
result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, timeout=timeout)
return result
def get_fossil_branch():
"""Attempts to find the current fossil branch. If this could not
be determined (timeout, not in a fossil checkout, etc.) then this returns None.
"""
# from fossil branch --help: "fossil branch current: Print the name of the branch for the current check-out"
cmd = "fossil branch current".split()
try:
branch = xt.decode_bytes(_run_fossil_cmd(cmd))
except (subprocess.CalledProcessError, OSError):
branch = None
else:
branch = RE_REMOVE_ANSI.sub("", branch.splitlines()[0])
return branch
_FIRST_BRANCH_TIMEOUT = True
@ -156,7 +177,7 @@ def _vc_has(binary):
def current_branch():
"""Gets the branch for a current working directory. Returns an empty string
if the cwd is not a repository. This currently only works for git and hg
if the cwd is not a repository. This currently only works for git, hg, and fossil
and should be extended in the future. If a timeout occurred, the string
'<branch-timeout>' is returned.
"""
@ -165,6 +186,8 @@ def current_branch():
branch = get_git_branch()
if not branch and _vc_has("hg"):
branch = get_hg_branch()
if not branch and _vc_has("fossil"):
branch = get_fossil_branch()
if isinstance(branch, subprocess.TimeoutExpired):
branch = "<branch-timeout>"
_first_branch_timeout_message()
@ -234,6 +257,20 @@ def hg_dirty_working_directory():
return None
def fossil_dirty_working_directory():
"""Returns whether the fossil checkout is dirty. If this could not
be determined (timeout, file not found, etc.) then this returns None.
"""
cmd = ["fossil", "changes"]
try:
status = _run_fossil_cmd(cmd)
except (subprocess.CalledProcessError, OSError):
status = None
else:
status = bool(status)
return status
def dirty_working_directory():
"""Returns a boolean as to whether there are uncommitted files in version
control repository we are inside. If this cannot be determined, returns
@ -244,6 +281,8 @@ def dirty_working_directory():
dwd = git_dirty_working_directory()
if dwd is None and _vc_has("hg"):
dwd = hg_dirty_working_directory()
if dwd is None and _vc_has("fossil"):
dwd = fossil_dirty_working_directory()
return dwd