make hglabs bookmarks behave more like git/github branches workflows

Commit f0e776786838 · Harrison Erd · 2026-05-05 10:50 -0400

Changeset
f0e776786838b0ba4763aea4f2818c888732369e

View source at this commit

Comments

No comments yet.

Log in to comment

Diff

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -29,6 +29,24 @@
 hg push http://<user>@127.0.0.1:8080/hg/<user>/<repo>
 ```
 
+For a Git-like branch workflow, keep the main line on Mercurial's special `@`
+bookmark and put feature work on separate bookmarks:
+
+```sh
+hg bookmark @
+hg push -B @
+
+hg update @
+hg bookmark feature/xyz
+# edit files
+hg commit -m "Implement xyz"
+hg push -B feature/xyz
+```
+
+HgLab treats `@` as the repository's default code ref when it exists. The
+feature bookmark remains browsable from the Bookmarks tab without replacing the
+unqualified repository view.
+
 ## Configuration
 
 - `SECRET_KEY`: Bottle signed-cookie secret.
diff --git a/app.py b/app.py
--- a/app.py
+++ b/app.py
@@ -76,6 +76,7 @@
 REF_PICKER_TABS = {"overview", "source", "commits", "tags", "branches", "bookmarks"}
 REF_QUERY_KEYS = {"ref", "ref_type", "ref_value"}
 REF_VALUE_SEPARATOR = "|"
+DEFAULT_BOOKMARK_NAME = "@"
 SCRIPT_STYLE_RE = re.compile(r"(?is)<(script|style)\b[^>]*>.*?</\1>")
 RESERVED_USERNAMES = {"dashboard", "favicon.ico", "hg", "login", "logout", "new", "settings", "signup", "static", "harrisonerd"}
 WRITE_HG_COMMANDS = {"pushkey", "unbundle"}
@@ -1670,7 +1671,19 @@
     return bookmarks
 
 
+def default_bookmark_ref(path):
+    for bookmark in list_repo_bookmarks(path):
+        if bookmark["name"] == DEFAULT_BOOKMARK_NAME:
+            selected = dict(bookmark)
+            selected["is_default"] = True
+            return selected
+    return None
+
+
 def default_code_ref(path):
+    selected_bookmark = default_bookmark_ref(path)
+    if selected_bookmark:
+        return selected_bookmark
     for branch in list_repo_branches(path):
         if branch["name"] == "default" and not branch["closed"]:
             selected = dict(branch)
diff --git a/tests/test_app.py b/tests/test_app.py
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -694,6 +694,38 @@
     assert isolated_app.is_ancestor(path, isolated_app.NULL_REV, node)
 
 
+def test_default_code_ref_prefers_at_bookmark_for_git_like_bookmark_branches(isolated_app):
+    owner = create_user("alice")
+    isolated_app.create_repository(owner, "demo", "")
+    path = isolated_app.repo_path("alice", "demo")
+    default_node = commit_file(path, "README.md", "# Demo\n", message="initial", user="alice")
+    isolated_app.run_hg(["bookmark", "-r", default_node, isolated_app.DEFAULT_BOOKMARK_NAME], cwd=path)
+    isolated_app.run_hg(["update", isolated_app.DEFAULT_BOOKMARK_NAME], cwd=path)
+    isolated_app.run_hg(["bookmark", "feature/xyz"], cwd=path)
+    feature_node = commit_file(path, "feature.txt", "feature work\n", message="feature work", user="alice")
+
+    branches = {branch["name"]: branch for branch in isolated_app.list_repo_branches(path)}
+    default_ref = isolated_app.default_code_ref(path)
+    feature_query = urlencode({"ref_type": isolated_app.REF_TYPE_BOOKMARK, "ref": "feature/xyz"})
+    client = WsgiClient(isolated_app.app)
+
+    assert branches["default"]["node"] == feature_node
+    assert default_ref["type"] == isolated_app.REF_TYPE_BOOKMARK
+    assert default_ref["name"] == isolated_app.DEFAULT_BOOKMARK_NAME
+    assert default_ref["node"] == default_node
+    assert isolated_app.hg_files(path, default_ref["node"]) == ["README.md"]
+    assert "feature.txt" in isolated_app.hg_files(path, feature_node)
+
+    default_response = client.get("/alice/demo/src")
+    feature_response = client.get(f"/alice/demo/src?{feature_query}")
+
+    assert default_response.status_code == 200
+    assert "README.md" in default_response.text
+    assert "feature.txt" not in default_response.text
+    assert feature_response.status_code == 200
+    assert "feature.txt" in feature_response.text
+
+
 def test_create_pull_request_between_fork_repositories(isolated_app):
     owner = create_user("alice")
     author = create_user("bob")