Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Review Suggested Edit

You can't approve or reject suggested edits because you haven't yet earned the Edit Posts ability.

Approved.
This suggested edit was approved and applied to the post about 2 years ago by hkotsubo‭.

0 / 255
  • First we need to understand what a Git repository actually is. For that, [refer to this article](http://think-like-a-git.net/): it explains that a Git repository is actually a DAG (Directed Acyclic Graph). I'm not going into the mathematical details (which can be checked [here](https://en.wikipedia.org/wiki/Directed_acyclic_graph)), but basically, we can think of a Git repository as a series of nodes pointing to each other.
  • Some nodes are commits, and they point to other nodes, which can be another commit (each commit points to its parent(s) - yes, some merges can generate commits with more than one parent), or trees (structures that represent a directory/folder structure, which in turn can point to files or another trees):
  • ![Git repository](https://software.codidact.com/uploads/RGxVVJQrASEpfh5j7zLtsgHb)
  • All nodes are identified by their hash (those "big hex numbers" such as `618ce9936d015012ae55b95ac6afcc286d02682d`). But some commits can also have a "name"/label, called branches (as mentioned [here](https://software.codidact.com/posts/282752/282753#answer-282753), a branch is just a [pointer to a specific commit](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)).
  • For example, in the following diagram, the master branch points to "second commit", and new_branch points to "third commit". And "first commit" has no branches pointing to it:
  • ```none
  • +--------------+ +---------------+
  • | first commit | <-- | second commit | <-- master
  • +--------------+ +---------------+
  • +---------------+
  • | third commit | <-- new_branch
  • +---------------+
  • ```
  • ---
  • The HEAD is just a pointer to a specific commit (which, in turn, might or might not have a branch pointing to it). As a simplification, many people say that HEAD points to the "current branch" (probably because it's the most common use case), but **it can actually point to any commit, regardless of having a branch pointing to it** (more details below, in the ["*HEAD is not always the current branch*" section](#head-not-current-branch)).
  • Anyway, HEAD is used by Git to know where to add new commits. If my repository was like the diagram above, what would happen when I add a new commit?
  • If HEAD is pointing to master, it'll be added after "second commit":
  • ```none
  • Add new commit to branch master
  • +--------------+ +---------------+ +---------------+
  • | first commit | <-- | second commit | <-- | fourth commit | <-- master (HEAD)
  • +--------------+ +---------------+ +---------------+
  • +---------------+
  • | third commit | <-- new_branch
  • +---------------+
  • ```
  • But if HEAD is pointing to new_branch, it'll be added after "third commit":
  • ```none
  • Add new commit to branch new_branch
  • +--------------+ +---------------+
  • | first commit | <-- | second commit | <-- master
  • +--------------+ +---------------+
  • +---------------+
  • | third commit | <-- new_branch
  • +---------------+
  • +---------------+
  • | fourth commit | <-- new_branch (HEAD)
  • +---------------+
  • ```
  • To change the HEAD (AKA "to change where HEAD points to"), we do `git checkout [branch or commit]`. Therefore, `git checkout master` makes HEAD point to master, while `git checkout new_branch` makes HEAD point to new_branch.
  • ---
  • There are many ways to check where HEAD is pointing to. One is to check the `.git/HEAD` file (the `.git` folder is in the repository's root). For example, I've created a repository in my machine, made a commit, and this file now contains:
  • ```none
  • ref: refs/heads/master
  • ```
  • This means that HEAD is pointing to the master branch. To be more precise, it's pointing to another file: `.git/refs/heads/master`. And this file contains the hash of the respective commit:
  • ```none
  • 618ce9936d015012ae55b95ac6afcc286d02682d
  • ```
  • Of course there are other ways to check this information, such as `git status` (at the first line it'll say "_On branch master_"), or `git show HEAD`, that returns lots of info, including the hash of the commit HEAD is pointing to, or `git log -1`, etc.
  • ---
  • When switching to another branch (`git checkout new_branch`), the contents of `.git/HEAD` file changed to:
  • ```none
  • ref: refs/heads/new_branch
  • ```
  • Which means that now HEAD is pointing to new_branch, and in the respective file (`.git/refs/heads/new_branch`) we can see the hash of the commit it points to.
  • ---
  • <h1 id="head-not-current-branch">HEAD is not always the "current branch"</h1>
  • As already said, HEAD doesn't necessarily point to the "current branch". That's a simplification for the most common use case, when HEAD is pointing to a branch where new commits will be added.
  • But we can also make HEAD point to a commit that has no branches pointing to it. For example, suppose my repository is like this:
  • ```none
  • +--------------+ +---------------+
  • | 618ce99 | | 4dc1fe0 |
  • | first commit | <-- | second commit | <-- master (HEAD)
  • +--------------+ +---------------+
  • ```
  • HEAD is pointing to master (hash 4dc1fe0 - I'm using the "short hash" for the sake of simplicity). If I run `git checkout 618ce99`, a message like this will appear:[^1]
  • [^1]: I used Git 2.25.1 in Ubuntu 20. Other versions might give different messages (the concept of "detached HEAD" is the same, though).
  • ```none
  • Note: switching to '618ce99'.
  • You are in 'detached HEAD' state. You can look around, make experimental
  • changes and commit them, and you can discard any commits you make in this
  • state without impacting any branches by switching back to a branch.
  • If you want to create a new branch to retain commits you create, you may
  • do so (now or later) by using -c with the switch command. Example:
  • git switch -c <new-branch-name>
  • Or undo this operation with:
  • git switch -
  • Turn off this advice by setting config variable advice.detachedHead to false
  • HEAD is now at 618ce99 first
  • ```
  • This means that we are in **detached HEAD** state: HEAD is pointing to a commit that has no branches pointing to it.
  • In that case, the contents of `.git/HEAD` file is no longer "refs: etc", but the hash itself. Therefore, HEAD is not pointing to a branch.
  • As the message above says, it's still possible to add commits and create a new branch (which will make HEAD not be detached anymore), or you can simply discard everything, without affecting the other existing branches.
  • > _Note: The message above contains instructions to use `git switch`, because I'm using Git >= 2.23.0. For older versions, the message will be different because the `switch` command didn't exist._
  • ---
  • Therefore, HEAD doesn't necessarily point to a branch. "Pointing to a branch" is the most common use case, but it's not the only possible one. Well, maybe I'm being too pedantic, after all everybody understands when you say that HEAD points to the "current branch" (and a detached HEAD would be a "special case" that you won't need in "most cases").
  • The thing is, HEAD always points to a specific commit, either directly (when it's detached), or indirectly (when it points to a branch).
  • Yeah, when HEAD points to a branch, it's **indirectly** pointing to a commit. Let's take the same repository as an example:
  • ```none
  • +--------------+ +---------------+
  • | 618ce99 | | 4dc1fe0 |
  • | first commit | <-- | second commit | <-- master (HEAD)
  • +--------------+ +---------------+
  • ```
  • The contents of `.git/HEAD` file is `ref: refs/heads/master`, and the file `.git/refs/heads/master` contains the hash 4dc1fe0.
  • After adding a new commit, the repository will be:
  • ```none
  • +--------------+ +---------------+ +--------------+
  • | 618ce99 | | 4dc1fe0 | | 630d06b |
  • | first commit | <-- | second commit | <-- | third commit | <-- master (HEAD)
  • +--------------+ +---------------+ +--------------+
  • ```
  • The contents of `.git/HEAD` file **will remain the same** (`ref: refs/heads/master`), and only the contents of the branch file (`.git/refs/heads/master`) will change, to contain the new commit's hash (630d06b). Hence, the branch is changed (it points to another commit), but the HEAD stays the same (it still points to the same branch).
  • Again, maybe that's too pedantic, because "everybody understands" if we say that HEAD is pointing to the "last commit of the current branch" (or just to the "current branch"). But IMO that's an important detail: HEAD (when it's non-detached) points to a branch, and the branch points to a commit. After adding a new commit, the branch points to this new commit, but HEAD keeps pointing to the same branch (we could say that "branch changes, HEAD doesn't").
  • And this is true for any operation that makes the branch point to a different commit. For example, if I do `git reset --hard 618ce99`, master will point to "first commit" and the repository will be like this:
  • ```none
  • +--------------+
  • | 618ce99 |
  • | first commit | <-- master (HEAD)
  • +--------------+
  • ```
  • And again, the contents of `.git/HEAD` file remains the same (`ref: refs/heads/master` - HEAD keeps pointing to the same branch), and only the contents of `.git/refs/heads/master` file is changed (now it contains the hash 618ce99).
  • ---
  • > _Note: for Git >= 2.23.0, you can use `git switch branchname` instead of `git checkout branchname`. And for detached HEAD, use `git switch commit_hash --detached`._
  • First, we need to understand what a Git repository actually is. For that, [refer to this article](http://think-like-a-git.net/): it explains that a Git repository is actually a DAG (Directed Acyclic Graph). I'm not going into the mathematical details (which can be checked [here](https://en.wikipedia.org/wiki/Directed_acyclic_graph)), but basically, we can think of a Git repository as a series of nodes pointing to each other.
  • Some nodes are commits, and they point to other nodes, which can be another commit (each commit points to its parent(s) - yes, some merges can generate commits with more than one parent), or trees (structures that represent a directory/folder structure, which in turn can point to files or another trees):
  • ![Git repository](https://software.codidact.com/uploads/RGxVVJQrASEpfh5j7zLtsgHb)
  • All nodes are identified by their hash (those "big hex numbers" such as `618ce9936d015012ae55b95ac6afcc286d02682d`). But some commits can also have a "name"/label, called branches (as mentioned [here](https://software.codidact.com/posts/282752/282753#answer-282753), a branch is just a [pointer to a specific commit](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)).
  • For example, in the following diagram, the master branch points to "second commit", and new_branch points to "third commit". And "first commit" has no branches pointing to it:
  • ```none
  • +--------------+ +---------------+
  • | first commit | <-- | second commit | <-- master
  • +--------------+ +---------------+
  • +---------------+
  • | third commit | <-- new_branch
  • +---------------+
  • ```
  • ---
  • The HEAD is just a pointer to a specific commit (which, in turn, might or might not have a branch pointing to it). As a simplification, many people say that HEAD points to the "current branch" (probably because it's the most common use case), but **it can actually point to any commit, regardless of having a branch pointing to it** (more details below, in the ["*HEAD is not always the current branch*" section](#head-not-current-branch)).
  • Anyway, HEAD is used by Git to know where to add new commits. If my repository was like the diagram above, what would happen when I add a new commit?
  • If HEAD is pointing to master, it'll be added after "second commit":
  • ```none
  • Add new commit to branch master
  • +--------------+ +---------------+ +---------------+
  • | first commit | <-- | second commit | <-- | fourth commit | <-- master (HEAD)
  • +--------------+ +---------------+ +---------------+
  • +---------------+
  • | third commit | <-- new_branch
  • +---------------+
  • ```
  • But if HEAD is pointing to new_branch, it'll be added after "third commit":
  • ```none
  • Add new commit to branch new_branch
  • +--------------+ +---------------+
  • | first commit | <-- | second commit | <-- master
  • +--------------+ +---------------+
  • +---------------+
  • | third commit | <-- new_branch
  • +---------------+
  • +---------------+
  • | fourth commit | <-- new_branch (HEAD)
  • +---------------+
  • ```
  • To change the HEAD (AKA "to change where HEAD points to"), we do `git checkout [branch or commit]`. Therefore, `git checkout master` makes HEAD point to master, while `git checkout new_branch` makes HEAD point to new_branch.
  • ---
  • There are many ways to check where HEAD is pointing to. One is to check the `.git/HEAD` file (the `.git` folder is in the repository's root). For example, I've created a repository in my machine, made a commit, and this file now contains:
  • ```none
  • ref: refs/heads/master
  • ```
  • This means that HEAD is pointing to the master branch. To be more precise, it's pointing to another file: `.git/refs/heads/master`. And this file contains the hash of the respective commit:
  • ```none
  • 618ce9936d015012ae55b95ac6afcc286d02682d
  • ```
  • Of course, there are other ways to check this information, such as `git status` (at the first line it'll say "_On branch master_"), or `git show HEAD`, that returns lots of info, including the hash of the commit HEAD is pointing to, or `git log -1`, etc.
  • ---
  • When switching to another branch (`git checkout new_branch`), the contents of `.git/HEAD` file changed to:
  • ```none
  • ref: refs/heads/new_branch
  • ```
  • Which means that now HEAD is pointing to new_branch, and in the respective file (`.git/refs/heads/new_branch`) we can see the hash of the commit it points to.
  • ---
  • <h1 id="head-not-current-branch">HEAD is not always the "current branch"</h1>
  • As already said, HEAD doesn't necessarily point to the "current branch". That's a simplification for the most common use case, when HEAD is pointing to a branch where new commits will be added.
  • But we can also make HEAD point to a commit that has no branches pointing to it. For example, suppose my repository is like this:
  • ```none
  • +--------------+ +---------------+
  • | 618ce99 | | 4dc1fe0 |
  • | first commit | <-- | second commit | <-- master (HEAD)
  • +--------------+ +---------------+
  • ```
  • HEAD is pointing to master (hash 4dc1fe0 - I'm using the "short hash" for the sake of simplicity). If I run `git checkout 618ce99`, a message like this will appear:[^1]
  • [^1]: I used Git 2.25.1 in Ubuntu 20. Other versions might give different messages (the concept of "detached HEAD" is the same, though).
  • ```none
  • Note: switching to '618ce99'.
  • You are in 'detached HEAD' state. You can look around, make experimental
  • changes and commit them, and you can discard any commits you make in this
  • state without impacting any branches by switching back to a branch.
  • If you want to create a new branch to retain commits you created, you may
  • do so (now or later) by using -c with the switch command. Example:
  • git switch -c <new-branch-name>
  • Or undo this operation with:
  • git switch -
  • Turn off this advice by setting config variable advice.detachedHead to false
  • HEAD is now at 618ce99 first
  • ```
  • This means that we are in **detached HEAD** state: HEAD is pointing to a commit that has no branches pointing to it.
  • In that case, the contents of `.git/HEAD` file is no longer "refs: etc", but the hash itself. Therefore, HEAD is not pointing to a branch.
  • As the message above says, it's still possible to add commits and create a new branch (which will make HEAD not be detached anymore), or you can simply discard everything, without affecting the other existing branches.
  • > _Note: The message above contains instructions to use `git switch`, because I'm using Git >= 2.23.0. For older versions, the message will be different because the `switch` command didn't exist._
  • ---
  • Therefore, HEAD doesn't necessarily point to a branch. "Pointing to a branch" is the most common use case, but it's not the only possible one. Well, maybe I'm being too pedantic, after all everybody understands when you say that HEAD points to the "current branch" (and a detached HEAD would be a "special case" that you won't need in "most cases").
  • The thing is, HEAD always points to a specific commit, either directly (when it's detached), or indirectly (when it points to a branch).
  • Yeah, when HEAD points to a branch, it's **indirectly** pointing to a commit. Let's take the same repository as an example:
  • ```none
  • +--------------+ +---------------+
  • | 618ce99 | | 4dc1fe0 |
  • | first commit | <-- | second commit | <-- master (HEAD)
  • +--------------+ +---------------+
  • ```
  • The contents of `.git/HEAD` file is `ref: refs/heads/master`, and the file `.git/refs/heads/master` contains the hash 4dc1fe0.
  • After adding a new commit, the repository will be:
  • ```none
  • +--------------+ +---------------+ +--------------+
  • | 618ce99 | | 4dc1fe0 | | 630d06b |
  • | first commit | <-- | second commit | <-- | third commit | <-- master (HEAD)
  • +--------------+ +---------------+ +--------------+
  • ```
  • The contents of `.git/HEAD` file **will remain the same** (`ref: refs/heads/master`), and only the contents of the branch file (`.git/refs/heads/master`) will change, to contain the new commit's hash (630d06b). Hence, the branch is changed (it points to another commit), but the HEAD stays the same (it still points to the same branch).
  • Again, maybe that's too pedantic, because "everybody understands" if we say that HEAD is pointing to the "last commit of the current branch" (or just to the "current branch"). But IMO that's an important detail: HEAD (when it's non-detached) points to a branch, and the branch points to a commit. After adding a new commit, the branch points to this new commit, but HEAD keeps pointing to the same branch (we could say that "branch changes, HEAD doesn't").
  • And this is true for any operation that makes the branch point to a different commit. For example, if I do `git reset --hard 618ce99`, master will point to "first commit" and the repository will be like this:
  • ```none
  • +--------------+
  • | 618ce99 |
  • | first commit | <-- master (HEAD)
  • +--------------+
  • ```
  • And again, the contents of `.git/HEAD` file remains the same (`ref: refs/heads/master` - HEAD keeps pointing to the same branch), and only the contents of `.git/refs/heads/master` file is changed (now it contains the hash 618ce99).
  • ---
  • > _Note: for Git >= 2.23.0, you can use `git switch branchname` instead of `git checkout branchname`. And for detached HEAD, use `git switch commit_hash --detached`._

Suggested about 2 years ago by Ethan‭