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.
Post History
First, we need to understand what a Git repository actually is. For that, refer to this article: it explains that a Git repository is actually a DAG (Directed Acyclic Graph). I'm not going into the...
Answer
#6: Post edited
- 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`._
- 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`._
#5: Post edited
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`._
#4: Post edited
- 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.
- ---
- 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).
- 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`._
#3: Post edited
- 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.
- ---
- 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).
- 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.
- ---
- 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).
#2: Post edited
- 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).- 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.
- ---
# HEAD is not always the "current branch"- 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.
- ---
- 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).
- 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.
- ---
- 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).
#1: Initial revision
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). 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. --- # HEAD is not always the "current branch" 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. --- 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).