HN Debrief

git's –end-of-options Flag

  • Developer Tools
  • Programming
  • Security
  • Open Source

The post walks through a specific Git oddity that trips up shell scripts and humans alike: `--` normally means “stop parsing options,” but in many Git commands it already serves a different job, separating revisions from pathspecs. That leaves a hole when a branch or tag itself begins with `-`, which is why Git added the more explicit `--end-of-options` flag. The piece is not about a new feature so much as a forgotten one, and it shows how easy it is to write a command that looks correct yet still lets a revision name get parsed as an option.

If your tooling accepts user-controlled branch, tag, or file names, stop assuming `--` is sufficient in Git commands and test edge cases explicitly. More broadly, treat Git’s CLI as a leaky interface layer and consider wrapping it with safer abstractions for everyday developer workflows.

Discussion mood

Mostly negative about Git’s CLI. People accepted the technical reason for `--end-of-options`, but saw it as another reminder that Git’s command interface is historically inconsistent, easy to misuse, and risky in scripts unless you handle odd branch and file names very carefully.

Key insights

  1. 01

    Git reused `--` too early

    Git likely painted itself into this corner by spending `--` on the revision-versus-path separator before it needed a true end-of-options marker. That choice clashes with a Unix convention that was already old by the time Git shipped, so `--end-of-options` reads less like a feature and more like retrofit damage control.

    When you design a CLI or API, do not repurpose familiar syntax for a near miss. Short-term convenience can turn into permanent ambiguity that every wrapper, tutorial, and user has to carry forever.

      Attribution:
    • dmurray #1
    • userbinator #1
    • stingraycharles #1
  2. 02

    Weird names become an input safety issue

    Branch names and filenames that start with dashes are not just self-inflicted weirdness. In hosted repos and automation, those strings can be user-controlled, which turns Git argument parsing into a real trust boundary. The point is not that an attacker “has your git.” It is that your code may feed hostile names into Git commands.

    Audit any service, hook, bot, or CI step that interpolates ref names or paths into Git commands. Treat those values like untrusted input and use Git’s explicit delimiters instead of relying on happy-path naming.

      Attribution:
    • zanecodes #1
    • kangalioo #1
    • reaperducer #1
  3. 03

    Abstractions beat memorizing Git CLI quirks

    People who use Git heavily still dodge the stock CLI by leaning on Magit or good graph-oriented GUIs. The useful framing here is that a GUI is not hiding the truth if it shows the commit graph and staging state clearly. In many cases it exposes Git’s actual model better than a pile of subcommands and flags does.

    For team productivity, optimize for tools that make refs, commits, staging, and rebases visible. Requiring everyone to internalize Git’s command grammar is not the same as teaching them Git’s model.

      Attribution:
    • ulrikrasmussen #1
    • IshKebab #1 #2
  4. 04

    Git’s data model outlived its interface

    Several commenters sharpened an old point about Git’s architecture. The durable part is the object model and the plumbing-versus-porcelain split, not the human CLI that grew around it. That is why newer systems like Jujutsu, Sapling, and git-butler can reuse Git’s storage ideas while trying to fix the user experience.

    If you are building developer tooling, separate the stable backend model from the operator interface. That makes it easier to ship safer UX without rewriting the whole storage and sync stack.

      Attribution:
    • pydry #1
    • edelbitter #1
    • mhh__ #1
  5. 05

    Help flags still reveal command parsing oddities

    One commenter checked current Git behavior and found that help output still depends on where `-h` appears. `git -h branch`, `git --help branch`, and `git branch --help` behave like asking the top-level help system about `branch`, while `git branch -h` asks the subcommand for short help. That small example captures the larger issue. Git command parsing often depends on which layer sees the token first.

    Do not assume positional equivalence in Git just because two forms look similar. When documenting internal tooling or onboarding users, give the exact command form you expect them to run.

      Attribution:
    • freehorse #1

Against the grain

  1. 01

    The explicit flag is ugly but clear

    `--end-of-options` was defended as a discoverable fix rather than a fresh complication. It has existed since 2019, says exactly what it does, and only matters in a narrow class of ambiguous commands. That does not redeem the original design, but it does mean the practical remedy is straightforward once you know the flag exists.

    Do not spend cycles wishing Git had chosen differently in 2005. Update your scripts, docs, and code generators to emit the explicit flag where ambiguity is possible.

      Attribution:
    • windward #1
  2. 02

    Escaping never fully disappears in text CLIs

    A few comments pushed back on the idea that this is uniquely Git’s fault. Once commands and data share the same token stream, some escape hatch is unavoidable, because any delimiter you pick can itself become data unless the system reserves an impossible value like NUL. That does not excuse Git’s specific choices, but it does limit how clean a pure text CLI can ever be.

    Expect textual command interfaces to need quoting, escaping, or explicit separators somewhere. If your workflow cannot tolerate that ambiguity, move critical operations behind typed APIs or structured interfaces.

      Attribution:
    • inigyou #1
    • yencabulator #1
    • Gormo #1

In plain english

CI
Continuous Integration, an automated process that runs tests and checks when code changes are submitted.
CLI
Command-line interface, a text-based way to run programs by typing commands and arguments.
GUI
Graphical User Interface, the visual desktop application layer of a tool.
injection surface
A place where untrusted input can be interpreted as commands or control data instead of plain data, creating a security risk.
Jujutsu
A newer version control tool, often written as `jj`, that can interoperate with Git while offering a different user interface and workflow model.
Magit
A popular Git interface for Emacs.
NUL
The null byte, a zero-valued byte often used as a special terminator in low-level systems and not normally allowed inside text strings or filenames on Unix-like systems.
plumbing
In Git, low-level commands and internal mechanisms intended as building blocks rather than everyday user-facing commands.
porcelain
In Git, the higher-level commands intended for normal human use, built on top of lower-level plumbing commands.
Sapling
A version control system developed by Meta that aims to improve usability while remaining compatible with Git-style workflows in some contexts.

Reference links

Background on Git UX

Standards and conventions

Alternative version control tooling

  • gitoxide
    Mentioned as the library Jujutsu uses for working with Git repositories locally.

Shells and structured command interfaces

  • Scsh
    Linked as an example of a shell built around a richer programming-language model, in contrast to plain text command interfaces.
  • libxo documentation
    Referenced as an example of Unix tools emitting structured output in multiple formats instead of only plain text.

Related culture and side references