Summary
Creating a hierarchical tag like foo/bar under an existing branch name works in Git, but it introduces subtle problems. While Git permits the ref name, it mixes two separate namespaces (branches vs. tags) and can cause confusion, tooling breakage, and future ref‑creation failures.
Root Cause
- Git stores branches under
refs/heads/and tags underrefs/tags/. - A name such as
foo/barcan be interpreted as a sub‑ref offooiffoois a branch, because Git treats the path component after the slash as a child of the same namespace. - When
fooalready exists as a branch, Git refuses to createrefs/heads/foo/bar. - Tags live in a different namespace, so
git tag foo/barcreatesrefs/tags/foo/barwithout collision, even though the prefixfoomatches a branch name.
Why This Happens in Real Systems
- Namespace isolation: Git only checks for collisions inside the same namespace (
headsvs.tags). - Legacy compatibility: Early Git versions allowed overlapping names; the rule was added later only for branches to avoid ambiguous ref resolution.
- Human conventions: Teams often mirror directory‑like structures in tag names (e.g.,
release/2024.1) even when a branch of the same prefix exists.
Real-World Impact
- Ambiguous commands –
git rev-parse foo/barmay resolve to a tag, but scripts expecting a branch can misbehave. - CI/CD pipelines – Automatic branch detection (
git rev-parse --abbrev-ref HEAD) might pick up a tag with the same prefix, causing deployment of the wrong artifact. - Tooling incompatibility – GUIs, webhooks, or third‑party services may refuse to display or fetch refs that share a prefix across namespaces.
- Future ref creation errors – Adding a sub‑branch later (
git branch foo/bar/baz) will fail becausefoo/baralready exists as a tag, forcing a ref rename or deletion.
Example or Code (if necessary and relevant)
git branch foo
git tag foo/bar # creates refs/tags/foo/bar
git branch foo/bar # fails: fatal: cannot lock ref 'refs/heads/foo/bar': 'refs/heads/foo' exists
How Senior Engineers Fix It
- Keep namespaces separate: Reserve a clear prefix for tags (e.g.,
v/,release/) and for branches (e.g.,feature/,bugfix/). - Enforce naming conventions via repository policies or server‑side hooks that reject overlapping prefixes.
- Rename conflicting tags when a branch with the same prefix is needed, using
git tag -d old && git tag new. - Document the convention in the project’s contribution guide and embed linting checks in CI.
Why Juniors Miss It
- They assume Git treats any ref name as a flat list, overlooking the namespace distinction.
- They focus on the immediate success of
git tag foo/barand ignore downstream tooling that expects a clean separation. - Lack of experience with large, automated pipelines where ambiguous ref names cause subtle breakages.