Reading List

The most recent articles from a list of feeds I subscribe to.

The "current branch" in git

Hello! I know I just wrote a blog post about HEAD in git, but I’ve been thinking more about what the term “current branch” means in git and it’s a little weirder than I thought.

four possible definitions for “current branch”

  1. It’s what’s in the file .git/HEAD. This is how the git glossary defines it.
  2. It’s what git status says on the first line
  3. It’s what you most recently checked out with git checkout or git switch
  4. It’s what’s in your shell’s git prompt. I use fish_git_prompt so that’s what I’ll be talking about.

I originally thought that these 4 definitions were all more or less the same, but after chatting with some people on Mastodon, I realized that they’re more different from each other than I thought.

So let’s talk about a few git scenarios and how each of these definitions plays out in each of them. I used git version 2.39.2 (Apple Git-143) for all of these experiments.

scenario 1: right after git checkout main

Here’s the most normal situation: you check out a branch.

  1. .git/HEAD contains ref: refs/heads/main
  2. git status says On branch main
  3. The thing I most recently checked out was: main
  4. My shell’s git prompt says: (main)

In this case the 4 definitions all match up: they’re all main. Simple enough.

scenario 2: right after git checkout 775b2b399

Now let’s imagine I check out a specific commit ID (so that we’re in “detached HEAD state”).

  1. .git/HEAD contains 775b2b399fb8b13ee3341e819f2aaa024a37fa92
  2. git status says HEAD detached at 775b2b39
  3. The thing I most recently checked out was 775b2b399
  4. My shell’s git prompt says ((775b2b39))

Again, these all basically match up – some of them have truncated the commit ID and some haven’t, but that’s it. Let’s move on.

scenario 3: right after git checkout v1.0.13

What if we’ve checked out a tag, instead of a branch or commit ID?

  1. .git/HEAD contains ca182053c7710a286d72102f4576cf32e0dafcfb
  2. git status says HEAD detached at v1.0.13
  3. The thing I most recently checked out was v1.0.13
  4. My shell’s git prompt says ((v1.0.13))

Now things start to get a bit weirder! .git/HEAD disagrees with the other 3 indicators: git status, the git prompt, and what I checked out are all the same (v1.0.13), but .git/HEAD contains a commit ID.

The reason for this is that git is trying to help us out: commit IDs are kind of opaque, so if there’s a tag that corresponds to the current commit, git status will show us that instead.

Some notes about this:

  • If we check out the commit by its ID (git checkout ca182053c7710a286d72) instead of by its tag, what shows up in git status and in my shell prompt are exactly the same – git doesn’t actually “know” that we checked out a tag.
  • it looks like you can find the tags matching HEAD by running git describe HEAD --tags --exact-match (here’s the fish git prompt code)
  • You can see where git-prompt.sh added support for describing a commit by a tag in this way in commit 27c578885 in 2008.
  • I don’t know if it makes a difference whether the tag is annotated or not.
  • If there are 2 tags with the same commit ID, it gets a little weird. For example, if I add the tag v1.0.12 to this commit so that it’s with both v1.0.12 and v1.0.13, you can see here that my git prompt changes, and then the prompt and git status disagree about which tag to display:
bork@grapefruit ~/w/int-exposed ((v1.0.12))> git status
HEAD detached at v1.0.13

(my prompt shows v1.0.12 and git status shows v1.0.13)

scenario 4: in the middle of a rebase

Now: what if I check out the main branch, do a rebase, but then there was a merge conflict in the middle of the rebase? Here’s the situation:

  1. .git/HEAD contains c694cf8aabe2148b2299a988406f3395c0461742 (the commit ID of the commit that I’m rebasing onto, origin/main in this case)
  2. git status says interactive rebase in progress; onto c694cf8
  3. The thing I most recently checked out was main
  4. My shell’s git prompt says (main|REBASE-i 1/1)

Some notes about this:

  • I think that in some sense the “current branch” is main here – it’s what I most recently checked out, it’s what we’ll go back to after the rebase is done, and it’s where we’d go back to if I run git rebase --abort
  • in another sense, we’re in a detached HEAD state at c694cf8aabe2. But it doesn’t have the usual implications of being in “detached HEAD state” – if you make a commit, it won’t get orphaned! Instead, assuming you finish the rebase, it’ll get absorbed into the rebase and put somewhere in the middle of your branch.
  • it looks like during the rebase, the old “current branch” (main) is stored in .git/rebase-merge/head-name. Not totally sure about this though.

scenario 5: right after git init

What about when we create an empty repository with git init?

  1. .git/HEAD contains ref: refs/heads/main
  2. git status says On branch main (and “No commits yet”)
  3. The thing I most recently checked out was, well, nothing
  4. My shell’s git prompt says: (main)

So here everything mostly lines up, except that we’ve never run git checkout or git switch. Basically Git automatically switches to whatever branch was configured in init.defaultBranch.

scenario 6: a bare git repository

What if we clone a bare repository with git clone --bare https://github.com/rbspy/rbspy?

  1. HEAD contains ref: refs/heads/main
  2. git status says fatal: this operation must be run in a work tree
  3. The thing I most recently checked out was, well, nothing, git checkout doesn’t even work in bare repositories
  4. My shell’s git prompt says: (BARE:main)

So #1 and #4 match (they both agree that the current branch is “main”), but git status and git checkout don’t even work.

Some notes about this one:

  • I think HEAD in a bare repository mainly only really affects 1 thing: it’s the branch that gets checked out when you clone the repository. It’s also used when you run git log.
  • if you really want to, you can update HEAD in a bare repository to a different branch with git symbolic-ref HEAD refs/heads/whatever. I’ve never needed to do that though and it seems weird because git symbolic ref doesn’t check if the thing you’re pointing HEAD at is actually a branch that exists. Not sure if there’s a better way.

all the results

Here’s a table with all of the results:

.git/HEAD git status checked out prompt
1. checkout main ref: refs/heads/main On branch main main (main)
2. checkout 775b2b 775b2b399... HEAD detached at 775b2b39 775b2b399 ((775b2b39))
3. checkout v1.0.13 ca182053c... HEAD detached at v1.0.13 v1.0.13 ((v1.0.13))
4. inside rebase c694cf8aa... interactive rebase in progress; onto c694cf8 main (main|REBASE-i 1/1)
5. after git init ref: refs/heads/main On branch main n/a (main)
6. bare repository ref: refs/heads/main fatal: this operation must be run in a work tree n/a (BARE:main)

“current branch” doesn’t seem completely well defined

My original instinct when talking about git was to agree with the git glossary and say that HEAD and the “current branch” mean the exact same thing.

But this doesn’t seem as ironclad as I used to think anymore! Some thoughts:

  • .git/HEAD is definitely the one with the most consistent format – it’s always either a branch or a commit ID. The others are all much messier
  • I have a lot more sympathy than I used to for the definition “the current branch is whatever you last checked out”. Git does a lot of work to remember which branch you last checked out (even if you’re currently doing a bisect or a merge or something else that temporarily moves HEAD off of that branch) and it feels weird to ignore that.
  • git status gives a lot of helpful context – these 5 status messages say a lot more than just what HEAD is set to currently
    1. on branch main
    2. HEAD detached at 775b2b39
    3. HEAD detached at v1.0.13
    4. interactive rebase in progress; onto c694cf8
    5. on branch main, no commits yet

some more “current branch” definitions

I’m going to try to collect some other definitions of the term current branch that I heard from people on Mastodon here and write some notes on them.

  1. “the branch that would be updated if i made a commit”
  • Most of the time this is the same as .git/HEAD
  • Arguably if you’re in the middle of a rebase, it’s different from HEAD, because ultimately that new commit will end up on the branch in .git/rebase-merge/head-name
  1. “the branch most git operations work against”
  • This is sort of the same as what’s in .git/HEAD, except that some operations (like git status) will behave differently in some situations, like how git status won’t tell you the current branch if you’re in a bare repository

on orphaned commits

One thing I noticed that wasn’t captured in any of this is whether the current commit is orphaned or not – the git status message (HEAD detached from c694cf8) is the same whether or not your current commit is orphaned.

I imagine this is because figuring out whether or not a given commit is orphaned might take a long time in a large repository: you can find out if the current commit is orphaned with git branch --contains HEAD, and that command takes about 500ms in a repository with 70,000 commits.

Git will warn you if the commit is orphaned (“Warning: you are leaving 1 commit behind, not connected to any of your branches…”) when you switch to a different branch though.

that’s all!

I don’t have anything particularly smart to say about any of this. The more I think about git the more I can understand why people get confused.

The negotiation cycle.

On the work, and being endlessly clever.

On popover accessibility: what the browser does and doesn’t do

One of the premises of the new popover attribute is that it comes with general accessibility considerations “built in”. What does “built in accessibility” actually mean for browsers that support popover?

See also: Scott's post Popping preconceived popover ponderings and Hidde's talk on popovers, and other posts about popover semantics, positioning popovers and the difference with dialogs and other components.

About this post

NOTE: except for this note, this whole post was co-written with Scott O’Hara (thanks Scott!). See also Scott's post, popover accessibility and his post Popping preconceived popover ponderings. Whether you're a developer, designer or accessibility specialist, hearing “accessibility is built in” probably makes you want to know what exactly is built-in. For popover, this actually changed quite a bit over time, after discussions at Open UI and with WHATWG. At first, the plan was to introduce a popup element with built-in roles. Later, an attribute ended up making more sense (more on that in the post). For that attribute, and thanks to the great effort of Scott and others, some “accessibility guardrails” have now emerged. And they shipped in most browsers. I hope this post helps you understand better what accessibility is “built-in” when you use popover, and what is not.

In this post

  1. Accessibility semantics
  2. What browsers do (aria-expanded, aria-details, group, keyboard accessibility)
  3. What browsers don't do
  4. Conclusion

Accessibility semantics

The “built-in” accessibility of popover is in the addition of guardrails: browsers try to improve accessibility where they can. These guardrails exist mostly in the form of browsers augmenting accessibility semantics. Before we get into what those guardrails are, let's clarify what that term means.

Many features of HTML have some amount of accessibility semantics associated with them - e.g., roles, states and properties. This is information that a web page exposes, which browsers then pass on to platform accessibility APIs. They do this, so that assistive technologies can build UIs around them (see: How accessibility trees inform assistive tech). These semantics are sometimes baked into native HTML elements. For instance, headings and lists have implicit roles (heading and list, respectively). Other elements, like the checkbox input type, have an implicit role as well as additional states and properties. Developers can use HTML elements with such “built-in” semantics. But they can also set, overwrite and augment accessibility semantics more directly in their HTML structure, using WAI-ARIA.

The thing with the popover attribute is that it doesn’t have a built-in role. After all, it’s not an element. Its purpose is to only add “popover behaviour”, as discussed in Popover semantics. In that sense, popover is a bit like tabindex or contenteditable. These attributes also add behaviour: tabability and editability behaviours, respectively.

A major reason for this choice is that there are a number of components that exhibit popover behaviours. Examples include menus, “toast” messages, sub-navigation lists of links and tooltips. You can use popover on a specific element, then it will get that element's role. Or you can use it with a generic element, and add a role that best matches what you are building.

So, while the default role is ‘generally’ not handled by the attribute (more on that later), there are other semantics (properties and states) that the attribute will expose. Browsers can take care of those with some degree of confidence.

What browsers do

There are two semantics that the browser should take care of when you use popover, and its associated popovertarget attribute. Additionally, there is some keyboard focus behaviour that may also be handled automatically, depending on the type of popover you are using.

The aria-expanded state

First, aria-expanded. This state is exposed on the element that invokes the popover, currently limited to buttons (for a variety of reasons that would require a whole other article to talk about - so this is all you get right now). When a popover is invoked by / associated with a button with the popovertarget attribute, the browser will automatically convey whether the popover is in the expanded (rendered) state, or if it is in the collapsed (hidden) state. This is implemented in Edge, Chrome, Firefox and Safari.

For the following example, the ‘heyo’ button will automatically convey whether its associated popover list is in the expanded or collapsed state, based on whether the popover list is invoked as a popover.

<button popovertarget=p>
  Heyo
</button><ul 
  aria-label="Heyo subpages" 
  id=p 
  popover
></ul>

Note: the state won’t be applied if script, rather than the declarative attribute, does the opening on click of any button (or any other element). Needless to say: it also doesn’t work if there isn’t an invoking button, for instance, and script invokes this popover (because in that case, there isn’t any expanding going on). Additionally, if you force open your popover using CSS display block, then it will not be rendered as a popover - and thus the button will still communicate that the “popover” is in the collapsed state. Also, if you’re doing that - forcing your popover open with CSS - maybe you have some things you need to reconsider with your UI.

The aria-details relationship

When the popover doesn’t immediately follow its invoking button in the accessibility tree, browsers are supposed to create an aria-details relationship on the popover’s invoking button with its associated popover. At the time of writing, this is implemented in Chrome, Edge and Firefox.

For instance, in the following markup snippet an implicit aria-details relationship will be made with the button that invokes the popover, because the button and the popover are not immediate siblings in the accessibility tree.

<button popovertarget=foo>something</button>

<p>...</p>

<div role=whatever popover id=foo>...</div>

Similarly, an aria-details relationship will be made with the next markup snippet too, because even though the popover and its invoking button are siblings, the popover is a previous sibling to the invoking element, and it might not be understood which element is the popover, because it doesn’t immediately follow the element that invoked it.

<div role=whatever popover id=foo>...</div>

<button popovertarget=foo>something</button>

In contrast, the next two examples have no aria-details association because that would be unnecessary. For the first, the popover is the immediate next sibling in the accessibility tree (note divs are generic and often ignored when they do not provide information important to accessibility). For the second, the button is a descendant of the popover, so browsers do not need to tell users that the button they are interacting with is about the context they are within. That’d be silly.

<!--  
  example 1: 
  popover immediate sibling in acc tree 
-->
<button popovertarget=m>something</button>
<div class=presentational-styles-only><div role=menu popover id=m>...</div>
</div>

<!-- 
  example 2:
  button descendant of popoover
-->

<dialog popover id=d>
  <button popovertarget=d>close</button></dialog>

For more information on how aria-details works, check out the details in the ARIA spec.

Note: aria-details is often confused with aria-describedby. That makes sense, “details” and “descriptions” are very similar. However, these two properties expose different information. aria-describedby takes the associated element’s content, flattens it into a single text string, and exposes that as the ‘description’ or ‘hint’ of the element which the attribute is specified. In contrast, aria-details only informs a user that there is additional information about the current element. That might not seem useful, until you know that screen readers largely provide quick-keys to navigate to and from that associated element which provides the details.

At the time of writing, navigating into the referenced content using quick-keys is supported by JAWS and NVDA (release notes), but not VoiceOver.

Here’s a quick demo of that with JAWS 2023 in Edge 124. JAWS lets us jump to the details content if we press Alt + Ins + D:

In NVDA (2023.3.4), tested with Edge 124, it works slightly differently: when you press the shortcut (NVDA + D), we don't jump to the details content, but it is read out after the shortcut is pressed:

(see demo on CodePen; note: announcements and shortcuts depend on the user's settings, versions, etc)

In the following demo, you can see how the aria-details relationship works between popovers and their invokers (in JAWS 2023 with Edge 124):

(video contains screenshot of code, see demo on CodePen)

In summary: the aria-details association is not announced by JAWS when we focus the invoking button for the first time. This is because the corresponding popover is hidden, so the association isn't made yet. After we open the popover, JAWS announces the button's “has details” association while it is open, to hear it we navigate away and back. This is also how it works in NVDA, which, in addition, also requires you to switch to forms mode to actually hear the relationship announced.

Warning: even if the aria-details association is implemented, it may not be completely ironed out in how the UX behaves for people. For instance, there isn't currently a way for users to find out about the details relationship once it is established, like when the popover opened. It requires for the user to move away from the button and return to it, at which point the relationship is announced. Maybe it would be helpful if the browser would fire some kind of event, to let AT like JAWS know that an element representing details has appeared.

We mention this not to deter you from using popover or to indicate that anyone is doing something “wrong” here. Rather, this is a relatively new feature that people still need to figure out some of the UX kinks around. Feedback is welcome, and to help ensure the best UX is provided, please reach out to the necessary browsers / AT with your thoughts.

The group role

As mentioned above, popover can be used on any element, including elements that don’t have a built-in role, like div. But even without a role, it’s likely that the contents of a popover form some kind of meaningful whole. This is why in Chrome, Edge and Firefox, a role of group is automatically added to popovers if they would otherwise have no role, or a role of generic (for instance, divs and spans).

The group role is added, so that assistive technology can have the option to expose the boundaries of the popover that is being displayed. This can be important to users, because a popover is a behavior and visual treatment of its content. How is one to know where such content begins or ends if it doesn’t have boundaries to expose?

It’s important to know that an unnamed group is often ignored by screen readers. This is because otherwise the Internet would be riddled with unhelpful “group” announcements. (See also why Webkit made the decision to remove list semantics from lists that have been styled to not look like lists. These topics are related). Here though, it again comes down to what assistive technology wants to do. By exposing the group role for the popover, now the element can be named by authors, which will force the group role to be exposed in most cases. Then, if AT decided they want to do something special for popover groups, they now have the opportunity to do so.

Keyboard accessibility

One more aspect of “built-in accessibility” that browsers do for your popover, is take care of some keyboard behaviors.

Moving focus back to invoking element

Edge/Chrome, Firefox and Safari will all return focus to the invoking element when you close the popover (only if you are inside of it). This is useful, because if focus was on an element inside the popover, the default would be to return focus to the start of the document. Various groups of users would get lost, increasingly so on pages with a lot of content. Moving focus back to the invoking element helps ensure people can quickly return to what they were doing, rather than spending time having to re-navigate to where they think they were last.

Popover content in tab order

Desktop browsers also do something else: they add the popover content into the tab order just after the invoking button. Even if that’s not where the popover is in the DOM.

Imagine this DOM structure:

<button popovertarget=p>Open popover</button>

<p>content… content… <a href="#">link 1</a></p>

<p>content… content… <a href="#">link 2</a></p>

<div popover id="p"><a href="#">link 3</a></div>

When the popover opens, and you press Tab, you might think you’d jump to “link 1”, the next interactive element in the DOM. Except, in desktop browsers, you will jump to “link 3” instead. The browser basically moves the popover content’s position in tab order to just after its invoking button. This takes it out of its expected position in the tab order. That improves the user experience, because it is likely that upon opening the popover, users will want to interact with its contents.

Keep in mind: browsers adjust the Tab order for instances like this, but they don't adjust the placement of the content in the accessibility tree. This is why the aria-details association was implemented. This special Tab order behavior helps ensure logical focus order for keyboard accessibility. However, we should still strive to make sure our popovers come after the invoking element in the DOM.

But since there will be times where the exact location of the popover in the DOM may be out of one’s control, this behavior is still quite welcome. For instance, if the popover happens to be far away in the DOM, having to go through the rest of the document before reaching the popover would be a nuisance. It would be highly unexpected and unwanted to have to navigate through all other focusable elements in the DOM, prior to the popover one just opened. WCAG 2.4.3 Focus Order requires focusable elements to receive focus in an order that “preserves meaning and operability”. This special browser Tab restructuring helps ensure that requirement can be met.

What browsers don’t do

We can keep this one short: the browser will not do anything apart from the behaviours listed above. Browsers will not add behaviors based on which elements you use or which role you add to them. The popover attribute is merely a first step for us to build new components.

Conclusion

The popover attribute provides a starting point for building popover-like interactions on the web. Browsers don't magically make your components accessible, that's not a thing. But there are some specific keyboard behaviours included with popover, as well as these semantics:

  • In specific cases, browsers set the aria-expanded state and/or set the aria-details relationship on the invoker.
  • Browsers apply the group role to the popover content if it doesn’t have a role of its own, so that assistive technologies can expose their boundaries.

Browser support note: at the time of writing, Safari only sets aria-expanded, not aria-details. It also doesn't add a group role fallback.


Originally posted as On popover accessibility: what the browser does and doesn’t do on Hidde's blog.

Reply via email

Why does an extraneous build step make my Zig app 10x faster?

For the past few months, I’ve been curious about two technologies: the Zig programming language and Ethereum cryptocurrency. To learn more about both, I’ve been using Zig to write a bytecode interpreter for the Ethereum Virtual Machine.

Zig is a great language for performance optimization, as it gives you fine-grained control over memory and control flow. To motivate myself, I’ve been benchmarking my Ethereum implementation against the official Go implementation.

TinyPilot: Month 44

New here?

Hi, I’m Michael. I’m a software developer and the founder of TinyPilot, an independent computer hardware company. I started the company in 2020, and it now earns $80-100k/month in revenue and employs six other people.

Every month, I publish a retrospective like this one to share how things are going with my business and my professional life overall.

Highlights

  • We completed the first-ever TinyPilot release where I didn’t perform any release task directly.
  • Publishing a release through delegation helped identify many undocumented or poorly conceived steps in our release process.
  • I’m continuing to enjoy writing a bytecode interpreter in Zig.

Goal grades

At the start of each month, I declare what I’d like to accomplish. Here’s how I did against those goals: