Detached head means you are no longer on a branch, you have checked out a single commit in the history (in this case the commit previous to HEAD, i.e. HEAD^).
If you want to delete your changes associated with the detached HEAD
You only need to checkout the branch you were on, e.g.
git checkout master
Next time you have changed a file and want to restore it to the state it is in the index, don’t delete the file first, just do
git checkout -- path/to/foo
This will restore the file foo to the state it is in the index.
If you want to keep your changes associated with the detached HEAD
- Run
git branch tmp
— this will save your changes in a new branch calledtmp
. - Run
git checkout master
- If you would like to incorporate the changes you made into
master
, rungit merge tmp
from themaster
branch. You should be on themaster
branch after runninggit checkout master
.
answered Apr 19, 2012 at 13:32
ralphtheninjaralphtheninja
124k20 gold badges108 silver badges120 bronze badges
30
If you have changed files you don’t want to lose, you can push them. I have committed them in the detached mode and after that you can move to a temporary branch to integrate later in master.
git commit -m "....."
git branch my-temporary-work
git checkout master
git merge my-temporary-work
Extracted from:
What to do with commit made in a detached head
answered Dec 9, 2013 at 20:05
Toni GamezToni Gamez
6,7491 gold badge22 silver badges18 bronze badges
9
A solution without creating a temporary branch.
How to exit (“fix”) detached HEAD state when you already changed something in this mode and, optionally, want to save your changes:
-
Commit changes you want to keep. If you want to take over any of the changes you made in detached HEAD state, commit them. Like:
git commit -a -m "your commit message"
-
Discard changes you do not want to keep. The hard reset will discard any uncommitted changes that you made in detached HEAD state:
git reset --hard
(Without this, step 3 would fail, complaining about modified uncommitted files in the detached HEAD.)
-
Check out your branch. Exit detached HEAD state by checking out the branch you worked on before, for example:
git checkout master
-
Take over your commits. You can now take over the commits you made in detached HEAD state by cherry-picking, as shown in my answer to another question.
git reflog git cherry-pick <hash1> <hash2> <hash3> …
answered Jun 11, 2013 at 13:23
taniustanius
12.2k3 gold badges44 silver badges57 bronze badges
2
Detached head means:
- You are no longer on a branch,
- You have checked out a single commit in the history
If you have no changes: you can switch to master by applying the following command
git checkout master
If you have changes that you want to keep:
In case of a detached HEAD, commits work like normal, except no named branch gets updated. To get master branch updated with your committed changes, make a temporary branch where you are (this way the temporary branch will have all the committed changes you have made in the detached HEAD), then switch to the master branch and merge the temporary branch with the master.
git branch temp
git checkout master
git merge temp
answered Aug 29, 2016 at 0:39
Razan PaulRazan Paul
13.4k3 gold badges67 silver badges60 bronze badges
2
HEAD is a pointer, and it points — directly or indirectly — to a particular commit:
Attached HEAD means that it is attached to some branch (i.e. it points to a branch).
Detached HEAD means that it is not attached to any branch, i.e. it points directly to some commit.
In other words:
- If it points to a commit directly, the HEAD is detached.
- If it points to a commit indirectly, (i.e. it points to a branch, which in turn points to a commit), the HEAD is attached.
To better understand situations with attached / detached HEAD, let’s show the steps leading to the quadruplet of pictures above.
We begin with the same state of the repository (pictures in all quadrants are the same):
Now we want to perform git checkout
— with different targets in the individual pictures (commands on top of them are dimmed to emphasize that we are only going to apply those commands):
This is the situation after performing those commands:
As you can see, the HEAD points to the target of the git checkout
command — to a branch (first 3 images of the quadruplet), or (directly) to a commit (the last image of the quadruplet).
The content of the working directory is changed, too, to be in accordance with the appropriate commit (snapshot), i.e. with the commit pointed (directly or indirectly) by the HEAD.
So now we are in the same situation as in the start of this answer:
answered Sep 27, 2019 at 23:29
MarianDMarianD
12.4k12 gold badges39 silver badges53 bronze badges
7
If you made changes and then realized that you are on a detached head, you can do: stash -> checkout master -> stash pop:
git stash
git checkout master # Fix the detached head state
git stash pop # Or for extra safety use 'stash apply' then later
# after fixing everything do 'stash drop'
You will have your uncommited changes and normal «attached» HEAD, like nothing happened.
answered Jan 1, 2015 at 22:40
mojubamojuba
11.6k7 gold badges51 silver badges70 bronze badges
4
Here’s what I just did after I realized I was on a detached head and had already made some changes.
I committed the changes.
$ git commit -m "..."
[detached HEAD 1fe56ad] ...
I remembered the hash (1fe56ad) of the commit. Then I checked out the branch I should have been on.
$ git checkout master
Switched to branch 'master'
Finally I applied the changes of the commit to the branch.
$ git cherry-pick 1fe56ad
[master 0b05f1e] ...
I think this is a bit easier than creating a temporary branch.
answered Aug 2, 2014 at 23:33
Philippe GerberPhilippe Gerber
17.3k6 gold badges44 silver badges40 bronze badges
6
When you check out a specific commit in git
, you end up in a detached head state…that is, your working copy no longer reflects the state of a named reference (like «master»). This is useful for examining the past state of the repository, but not what you want if you’re actually trying to revert changes.
If you have made changes to a particular file and you simply want to discard them, you can use the checkout
command like this:
git checkout myfile
This will discard any uncommitted changes and revert the file to whatever state it has in the head of your current branch. If you want to discard changes that you have already committed, you may want to use the reset
command. For example, this will reset the repository to the state of the previous commit, discarding any subsequent changes:
git reset --hard HEAD^
However, if you are sharing the repository with other people, a git reset
can be disruptive (because it erases a portion of the repository history). If you have already shared changes with other people, you generally want to look at git revert
instead, which generates an «anticommit» — that is, it creates a new commit that «undoes» the changes in question.
The Git Book has more details.
answered Apr 19, 2012 at 13:32
larskslarsks
257k40 gold badges373 silver badges371 bronze badges
1
Since «detached head state» has you on a temp branch, just use git checkout -
which puts you on the last branch you were on.
yyny
1,57317 silver badges20 bronze badges
answered Nov 20, 2015 at 19:30
MikeMike
1,8442 gold badges26 silver badges42 bronze badges
3
you probably did git reset --hard origin/your-branch
.
Try to just git checkout your-branch
answered Apr 1, 2020 at 11:00
Johnny CageJohnny Cage
5,0882 gold badges11 silver badges7 bronze badges
0
Being in «detached head» means that HEAD refers to a specific unnamed commit (as opposite to a named branch) (cf: https://git-scm.com/docs/git-checkout section Detached head).
In reality, this means that you have checked out a commit but there is no branch name associated with it.
You may choose to only create a new branch associated with your commit by
git branch new-branch-name
.
This allows you to save your current state in a new branch named new-branch-name
and not be in a detached head
state anymore.
Or if you would like to come back to the previous state, you need to select the branch that was selected before by
git checkout @{-1}
answered Feb 15, 2019 at 15:16
Pat. ANDRIAPat. ANDRIA
2,3021 gold badge11 silver badges27 bronze badges
To further clarify @Philippe Gerber’s answer, here it is:
Before cherry-pick
, a git checkout master
is necessary in this case. Furthermore, it is only needed with a commit
in detached head
.
answered Dec 6, 2017 at 13:39
TimoTimo
2,8303 gold badges29 silver badges27 bronze badges
Addendum
If the branch to which you wish to return was the last checkout that you had made, you can simply use checkout @{-1}
. This will take you back to your previous checkout.
Further, you can alias this command with, for example, git global --config alias.prev
so that you just need to type git prev
to toggle back to the previous checkout.
answered Jan 22, 2016 at 11:09
David BrowerDavid Brower
2,7982 gold badges24 silver badges30 bronze badges
1
Detached head means you have not checked out your branch properly or you have just checked out a single commit.
If you encounter such an issue then first stash your local changes so that you won’t lose your changes.
After that… checkout your desired branch using the command:
Let’s say you want branch MyOriginalBranch:
git checkout -b someName origin/MyOriginalBranch
answered Jan 13, 2020 at 6:35
To add to @ralphtheninja’s answer. If you get this message after using git checkout master
:
Please commit your changes or stash them before you switch branches.
Aborting
Then you can simply force the checkout using the -f flag as follows:
git checkout -f master
Apparently this will result in losing all the changes made in the detached mode. So be careful when using it.
answered Jan 25, 2022 at 20:12
SouhaibSouhaib
1091 silver badge4 bronze badges
This approach will potentially discard part of the commit history, but it is easier in case the merge of the old master branch and the current status is tricky, or you simply do not mind losing part of the commit history.
To simply keep things as currently are, without merging, turning the current detached HEAD into the master branch:
- Manually back up the repository, in case things go unexpectedly wrong.
- Commit the last changes you would like to keep.
- Create a temporary branch (let’s name it
detached-head
) that will contain the files in their current status:
git checkout -b detached-head
- (a) Delete the master branch if you do not need to keep it
git branch -D master
- (b) OR rename if you want to keep it
git branch -M master old-master
- Rename the temporary branch as the new master branch
git branch -M detached-head master
Credit: adapted from this Medium article by Gary Lai.
answered Sep 29, 2020 at 16:37
alexhgalexhg
6507 silver badges11 bronze badges
0
Git told me how to do it.
if you typed:
git checkout <some-commit_number>
Save the status
git add .
git commit -m "some message"
Then:
git push origin HEAD:<name-of-remote-branch>
answered Jan 31, 2019 at 19:30
Sterling DiazSterling Diaz
3,7212 gold badges30 silver badges35 bronze badges
1
I was in a similar situation.
For some reason I ended up with a detached head — I had made commits on the same path as the branch I thought I was on — eg HEAD was a child of the branch tag but for some reason the branch tag had stayed back at a historic commit… possibly because I had pushed??
It wouldn’t let me push because I wasn’t considered to be on the branch I thought I was on.
I didn’t want to change any of my history or do any cherry picking and I’d just spent about 8 weeks working on the branch so reset --hard
was making me a bit nervous!
The solution was just to do the following:
git branch -f myStuckBranch HEAD
git checkout myStuckBranch
You need to do the checkout even though HEAD and myStuckBranch are now pointing at the same thing because you are still considered to be in the detached head state (not on a branch)
I’m not an expert with git (having mostly used mercurial which would never create this weird situation) but my understanding of this command is that it just says
«change myStuckBranch to point at HEAD».
I routinely find myself using this command to merge in changes from master after fetching without having to swap my working directory — otherwise it tries to use the old (uninteresting) version of master:
git fetch
git branch -f master origin/master -- err yeah don't just ignore what's been going on remotely - eg point my master at the real master
git merge master -- merge the changes into my local branch
It’s a bit annoying to have to manually have to do that all the time but still better than having to change your working directory just to update another branch in order to merge in changes from it.
answered Mar 31, 2021 at 14:41
JonnyRaaJonnyRaa
7,2295 gold badges43 silver badges49 bronze badges
4
Normally HEAD
points to a branch. When it is not pointing to a branch instead when it points to a commit hash like 69e51
it means you have a detached HEAD. You need to point it two a branch to fix the issue. You can do two things to fix it.
- git checkout other_branch // Not possible when you need the code in that commit
hash
- create a new branch and point the commit hash to the newly created branch.
HEAD must point to a branch, not a commit hash is the golden rule.
answered Jan 5, 2020 at 7:39
Krishnadas PCKrishnadas PC
5,6012 gold badges54 silver badges49 bronze badges
1
This was a confusing thing to me when I started to work with git and later I figure out why this is happening and what is the best way to deal with such a situation.
The root cause for such occurrence is that normally git HEAD is always pointing to some branch and when you try to point the HEAD to some specific commit, you put HEAD into a detached HEAD state.
When HEAD is attached state —
cat .git/HEAD # output--> ref: refs/heads/master or ref: refs/heads/main
When HEAD is detached state —
cat .git/HEAD # output--> b96660a90cad75867453ebe1b8d11754bbb68b0e <commit hash>
Solution —
git stash # Temporarily shelves (or stashes) changes
git branch # Find your default branch
git switch master # Point HEAD to master or main branch
git stash pop # Apply all the changes you had previously
answered Sep 24, 2022 at 0:44
I wanted to keep my changes so, I just fix this doing…
git add .
git commit -m "Title" -m "Description"
(so i have a commit now example: 123abc)
git checkout YOURCURRENTBRANCH
git merge 123abc
git push TOYOURCURRENTBRANCH
that work for me
Paul Rooney
20.3k9 gold badges41 silver badges61 bronze badges
answered Dec 4, 2018 at 22:11
When you’re in a detached head situation and created new files, first make sure that these new files are added to the index, for example with:
git add .
But if you’ve only changed or deleted existing files, you can add (-a) and commit with a message (-m) at the the same time via:
git commit -a -m "my adjustment message"
Then you can simply create a new branch with your current state with:
git checkout -b new_branch_name
You’ll have a new branch and all your adjustments will be there in that new branch. You can then continue to push to the remote and/or checkout/pull/merge as you please.
answered Mar 21, 2019 at 22:15
DZetDZet
5293 silver badges10 bronze badges
Realizing I had a detached head without knowing how I managed to get it (like three commits away), I also found out that trying to merge
, rebase
or cherry-pick
triggered hundreds of merge-conflicts, so I took a different approach:
-
(Assuming everything is committed (working tree is «clean»))
-
Save my commit messages:
git log > /tmp/log
-
Save my working tree:
mkdir /tmp/backup && cp -a all_my files_and_directories /tmp/backup
-
Revert to
master
:git checkout master
-
Remove all the working files and directories:
rm ...
-
Use the backup:
cp -a /tmp/backup/. .
-
git add
andgit commit
using messages from saved/tmp/log
, maybe repeating it with different sub-sets of files…
The disadvantage is that you loose your commit history if one file was changed multiple times since master
, but in the end I had a clean master
.
answered Jul 27, 2020 at 12:21
U. WindlU. Windl
3,18323 silver badges51 bronze badges
Git : You are not currently on a branch.
Time to time Git shows :
To push the history leading to the current (detached HEAD)
state now, use
git push origin HEAD:<name-of-remote-branch>
It means :
- » HEAD have no branch «
To fix that run 2 commands :
git branch -f {{your_working_branch}} HEAD
— set branch to your headgit checkout {{your_working_branch}}
— checkout==switch branch
answered Oct 11, 2022 at 3:21
BrunoBruno
6,2755 gold badges40 silver badges47 bronze badges
The detached HEAD means that you are currently not on any branch. If you want to KEEP your current changes and simply create a new branch, this is what you do:
git commit -m "your commit message"
git checkout -b new_branch
Afterwards, you potentially want to merge this new branch with other branches. Always helpful is the git «a dog» command:
git log --all --decorate --oneline --graph
answered Apr 21, 2020 at 10:00
gebbissimogebbissimo
1,8222 gold badges22 silver badges31 bronze badges
git pull origin master
worked for me. It was just about giving remote and branch name explicitly.
answered May 14, 2014 at 14:14
This works for me, It will assign a new branch for detached head :
git checkout new_branch_name detached_head_garbage_name
lenooh
10.1k5 gold badges57 silver badges49 bronze badges
answered Mar 17, 2020 at 17:06
Alok GuptaAlok Gupta
1,75021 silver badges21 bronze badges
With git rebase you can move your HEAD to the desired commit
Suppose you have your branch in a detached state, like this:
* bfcb8f9 Commit 4
* 540a123 Commit 3
* 4356d64 Commit 2
| * fecb8d2 Commit 2
|/
| * 8012f45 Commit 2x
|/
| * 6676d15 (HEAD -> master) Commit 2 --amend
|/
* 1818f91 Commit 1
The detached head was created by rebasing by mistake, pointing to a detached commit, which was created previously due a git commit —amend command.
If you want to move your HEAD ref to the most recent commit, apply a rebase with the desired HASH commit you want to point to. In this example, the hash is of the most recent commit:
git rebase bfcb8f9
and this will leave your branch with its HEAD poiting to the desired commit (the most recent in this case):
* bfcb8f9 (HEAD -> master) Commit 4
* 540a123 Commit 3
* 4356d64 Commit 2 --amend
| * fecb8d2 Commit 2
|/
| * 8012f45 Commit 2x
|/
| * 6676d15 Commit 2
|/
* 1818f91 Commit 1
answered Jul 12, 2021 at 16:48
В этой статье пойдет речь про отсоединенное состояние указателя HEAD. Надо понимать что названия веток в Git — это указатели на коммиты. Имя ветки, допустим master, указывает на последний коммит в ветке master.
Существует так же множество других указателей и один из них HEAD. Это очень важный указатель. И вот о нем мы и поговорим.
Итак, у нас есть история коммитов:
И сейчас указатель ветки master и указатель HEAD указывают на последний коммит С4 в ветке master.
То же самое можно увидеть в Git:
Оба указателя master и HEAD указывают на коммит С4 (efaaf18).
Теперь передвинем указатель HEAD на коммит C2 командой
$ git checkout 7bbbd68
Вот мы и получили отсоединенный указатель HEAD. И Git нам любезно сообщил что снег башка попал…
На диаграмме это можно изобразить так:
То есть команда git checkout 7bbbd68 просто перенесла указатель HEAD на коммит C2 (7bbbbd68). И вместе с этим вернула в рабочем каталоге состояние файлов этого коммита. То есть мы откатились назад по истории коммитов.
И Git порекомендовал нам создать новую ветку, а так же сообщил хэш коммита на который сейчас указывает HEAD.
Все это можно увидеть в Git:
Состояние HEAD detached означает что HEAD указывает не на вершину какой либо ветки, а просто на какой-то коммит.
Посмотреть историю перемещения головы можно командой git reflog:
На скрине мы так же посмотрели состояние Git и он нам (аж красным) сообщил, что башка отсоединена на коммит 7bbbd68.
HEAD всегда должен указывать на вершину какой-либо ветки. Это очень важно. И вот почему.
Указатель HEAD по существу указывает на тот коммит, после которого будет сделан следующий коммит. И если в состоянии перемещенного HEAD мы сейчас сделаем еще один коммит, то у нас будет шанс потерять его, не в смысле что он будет не доступен, а что если мы не будем помнить хэш этого коммита, то мы ни когда не сможем на него переключится.
Давайте сделаем коммит и посмотрим что будет. Изменим файлик и посмотрим статус
Ну и делаем коммит
Из лога коммитов видно что сейчас HEAD указывает на коммит С5 (84b361c), но на этот коммит не указывает ни какая ветка, верней сказать ни какой указатель ветки.
Графически это можно изобразить так:
Мы по прежнему находимся в состоянии detached HEAD, о чем нам все время напоминает Git. Еще раз напомню что это означает что HEAD указывает не на вершину какой-либо ветки, а просто на коммит. В нашем случае на коммит С5 (84b361c).
Выйти из состояния detached HEAD очень легко, для этого надо переключится на какую-либо ветку или создать новую ветку.
Давайте переключимся на ветку master командой git checkout master
При переключении на ветку master, Git заботливо нас предупредил, что мы оставляем 1 commit, который не присоединен ни к какой ветке. Сообщил нам имя этого коммита – С5 и его хэш – 84b361c. И посоветовал, что возможно уже самое время создать новую ветку командой:
git branch new_branch_name 84b361c
Поскольку если мы сейчас не создадим ветку (указатель) на этом коммите, то мы можем его потерять.
Если сейчас дать команду просмотра лога коммитов, то мы коммит С5 в логе не увидим:
Таким образом мы можем “потерять” коммит С5, если забудем его хэш. Конечно, как уже говорилось, что в Git какой-либо сделанный коммит сложно потерять (но можно). И в данном случае мы можем посмотреть историю перемещения HEAD:
и в ней мы можем увидеть наш потерянный коммит, хотя мы его и не видели в истории коммитов (логе коммитов).
Теперь дадим команду
$ git branch lost_branch 84b361c
и посмотрим лог:
Теперь, все хорошо. На коммит С5 указывает ветка lost_branch
Графически это выглядит так:
Теперь мы легко можем переключится на ветку lost_branch и состояния HEAD deatached уже не будет, поскольку HEAD уже будет указывать на вершину ветки lost_branch.
Сейчас переключение произошло безболезненно, поскольку это вполне штатная работа в Git.
Ну и покажу это состояние графически:
На этом, с отсоединенным указателем HEAD пока все. Хотя есть еще варианты по отделению указателя HEAD в Git, но об этом как-нибудь в другой раз.
«Git detached head» is a weird error message to receive…
The following is a guest blog post written by Carlos Schults.
Newcomers to Git often get confused with some of the messages that the VCS tool throws at them. The “You are in ‘detached HEAD’ state” one is certainly one of the weirdest. After coming across this message, most people start furiously Googling “git detached HEAD,” “git detached HEAD fix,” or similar terms, looking for anything that might be of help. If that’s your case, you’ve come to the right place.
Here’s the first thing you should know: you haven’t done anything wrong. Your repo isn’t broken or anything like that. The expression “Detached HEAD” might sound somewhat bizarre, but it’s a perfectly valid repository state in Git. Sure, it’s not the normal state, which would be—you’ve guessed it!—when HEAD is attached. The second thing you need to know is that going back to normal is super easy. If you just want to do that and get on with your day, go to the “How Do I Fix a Detached Head in Git?” section of this post to see how it’s done.
But if you want to know more—and I guess you do—stick around and we’ll help you. What does HEAD mean in Git? What does it mean for it to be attached or detached? These are the kind of questions we’ll answer in this post. By the end of it, you’ll have a better understanding of Git’s fundamentals, and detached HEADs will never trouble you again. Let’s dig in.
Git Detached HEAD: Reproducing the “Problem”
Let’s start with a quick demo showing how to reach the detached HEAD state. We’ll create a repository and add some commits to it:
mkdir git-head-demo
cd git-head-demo
git init
touch file.txt
git add .
git commit -m "Create file"
echo "Hello World!" > file.txt
git commit -a -m "Add line to the file"
echo "Second file" > file2.txt
git add .
git commit -m "Create second file"
With the commands above, we’ve created a new folder with a new repository inside it. Then we created a new empty file and committed that with the message “Create file.” Next, we added a line to that file and committed the change, with the message “Add a line to the file.” Finally, we’ve created another file with one line of text and committed that as well. If you use the git log –oneline command, you’ll see something like this:
Let’s say that, for testing purposes, we need to see how things were at the time of the second commit. How would we do that? As it turns out, we can check out a commit the same way we’d check out branches. Remember, branches are just names for commits. So, based on the example output above, we’d run git checkout 87ec91d. Keep in mind that if you’re following along by executing these commands on your own system, the hash for your commits will be different from those in the example. Use the log command to find it.
After running the command, we get the “You are in ‘detached HEAD’ state[…]” message. Before we go on, make sure you keep this in mind: you get to the detached HEAD state by checking out a commit directly.
What Is a HEAD in Git?
What does HEAD mean in Git? To understand that, we have to take a step back and talk fundamentals.
A Git repository is a collection of objects and references. Objects have relationships with each other, and references point to objects and to other references. The main objects in a Git repository are commits, but other objects include blobs and trees. The most important references in Git are branches, which you can think of as labels you put on a commit.
HEAD is another important type of reference. The purpose of HEAD is to keep track of the current point in a Git repo. In other words, HEAD answers the question, “Where am I right now?”
For instance, when you use the log command, how does Git know which commit it should start displaying results from? HEAD provides the answer. When you create a new commit, its parent is indicated by where HEAD currently points to.
Are you in ‘detached HEAD’ state?
You’ve just seen that HEAD in Git is only the name of a reference that indicates the current point in a repository. So, what does it mean for it to be attached or detached?
Most of the time, HEAD points to a branch name. When you add a new commit, your branch reference is updated to point to it, but HEAD remains the same. When you change branches, HEAD is updated to point to the branch you’ve switched to. All of that means that, in these scenarios, HEAD is synonymous with “the last commit in the current branch.” This is the normal state, in which HEAD is attached to a branch.
A visual representation of our demo repository would look like this:
As you can see, HEAD points to the controller branch, which points to the last commit. Everything looks perfect. After running git checkout 87ec91d, the repo looks like this:
This is the detached HEAD state; HEAD is pointing directly to a commit instead of a branch.
Benefits of a Git Detached HEAD
Are there good reasons for you to be in the detached HEAD state? You bet there are!
As you’ve seen, you detach the HEAD by checking out a commit. That’s already useful by itself since it allows you to go to a previous point in the project’s history. Let’s say you want to check if a given bug already existed last Tuesday. You can use the log command, filtering by date, to start the relevant commit hash. Then you can check out the commit and test the application, either by hand or by running your automated test suite.
What if you could not only take a look at the past, but also change it? That’s what a detached HEAD allows you to do. Let’s review how the detached message starts:
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.
In this state, you can make experimental changes, effectively creating an alternate history. So, let’s create some additional commits in the detached HEAD state and see how our repo looks afterward:
echo "Welcome to the alternate timeline, Morty!" > new-file.txt
git add .
git commit -m "Create new file"
echo "Another line" >> new-file.txt
git commit -a -m "Add a new line to the file"
We now have two additional commits that descend from our second commit. Let’s run git log –oneline and see the result:
This is what the diagram looks like now:
What should you do if you want to keep those changes? And what should you do if you want to discard them? That’s what we’ll see next.
How Do I Fix a Detached HEAD in Git?
You can’t fix what isn’t broken. As I’ve said before, a detached HEAD is a valid state in Git. It’s not a problem. But you may still want to know how to get back to normal, and that depends on why you’re in this situation in the first place.
Scenario #1: I’m Here by Accident
If you’ve reached the detached HEAD state by accident—that is to say, you didn’t mean to check out a commit—going back is easy. Just check out the branch you were in before:
git checkout <branch-name>
If you’re using Git 2.23.0 or newer, you can also use switch instead of checkout:
git switch <branch-name>
Scenario #2: I’ve Made Experimental Changes and I Want to Discard Them
You’ve entered the detached HEAD state and made a few commits. The experiment went nowhere, and you’ll no longer work on it. What do you do? You just do the same as in the previous scenario: go back to your original branch. The changes you made while in the alternate timeline won’t have any impact on your current branch.
Scenario #3: I’ve Made Experimental Changes and I Want to Keep Them
If you want to keep changes made with a detached HEAD, just create a new branch and switch to it. You can create it right after arriving at a detached HEAD or after creating one or more commits. The result is the same. The only restriction is that you should do it before returning to your normal branch.
Let’s do it in our demo repo:
git branch alt-history
git checkout alt-history
Notice how the result of git log –oneline is exactly the same as before (the only difference being the name of the branch indicated in the last commit):
We could just run git branch alt-history, and we’d be all set. That’s the new—and final—version of our diagram:
Getting Rid of the “Git Detached HEAD” Message
Before wrapping up, let’s share a final quick tip. Now that you understand everything about detached HEAD in Git and know that it’s not that big of a deal, seeing that message every time you check out a commit might become tiring. Fortunately, there’s a way to not see the warning anymore. Just run the following command:
git config advice.detached head fals
e
Easy, right? You can also use the –global modifier if you want the change to apply to every repository and not only the current one.
Git Detached HEAD: Less Scary Than It Sounds
A message talking about heads not being attached doesn’t sound like your routine software error message, right? Well, it’s not an error message.
As you’ve seen in this post, a detached HEAD doesn’t mean something is wrong with your repo. Detached HEAD is just a less usual state your repository can be in. Aside from not being an error, it can actually be quite useful, allowing you to run experiments that you can then choose to keep or discard.
Carlos Schults is a .NET software developer with experience in both desktop and web development, and he’s now trying his hand at mobile. He has a passion for writing clean and concise code, and he’s interested in practices that help you improve app health, such as code review, automated testing, and continuous build.
The introduction of Git as a source-code management system in 2005 fundamentally transformed the process of software development. Git allows developers to maintain a history of changes, or commits, to their code and revert to previous commits in seconds if something goes wrong. It makes collaboration easier by allowing branching to keep code for different features separate and seamlessly merging commits by various people. It is fast, scalable, and has more commands and flexibility than older version control tools like Apache Subversion (SVN) and Concurrent Versions System (CVS).
However, learning Git is more complicated than learning SVN or CVS. Complex commands and a less intuitive user interface can sometimes lead to unwanted states, including a state called detached HEAD. In this article, we will explore what the Git detached HEAD state is and some situations that cause it. Then, we will demonstrate how to save or discard changes in a detached head so you can quickly recover from the situation.
What does detached HEAD mean?
In Git, HEAD refers to the currently checked-out branch’s latest commit. However, in a detached HEAD state, the HEAD does not point to any branch, but a specific commit or the remote repository.
Below is a diagram of the Git HEAD in a normal state, pointing to the latest commit in the main branch.
In this image, the HEAD points to the latest commit and current checked-out branch on every commit in the current branch.
HEAD is also a common state when working with multiple branches.
In this situation, you have two branches, the main branch and a feature branch. Since you are checked out to the feature branch, HEAD points there. You can create this scenario with the following Git commands:
git branch feature
git checkout feature
git commit -m “checked out to feature”
git checkout Main
git commit
git commit -m “Latest commit”
git checkout feature
In both of the diagrams above, HEAD points to the most recent commit in the currently checked out branch, which is its normal state. In Git, you can also check out a particular commit, which results in the detached HEAD state. Continuing from the previous scenario, if you check out to the first commit on the main branch, you will see that HEAD is now detached.
In this case, HEAD does not point to any branch—it references a commit.
Scenarios that can cause a detached HEAD state
You can find yourself in a detached HEAD state primarily through two scenarios:
- Checking out a specific Secure Hash Algorithm 1 (SHA-1) commit hash
- Checking out to a remote branch without fetching it first
We already demonstrated that if you check out the SHA-1 commit hash, you will be in the detached HEAD state. Another situation that causes a detached HEAD is checking out the remote branch. If you check out to the origin (main) branch, which is read-only, you will be in the detached HEAD state.
Some other scenarios can cause a detached HEAD as well. For example, checking out to a specific tag name or adding ^0
on any given branch causes the detached HEAD state.
How to save changes in a detached HEAD
If you find yourself a detached HEAD state and realize it quickly, you can quickly recover by checking out the previous branch. But what if you are in the detached HEAD state by mistake then perform commits over commits? If you are committing in the detached HEAD state, does that mean your changes are not saved?
Not at all. It just means you are not currently attached to any branch, and as a result, your HEAD is detached. If you want to keep the changes you made while in the detached HEAD state, you can solve this problem with three simple steps: creating a new branch, committing the changes, and merging the changes.
Create a new branch
To save changes committed in a detached HEAD state, you first need to create a new branch.
Continuing from the scenario described above, you create a new branch called temp-branch
. As soon as you make the branch and check out to it, the HEAD is no longer detached.
Commit the changes
After checking out to the new branch, you can commit the changes, which Git will preserve.
Merge the changes
Now, you check out to the branch where you want the changes. In this case, you want to merge the changes to the main branch. So, you first need to check out the main branch, then merge the changes from temp-branch
and add the final commit message.
With these simple steps, you have successfully preserved your changes and recovered from the Git detached HEAD state.
How to discard changes in a detached HEAD
If you want to discard the changes in the detached HEAD state, you only need to check out to the existing or previous branch. The commits on the detached HEAD state will not affect your existing branch, and Git will archive them.
The diagram below shows a situation in which, after going into the detached HEAD state, you make two commits that you do not want to keep. Then, you check out to the main branch. The dotted circles indicate that these commits are no longer part of any branch, and Git will delete them.
Note that once Git prunes your detached HEAD state commits, there is no way to get them back. However, if they have not been deleted, you can check out to that SHA-1 commit hash, create a branch, and merge it to the desired branch to preserve the changes.
Conclusion
Git is a valuable developer tool and more popular than older versioning tools such as CVS and Subversion. That said, it can be more complex and challenging to master, and can sometimes lead to confusing situations such as the detached HEAD state.
If you find yourself in the detached HEAD state, remember that you can always preserve your changes by creating and checking out to a new branch, then committing and merging the changes in the desired branch. If you do not want to save the changes, you can simply check out to any branch, and Git removes those commits.
Also, Git 2.23 has a new command, git switch
. This is not a new feature but an alternative command to git checkout
so you can switch between the branches and create a new branch. To change from one branch to another, use git switch branchName
to create a new branch, then switch to it using the git switch -c branchName
command.
Although finding your code in the detached HEAD state is not ideal, you can use these methods to move or remove your commits and quickly get your project back on track.
В последнее время мои коллеги начинают знакомство с git’ом. И один из интересующих их вопросов — как откатиться до определённой ревизии. В интернете можно найти набор команд, но хочется, чтобы было понимание каждой из них. Баловство с комадами git’а без понимания может привести к потере истории разработки.
В этой статье я хочу рассказать о командах git checkout
и git reset
с ключами --soft
и --hard
.
Итак, начнём краткий ликбез по машине времени, предоставляемой git’ом. Сперва проиллюстрируем историю:
Здесь кружочками обозначены коммиты. Чем правее коммит, тем он новее. Коммит с хэшем 6e04e..-это самый первый коммит. Одно из основных понятий, которое стоит уяснить себе новичку, — это указатели на коммиты, а точнее некоторое «прозвище» того или иного коммита. Их тьма тьмущая, например: HEAD, master, FETCH_HEAD, ORIG_HEAD и т.д. Это я перечислил крупицу стандартных прозвищ. Их можно создавать и самим, но об этом впереди.
Заострим наше внимание на двух указателях: master и HEAD. master указывает на самый старший коммит в ветке под названием master (эта ветка создаётся при инициализации репозитория). HEAD указывает на указатель master (читай, текущее состояние файлов). После появления первого коммита в репозитории, HEAD и master указывают на один и тот же коммит. И так будет продолжать до тех пор, пока не переключимся на другую ветку, не откатимся по истории, либо не совершим ряд необдуманных действий. Итак, проиллюстрируем нашу историю с указателями:
Указатель HEAD в нашем случае указывает на master, а master — на коммит d79fb… Архиважно понять, что текущее состояние неизменённых файлов, находящихся под контролем версий, есть тот коммит, на который указывает HEAD. То есть, если HEAD будет указывать на коммит с хэшем 6e04e.., то файлы окажутся в первоначальном своём состоянии. Для «движения» указателя HEAD существует команда: git checkout . Те, кто знаком хоть чуть-чуть с git’ом, узнали в этой команде переключение на другую ветку. Всё совершенно верно — при переключении на другую ветку мы просто переносим указатель HEAD на последний коммит ветки.
Перенос указателя HEAD (git checkout
)
Откат по истории коммитов:
После завершения операции checkout мы будем находиться в состоянии, в котором были два коммита назад. Это всё прекрасно — мы сделали шажок в прошлое, что-то там подглядели, но как вернуться назад? Я вот, например, не обладаю сверхпамятью, и не помню хэш самого последнего коммита (тот, который самый правый — d79fb..). Если написать git log
, то увидим историю, состоящую из трёх коммитов:
[user@localhost project]$ git log --pretty=oneline
6741a69bd121c295413be95d7597cd7409e713a0 add unit test
b3e74f50c3cc48e6b335014b6dc7e301b382a903 add readme
6e04e39d0952a2d6022502d56aaa05d5a064bea Initial commit
Неужели мы потеряли всю историю? Как узнать самый «новый» коммит? Это не проблема — есть выход, и их несколько:
- Написать команду
git log --all
. Данная команда напечатает нам всю историю, вплоть до современности, т.е. в нашем случае историю из пяти коммитов:[user@localhost project]$ git log --pretty=oneline --all d79fb5688af71b4577f450919535e7177e9d74e8 fix bug 478927e3a088d3cec489ca8810eaaca97c6ce0ff documentation 6741a69bd121c295413be95d7597cd7409e713a0 add unit test b3e74f50c3cc48e6b335014b6dc7e301b382a903 add readme 6e04ee39d0952a2d6022502d56aaa05d5a064bea Initial commit
Далее остаётся скопировать нужный нам хэш и вновь запустить машину времени:
git checkout
. Но данный способ не рекомендую, так как он требует слишком много действий. - Git позволяет отслеживать все изменения указателя HEAD. Это возможно командой
git reflog
, но это уже не для новичков и используется не для поставленных нами целей. Самое грамотное — это поступить следующим образом: - Вспомнить, что указатель master указывает на самый свеженький коммит. Таким образом, возврат в исходное состояние выполняется одной командой:
git checkout master
. Вуа-ля:
Для прояснения механизма git checkout
создадим новую ветку devel:
[user@localhost project]$ git checkout -b devel
*флаг -b означает, что необходимо создать ветку с указанным именем и сразу переключится на неё.
Проиллюстрируем совершённое нами действие:
Заметим, что указатель HEAD указывает на вершину ветки devel.
Породим в новой ветке несколько коммитов. История репозитория будет выглядеть следующим образом:
Возвращение в ветку master происходит также безболезненно:
[user@localhost project]$ git checkout master
Итак, запоминаем первый пункт:
- Комнда git checkout передвигает указатель HEAD
Перенос указателя на вершину ветки (git reset ...
)
Кроме того, git позволяет двигать не только HEAD, но и
континеты
указатели на вершины веток. Для этого существует команда git reset
с ключами либо --soft
, либо --hard
.
- Ключ
--hard
означает, что мы теряем текущее состояние файлов и приобретаем состояние того коммита, куда был сделан reset. - Ключ
--soft
означает, что мы НЕ теряем текущее состояние проекта, но указатель на текущую ветку уже передвинут, т.е. git status нам выдаст разницу между текущим состоянием проекта (от которого мы сделали reset) и тем, на который мы сделали reset.
В обоих случаях появляется «прозвище» для коммита, с которого был совершён reset — ORIG_HEAD.
git reset --hard HEAD~2
:
git reset --soft HEAD~2
:
ORIG_HEAD полезен для редактирования неверных коммитов на локальной машине (!). Предположим, что мы хотим объединить два последних коммита в единый. Для этого, сохраняя текущее состояние файлов, переводим указатель master на два коммита назад:
[user@localhost project]$ git reset --soft HEAD~2
Посмотрим на изменения:
[user@localhost project]$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# ЧТО-ТО ТАМ ДЛЯ КОММИТА
#
Ну а теперь сделаем трюк — объединяем коммиты
[user@localhost project]$ git commit -c ORIG_HEAD
Вводим сообщение, сохраняемся. Теперь наша история выглядит вот так:
Важное замечание — ORIG_HEAD по-прежнему указывает на коммит d79fb… Если мы сейчас выполним команду git checkout ORIG_HEAD, то мы получим так называемое состояние detach HEAD. Оно характеризуется тем, что HEAD указывает не на вершину ветки, а просто на коммит. HEAD всегда должен указывать только на вершину какой-либо ветки!
Чтобы «выйти» из состояния detach HEAD достаточно просто переключиться на какую-либо ветку или создать новую ветку командой git checkout -b new_branch_name
Итак, запоминаем второй пункт:
git reset
с ключами--soft
или--hard
двигает указатель на вершину ветки, а вместе с ним и указатель HEAD.
И самое главное! Самая частая операция из вышеперечисленных при работе с git`ом — это переключение между ветками. Все остальные рассмотренные случаи встречаются редко, но тем не менее необходимо понимать всё, что происходит при их использовании!
Удачных вам путешествий по истории своего репозитория!
При подготовке материала использовались следующие источники:
Самый лучший manual — книга: ProGit
Наглядная справка по git: A Visual Git Reference (Русская версия)
UPD:
В комментариях посоветовали ещё один полезный ресурс по git`у: githowto
P.S. Благодарю за инвайт и всем желаю приятных выходных!
В этом уроке мы с вами узнаем, как перемещать указатель HEAD. Это знание откроет перед нами много возможностей. Например, мы сможем откатиться к предыдущему коммиту, добавить в созданный ранее коммит файлы или исправить ошибку в уже сделанном коммите.
Git. Урок 4.
Перемещение курсора и отмена изменений. Команды git restore, git rm, git reset, git checkout, git commit, git revert.
В этом уроке мы с вами узнаем, как перемещать указатель HEAD. Это знание откроет перед нами много возможностей. Например, мы сможем откатиться к предыдущему коммиту, добавить в созданный ранее коммит файлы или исправить ошибку в уже сделанном коммите.
Команды: git restore, git rm, git reset, git checkout, git commit, git revert.
Оглавление
Теоретический блок
1. Удаляем и восстанавливаем файлы правильно. Команды git rm и git restore.
2. Просмотр старых коммитов и перемещение указателя HEAD
- История выводится не полностью
- Как переключиться обратно
- Зачем может понадобиться переключать указатель на старые коммиты, и важные особенности состояния «detached head».
3. Откат коммитов. Команда git revert.
4. Удаление и объединение коммитов. Команда git reset.
- Редактирование или отмена последнего коммита
- Объединение нескольких коммитов в один.
- Удаление последних нескольких коммитов.
- Отмена изменений команды git reset.
5. Различие команд git reset и git checkout и git revert.
Перейти
Практический блок
1. Задание
2. Решение
Перейти
ТЕОРЕТИЧЕСКИЙ БЛОК
Сегодня мы познакомимся с командами, которые позволят удалять и восстанавливать файлы в рабочей копии и индексе, отменять изменения на уровне целых коммитов, возвращаться в прошлое нашего репозитория и манипулировать указателем HEAD.
Удаляем и восстанавливаем файлы правильно. Команды git rm и git restore.
Начнем с удаления файлов. У читателя может возникнуть вопрос: почему нельзя просто взять и переместить файл из репозитория в корзину, как мы привыкли это делать с обычными файлами? Давайте разбираться.
Как мы говорили во втором уроке, файл в репозитории может находится в нескольких состояниях:
1. Отслеживаемый.
- Измененный (неподготовленный к коммиту).
- Подготовленный к коммиту.
2. Неотслеживаемый.
Когда мы делаем файл отслеживаемым или подготавливаем его к коммиту, информация об этом файле записывается в индекс (файл .git/index). Эта информация никуда не денется, даже если мы удалили файл из рабочей копии. Рассмотрим пример. Допустим, мы выполним следующие действия:
1. Создадим файл sample.txt и внесем в него какие-то изменения;
2. Сделаем его отслеживаемым;
3. Удалим файл sample.txt;
4. Сделаем коммит;
В таком случае, просматривая содержимое нового коммита, мы обнаружим, что файл sample.txt в нем присутствует, несмотря на то, что мы вроде бы его удалили. Это произошло из-за того, что хоть мы и удалили файл из рабочей копии, но не удалили информацию о нем из индекса.
В Git существует специальная команда, чтобы удалять файлы «правильно».
Команда git rm
Формат
git rm <ключ> <имя файла>
Что делает
Удаляет файл из рабочей копии и индекса / только из индекса. Данная команда не может удалить файл только из рабочей копии.
Пример
# Удалим файл sample.txt из рабочей копии и индекса
$ git rm sample.txt
# Удалим файл sample.txt из индекса и перемеcтим его в категорию Untracked
$ git rm —cached sample.txt
Внимательный читатель спросит: но почему нельзя удалить файл из репозитория обычным образом, а затем добавить изменения в индекс командой git add и сделать коммит? На самом деле можно. Команда git rm является сокращением вышеописанных действий. Ее применение считается более правильным, поскольку она короче и красивее. Давайте рассмотрим небольшой пример:
# Удалим файл обычным образом
$ rm sample.txt
$ git status
On branch main
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: sample.txt
$ git add -A
# И вся последовательность команд выше эквивалентна всего одной:
$ git rm sample.txt
rm 'sample.txt'
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: sample.txt
# Осталось только сделать коммит, чтобы сохранить изменения:
$ git commit -m "L-04: Deleted sample.txt"
[main f34705a] L-04: Deleted sample.txt
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 sample.txt
Обычное удаление файла, который отслеживается Git
«Правильное» удаление файла. Команда git rm.
Итак, мы разобрались с удалением файлов. Но что, если мы захотим восстановить файл после удаления или изменения в рабочей копии или индексе? Для этого существует команда git restore. Давайте разберем ее подробнее.
Команда git restore
Формат
git restore <ключ> <имя файла>
Ключи
-s, —source=<tree>
Этот ключ нужен, чтобы передать команде путь к коммиту (ветке, пользовательскому указателю), откуда мы будем восстанавливать файл. По умолчанию файл берется из области индекса.
—worktree (англ. рабочая копия)
—staged (англ. область индекса)
Эти два ключа позволяют указать, где именно восстанавливать файл. По умолчанию, если ни один из этих двух ключей не передан, файл восстанавливается только в рабочей копии.
Если же передан ключ —staged, файл восстанавливается только в области индекса. В этом случае источником восстановления по умолчанию является коммит, на который указывает HEAD (поскольку мы не можем восстановить файл в области индекса из самой же области индекса).
Если же вы хотите восстановить файл и в рабочей копии, и в области индекса, вам нужно передать оба ключа.
Что делает
Восстанавливает указанный файл из переданного источника. По умолчанию источником является индекс. Если файла нет в указанном источнике, файл будет удален.
Пример
# Если вы случайно удалили файл sample.txt обычным способом, то можно восстановить его из индекса
$ git restore sample.txt
# Вернем файл sample.txt к определенному коммиту с хэшем 09c2240. При этом мы изменим только файл в рабочей копии, файл в области индекса не поменяется.
$ git restore —source 09c2240 sample.txt
# Вернем файл sample.txt в индексе к состоянию последнего коммита (отменим все внесенные изменения или удалим файл, если в предыдущем коммите его не было), при этом изменения коснутся только индекса файла, рабочая копия не поменяется.
$ git restore —staged sample.txt
# Сделаем то же, что и в предыдущем примере, но теперь изменения затронут и файл в рабочей копии.
$ git restore —staged —worktree sample.txt
С помощью команды git restore можно сделать неотслеживаемыми файлы, которые вы случайно добавили командой git add. Приведем пример:
# Допустим у нас есть некоторый репозиторий, просмотрим статус файлов:
$ git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
file_to_commit.txt
another_file_to_commit.txt
file_not_for_commit.txt
# Для простоты добавим все файлы в индекс разом
$ git add -A
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: file_to_commit.txt
new file: another_file_to_commit.txt
new file: file_not_for_commit.txt
# А теперь восстановим файл “file_not_for_commit.txt” в области индекса. Тогда источником станет последний коммит, а в нем такого файла нет (файл же новый). Поэтому файл будет удален из области индекса. Кстати, можно заметить, что даже Git подсказывает нам: “use "git restore --staged <file>..." to unstage”.
$ git restore --staged file_not_for_commit.txt
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: another_file_to_commit.txt
new file: file_to_commit.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file_not_for_commit.txt
# Для справки: в данном случае эквивалентной командой будет:
$ git rm --cached file_not_for_commit.txt
rm 'file_not_for_commit.txt'
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: another_file_to_commit.txt
new file: file_to_commit.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file_not_for_commit.txt
Подведем итогИтак, мы теперь умеем удалять и восстанавливать файлы из индекса и рабочей копии.
1. Чтобы удалить файл правильно, воспользуйтесь командой git rm. Она удалит файл из индекса и из рабочей копии.
- Вариант git rm —cached удалит файл из индекса и переместит его в категорию Untracked.
- По своей сути git rm <filename> представляет сокращение двух команд: rm <filename> и git add <filename>.
2.Чтобы восстановить файл в рабочей копии и/или индексе, воспользуйтесь командой git restore.
- Ключ —source=<tree> позволит вам указать место, откуда брать файл для восстановления. По умолчанию этим местом является область индекса.
- По умолчанию файл восстанавливается в рабочей копии, но вы можете восстановить файл в области индекса с ключом —staged или одновременно в области индекса и в рабочей копии, передав ключи —staged —worktree. В этом случае по умолчанию (если вы не передали ключ —source) файл будет взят из коммита, на который указывает HEAD.
Просмотр старых коммитов и перемещение указателя HEAD
Итак, пришло время разобраться, каким образом можно перемещать HEAD (и другие указатели тоже) и что это дает. Для начала нужно прояснить несколько важных аспектов, которые мы уже упоминали, но не обращали ваше внимание на них:
1. Весь репозиторий – это древовидный граф, ноды которого – наши коммиты, а ребра – родительские отношения между коммитами.
2. HEAD – это указатель (то есть ссылка на один из коммитов), главное назначение которого — определять, в каком состоянии находится рабочая копия. На какой коммит указывает HEAD – в таком состоянии файлы и находятся в рабочей области.
3. Обычно HEAD указывает не на определенный коммит, а на указатель ветки, который в свою очередь указывает на конкретный коммит.
4. HEAD можно перемещать: при перемещении указателя файлы в рабочей копии изменятся так, чтобы соответствовать коммиту, на который указывает HEAD.
5. Указывая путь до чего бы то ни было, вы можете использовать как абсолютные указатели, например, хэш коммита или имя ветки, так и относительные. Вспомним, как пользоваться относительными указателями:
- Знак ^ означает «предыдущий». Например путь HEAD^ означает «предыдущий коммит перед тем, на который указывает HEAD«
- Знак ~ позволяет вам указать число коммитов. Например, запись HEAD~7 означает «7 коммитов назад от коммита, на который указывает HEAD«.
Использование относительных путей особенно удобно, поскольку они позволяют не запоминать хэши коммитов и не обращаться к истории репозитория чаще, чем это действительно нужно.
Давайте подробнее разберемся с перемещением указателя, а затем поговорим, зачем это может быть нужно. Итак, чтобы перемещать указатель, нужно воспользоваться знакомой нам из прошлого урока командой git checkout.
Команда git checkout
Что делает
Переводит курсор HEAD на указанный коммит или другой указатель и копирует.
Пример
# Передвинем HEAD на два коммита назад
$ git checkout HEAD~2
HEAD is now at 7194f7e L-04: Fixing gradient bug
Можно заметить, что синтаксис совершенно такой же, как и в переключении ветки из предыдущего урока. Все верно, ведь переключение ветки – ни что иное, как передвижение указателя HEAD с указателя одной ветки на указатель другой. Чтобы подробно разобраться, что происходит при передвижении указателя, давайте рассмотрим пример. Изначально у нас есть такой репозиторий.
Серыми овалами обозначены коммиты, текст на них – часть хэша соответствующего коммита. Коричневым прямоугольником обозначен указатель ветки: она у нас одна и называется main. Белым прямоугольником обозначен указатель HEAD, иногда его называют курсором. Его-то мы и будем двигать. Давайте попробуем сдвинуть HEAD на два коммита назад (то есть на коммит с хэшем 90ab…) и посмотрим, что будет.
$ git checkout HEAD~2
Note: switching to 'HEAD~2'.
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 90abf7e L-04: Fixing gradient bug
Предупреждение Git мы разберем чуть ниже, а сейчас давайте посмотрим, в каком состоянии мы оказались.
Граф Git после перевода указателя HEAD на два коммита назад
Как видно из рисунка, указатель HEAD сейчас действительно передвинут на два коммита назад. Но указатель ветки main остался на месте и все еще указывает на последний коммит. Такое состояние, когда HEAD указывает не на указатель ветки, а непосредственно на сам коммит, называется detached head.
“
Detached head (англ. дословно – отрубленная голова, имеется в виду указатель HEAD, отключенный от графа) – состояние, при котором указатель HEAD не указывает ни на одну из веток репозитория, а ссылается непосредственно на сам коммит.
Если прочесть предупреждение Git выше, можно заметить, что он сообщает нам, что мы оказались в состоянии detached head. Давайте рассмотрим несколько особенностей этого состояния:
2.1. История выводится не полностью
Действительно, если мы из текущего состояния выполним команду git log:
$ git log --pretty=oneline
90abf7ef211229adfa4cb75e0f35a0561dd15467 (HEAD) L-04: Fixing gradient bug
3a3bb706651a19013822c09e5c70c9fc425a66dc L-04: Adding gradient function
d469222a7a760daa3cd56747e216e3de2a3343ee L-04: Initial commit
Видно, что вывелись только те коммиты, которые были сделаны позже коммита, на котором сейчас стоит HEAD. Если же мы хотим просмотреть всю историю, нужно воспользоваться ключом —all:
$ git log --all --pretty=oneline
62aa1ffe0587c7ffa3d865a3233da04d65818030 (main) L-04: Adding autograd
33ff7207fed9cbb34c9f3334249ef0707477f278 L-04: Adding neuron class
90abf7ef211229adfa4cb75e0f35a0561dd15467 (HEAD) L-04: Fixing gradient bug
3a3bb706651a19013822c09e5c70c9fc425a66dc L-04: Adding gradient function
d469222a7a760daa3cd56747e216e3de2a3343ee L-04: Initial commit
Тогда нам видна полная история репозитория. Если присмотреться, то можно увидеть, что напротив первого сверху коммита в скобках написано main. Так Git сообщает нам, что на данный коммит указывает ветка main. Аналогично с третьим сверху коммитом: напротив него написано HEAD. Это означает, что HEAD указывает на этот коммит.
2.2. Как переключиться обратно
Вам вовсе необязательно смотреть полную историю каждый раз, когда вы хотите вернуться обратно на ветку main. В предыдущем уроке мы рассматривали команду git checkout — и говорили, что с ее помощью можно вернуться на ветку, с которой мы переключились на текущую. Так вот, эта команда работает не только для веток, но и для любых перемещений HEAD. Если мы хотим перенести HEAD обратно, достаточно выполнить:
$ git checkout -
Previous HEAD position was 90abf7e L-04: Fixing gradient bug
Switched to branch 'main'
Как видно по выводу Git, мы успешно вернулись обратно на ветку main и вышли из состояния detached head.
У данного способа есть один минус: если вы «прыгали» по коммитам репозитория несколько раз, то git checkout — вернет вас на последний коммит, на котором вы были, а не на ветку main. В данном случае поможет простое и гениальное:
$ git checkout main
Previous HEAD position was 90abf7e L-04: Fixing gradient bug
Switched to branch 'main'
То есть мы напрямую указываем Git, что хотим переключиться на ветку main (или любую другую), таким образом выходя из состояния detached head.
2.3. Зачем может понадобиться переключать указатель на старые коммиты, и важные особенности состояния «detached head».
На самом деле, все зависит от конкретной задачи и проекта, над которым вы работаете. Вообще состояние detached head на практике используется довольно-таки редко. Но все же можно привести несколько примеров использования этого состояния:
Пример 1
Самый банальный пример – просмотр файлов в определенном коммите. Допустим, вы хотите просмотреть содержимое файла main.py в коммите с определенным хэшем.
Просто выполните:
# Команда cat выводит в консоль содержимое файла.
$ git checkout 5df3f7e
$ cat main.py
print(“Hello world”)
Таким образом, вы можете просмотреть содержимое любого, когда либо закоммиченного файла, что бывает довольно удобно.
Пример 2
Кроме того, когда вы работаете над большим проектом, может быть нужно вернуться на несколько коммитов назад и создать свою ветку оттуда: например, чтобы протестировать экспериментальную функцию в том состоянии проекта. Сделать это можно так:
# Переключим указатель HEAD на определенный коммит:
$ git checkout 9a4e88b
# Теперь создадим новую ветку
$ git checkout -b feature
Switched to a new branch 'feature'
# Добавим файл и сделаем коммит
$ echo "just docs we forgot to put" > docs.md
$ cat docs.md
just docs we forgot to put
$ git add -A
$ git commit -m "L-04: Adding docs"
[feature d5e3273] L-04: Adding docs
1 file changed, 1 insertion(+)
create mode 100644 docs.md
Теперь у нас есть целая ветка feature, которая берет свое начало с коммита 9a4e88b. На ней мы можем проводит эксперименты с тем состоянием репозитория, которое было несколько коммитов назад, не боясь навредить остальной части репозитория.
Заметьте, что когда мы создали и переключились на ветку, мы вышли из состояния detached head. Если бы мы сделали коммит, не создавая для этого ветки, этот коммит оказался бы «оторванным» от истории: на него не ссылалась бы ни одна ветка, а в выводе истории (даже полной) его не было бы видно. Иначе говоря, если бы мы сделали коммит без ветки, мы бы смогли получить к нему доступ только запомнив его хэш наизусть.
Поэтому с состоянием detached head следует быть очень осторожным: если забыть создать новую ветку и начать делать коммиты, их можно с легкостью потерять.
На самом деле, если вы сделали несколько коммитов из состояния detached head и забыли создать ветку, ничего страшного: ветку можно создать прямо сейчас. Рассмотрим пример с тем же репозиторием:
# Переключимся в состояние “detached head” на ветке main.
$ git checkout d5e3273
# Теперь мы создадим несколько коммитов, исключительно для примера:
$ echo 'print('hello world')' > hello_world.py
$ echo 'print('Hi')' > say_hi.py
$ echo 'Some text file' > about.txt
$ git add hello_world.py
$ git commit -m "L-04: Adding hello_world.py"
[detached HEAD d013c75] L-04: Adding hello_world.py
1 file changed, 1 insertion(+)
create mode 100644 hello_world.py
$ git add say_hi.py
$ git commit -m "L-04: Adding say_hi.py"
[detached HEAD 58f8c22] L-04: Adding say_hi.py
1 file changed, 1 insertion(+)
create mode 100644 say_hi.py
$ git add about.txt
$ git commit -m "adding about.txt"
[detached HEAD 7c10724] L-04: Adding about.txt
1 file changed, 1 insertion(+)
create mode 100644 about.txt
# А сейчас мы вспомнили, что сделали несколько коммитов в состоянии “detached head” и испугались, что потеряем их. Но не стоит бояться, можно просто создать ветку прямо сейчас:
$ git checkout -b feature
Switched to a new branch 'feature'
# И чтобы убедиться, что мы не потеряли ни одного коммита, давайте взглянем на историю ветки feature
$ git log --pretty=oneline main..feature
7c10724b12dba69ed1acf4c6fef804c251f7c290 (HEAD -> feature) L-04: Adding about.txt
58f8c226952d500df7a9c2f798011ab20165a286 L-04: Adding say_hi.py
d013c75418ac71ca8f4578583aa23d7567dab332 L-04: Adding hello_world.py
d5e327368d12d6e5ef5b04e16af1d96319069805 L-04: Adding docs
Даже в состоянии detached head коммиты сохраняют родительские отношения, то есть каждый коммит знает, кто его предшественник. Поэтому мы в любой момент можем восстановить их последовательность, если найдем крайний коммит в данной цепочке. То есть для нас главное не забыть создать ветку, находясь в состоянии detached head. А вот создать ее можно в любой момент: до того, как мы сделаем первый коммит из этого состояния, или в любой другой момент – не важно, главное сделать это до переключения на другую ветку или коммит. Тогда у нас будет ссылка на крайний коммит ветки, а по его родителям мы сможем определить всю последовательность.
Подведем итог
- Указатель HEAD можно перемещать на разные коммиты, точно так же, как мы перемещали его между ветками. Для этого нужно использовать команду git checkout.
- Когда мы перемещаем HEAD с указателя ветки на коммит, мы попадаем в состояние detached head.
- В состоянии detached head стоит быть осторожным: если планируете делать коммиты, лучше сразу создайте ветку командой git checkout -b <имя ветки>.
- Если вы сделали несколько коммитов из состояния detached head, забыв создать ветку – ничего страшного, ветку можно создать в любой момент.
- Обычно detached head используется редко, но вы можете использовать его, чтобы просматривать старые версии файлов или экспериментировать с предыдущими версиями проекта.
Откат коммитов. Команда git revert.
Пожалуй одна из самых важных частей в изучении Git – научиться откатываться к предыдущим коммитам. Смысл отката мы обсуждали в предыдущих уроках: ваш проект может перестать работать по непонятным вам причинам после внесения некоторых изменений в код, в таком случае важно быстро вернуть все к рабочему состоянию и только потом заниматься поиском ошибки. В этом-то случае нам и поможет откат коммитов и команда git revert.
Суть работы данной команды в том, что она создает новый коммит, который отменяет изменения внесенные в переданном коммите (последовательности коммитов).
Команда git revert
Формат
git revert <ключи> <адрес коммита>
Что делает
Отменяет изменения, внесенные в переданном коммите.
Пример
# Отменим изменения, внесенные 2 коммита назад
$ git revert HEAD~2
# Отменим все изменения в коммитах, начиная с пятого с конца и заканчивая вторым с конца.
$ git revert HEAD~5..HEAD~2
Команда git revert очень важна, поэтому давайте разберем, как с ней работать. Рассмотрим самый простой пример, в котором у нас не возникнет файловых конфликтов. Допустим, мы хотим отменить изменения предпоследнего коммита. Главное, что здесь нужно запомнить – это что у нас не должно быть незакоммиченых изменений в рабочей директории, ведь мы все-таки делаем реверт-коммит. Если у вас есть таковые, лучшим решением станет закоммитить (или удалить) все изменения, и только потом делать реверт.
# Выполним реверт предпоследнего коммита. Кстати, такой реверт никогда не вызовет конфликтов.
$ git revert HEAD~1
# Как только мы выполним команду, будет открыт консольный редактор, чтобы вы могли отредактировать сообщение нового коммита. Можно что-то дописать, можно сразу закрыть редактор сочетанием Ctrl+X (здесь приведено сочетание для редактора “nano”). Решать вам. В нашем случае окно редактора будет выглядеть так.
Revert "L-04: Addit docs.txt"
This reverts commit aadfbc3a6756289727e56ac3de59004e66e40033.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is up to date with 'origin/master'.
#
# Changes to be committed:
# modified: docs.txt
#
^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos
^X Exit ^R Read File ^ Replace ^U Paste Text^T To Spell ^_ Go To Line
# После закрытия редактора, в терминал будет выведена информация об успешном реверте коммита
[main e933971] Revert "addit docs.txt"
1 file changed, 1 insertion(+), 1 deletion(-)
Итак, это был самый простой пример. В основном сложности git revert связаны с разрешением файловых конфликтов. Разрешать мы их научимся в следующем уроке, а пока давайте обсудим, из-за чего может возникнуть конфликт. Может, обладая этими знаниями и столкнувшись с конфликтом во время выполнения git revert,вы поймете, что вам совсем не нужен откат.
Иногда при отмене изменений может возникнуть ситуация, когда в файл в рабочей копии были внесены изменения с момента коммита находится не в том же состоянии, как в коммите, который мы отменяем. Звучит сложно, поэтому давайте приведем пример. Допустим, у нас есть следующий репозиторий.
Репозиторий: 4 коммита — 4 изменения в файле docs.md
В каждом из вышеуказанных коммитов всего один файл: docs.md. Над хэшем каждого коммита на рисунке указано содержимое docs.md, которое было внесено в данный коммит.
Теперь, когда перед глазами у вас есть диаграмма, давайте разберемся, в каком случае при откате возникнет конфликт файлов. На самом деле, конфликт возникнет при откате любого, кроме самого последнего коммита. Как мы уже говорили, при реверте коммита, его изменения отменяются, то есть файлы возвращаются к состоянию предыдущего коммита. Например, если мы попробуем откатить коммит 33ff, то содержимое файла docs.md изменится с
Содержимое docs.md, коммит 33ff
Содержимое docs.md, коммит 90ab
Но ведь в последнем коммите, т.е. в рабочей копии находится файл, содержимое которого
Содержимое docs.md, коммит c732f69
Some docs here
and here
also here
что совсем не соответствует содержимому коммита, который мы откатываем. То есть с момента того коммита мы уже успели изменить файл docs.md, и теперь Git не очень понимает, как именно делать откат:
- оставить файл в рабочей копии нетронутым или,
- наоборот, заменить файл в рабочей копии на файл после отката.
Поэтому и возникает конфликт. Git напрямую спрашивает у нас: «Какой файл мне оставить?». На практике это выглядит вот так:
# Пробуем откатить коммит .
$ git revert 33ff381
Auto-merging docs.md
CONFLICT (content): Merge conflict in docs.md
error: could not revert 33ff381... L-04: Adding info to docs
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
В следующем уроке, когда мы будем говорить о слиянии веток, мы научимся разрешать файловые конфликты.
Кстати, если вы попали в ситуацию, когда Git сообщил вам о конфликте файлов, а вы такого не ожидали, или вы просто передумали делать откат из-за конфликта, можете в любой момент выполнить git revert —abort. Это остановит откат, и вы сможете спокойно разобраться, откуда возникает конфликт.
Подведем итог
- git revert – команда, отменяющая изменения переданного коммита. Она заменяет файлы в рабочей копии на файлы предка переданного коммита, а затем делает коммит, чтобы сохранить изменения.
- Иногда во время отката возникают конфликты. Их не стоит бояться, но нужно быть внимательным и разобраться, откуда возникает конфликт.
- Если вы столкнулись с конфликтом во время отката и передумали продолжать git revert, выполните команду git revert —abort. Она вернет все как было и отменит откат.
- В сообщении реверт-коммита следует указывать полезную информацию: зачем вы сделали откат, каким образом вы сливали файлы, если были конфликты, и т.д.
Удаление и объединение коммитов. Команда git reset.
Помните, мы говорили про команду git checkout и перемещение указателя HEAD? Тогда мы перемещали только сам указатель HEAD и попадали в состояние detached head. Команда git reset позволяет нам перемещать указатель ветки вместе с указателем HEAD. Давайте разберем эту команду подробнее.
Команда git reset
Формат
git reset <ключи> <адрес коммита>
Что делает
Переносит указатель ветки на переданный коммит.
Пример
# Сделаем reset последнего коммита, который мы сделали по ошибке. При этом оставим файлы в том же состоянии.
$ git reset —soft HEAD^
# Отменим последние три коммита и удалим все изменения в файлах.
$ git reset —hard HEAD~3
HEAD is now at 2f96b73 L-04: Create main.py
Чтобы лучше понять смысл этой команды приведем граф репозитория. Изначально он выглядел так.
Теперь выполним «мягкий» reset последних двух коммитов.
$ git reset --soft HEAD~2
Теперь наш граф выглядит так.
Репозиторий после выполнения команды git reset
Причем файлы в рабочей копии остались в том же состоянии, что и были в коммите 62aa.
Как видно из рисунка, Git создал новый указатель – ORIG_HEAD. Этот указатель создается при выполнении команды git reset и ссылается на тот коммит, от которого мы делали reset. Как мы увидим ниже, это очень полезный в работе с git reset указатель.
Кстати, если мы сейчас сделаем коммит:
$ git add -A
$ git commit -m “L-04: Reset commit”
[main 03953f8] L-04: Reset commit
1 file changed, 3 insertions(+), 1 deletion(-)
То получим такую картину:
Новый коммит после ресета
То есть наш коммит пойдет по ответвлению от исходной ветки main. Поэтому будьте внимательны, создавая новые коммиты.
Итак, теперь поговорим, как на практике используется git reset. В основном, все использование сводится к трем пунктам:
- Редактирование/отмена последнего коммита.
- Объединение нескольких коммитов в один.
- Удаление коммитов.
- Отмена изменений команды git reset.
Давайте подробно разберем каждый пункт.
4.1. Редактирование или отмена последнего коммита
Если вы случайно опечатались в сообщении последнего коммита, или хотите добавить в него файлов, то git reset поможет вам. Но прежде всего нам нужно разобрать новые ключи уже знакомой команды git commit. Они потребуются нам в дальнейшем.
Команда git commit
Формат
git commit <ключи> <адрес переданного коммита>
Что делает
Создает новый коммит.
Пример
# Сделаем новый коммит с таким же сообщением и информацией об авторе, как у коммита, на который указывает ветка feature.
$ git commit -C feature
# Сделаем то же самое, но отредактируем сообщение коммита.
$ git commit -с feature
Теперь, когда мы познакомились с новыми ключами, можем перейти к редактированию последнего коммита. Последовательность действий в данной ситуации такая:
- Откатиться к предпоследнему коммиту командой git reset —soft HEAD^
- Добавить в коммит новые файлы, если вам это нужно, использовав команду git add.
- Выполнить git commit -c ORIG_HEAD, если вы хотите отредактировать сообщение, или git commit -C ORIG_HEAD, если вы хотите оставить сообщение коммита без изменений.
Полезно знать
Кстати, данная последовательность команд идентична git commit —amend. Чтобы отредактировать последний коммит, следуйте инструкции:
1. Если вы хотите добавить в коммит файлы, то для начала добавьте их в индекс командой git add <имя файла>. Если не хотите, то проверьте, что проиндексированных изменений нет, иначе они будут добавлены в последний коммит.
2. Выполните
- git commit —amend. Тогда откроется консольный редактор, где вы сможете отредактировать сообщение последнего коммита.
- git commit —amend -m «<новое сообщение>». Тогда сообщение последнего коммита будет заменено на <новое сообщение>.
В случае, если же вы хотите насовсем удалить последний коммит, просто выполните
$ git reset --hard HEAD^
HEAD is now at c732f69 L-04: Adding more info to docs
Таким образом, последний коммит будет удален и не будет отображаться в логе. Тем не менее, вернуться к нему вы сможете по указателю ORIG_HEAD, который оставит Git.
4.2. Объединение нескольких коммитов в один.
Иногда на практике возникают ситуации, когда для удобства восприятия и красивой истории необходимо объединить несколько последних коммитов в один. Порядок действий тут почти такой же, как и в случае редактирования последнего коммита:
- Выполните git reset —soft HEAD~n, где n это число коммитов, которые вы хотите объединить. Эта команда вернет указатель ветки на n коммитов назад, оставив изменения в индексе и рабочей копии нетронутыми. То есть после выполнения этой команды вы откатитесь на n коммитов назад, но все изменения внесенные этими коммитами останутся у вас в рабочей копии и индексе.
- Выполните git commit -c ORIG_HEAD, а затем отредактируйте сообщение коммита должным образом. Эта команда сделает коммит всех изменений в индексе. То есть она сделает коммит, который по своему содержимому представляет объединение последних n коммитов.
Собственно, это все. То есть на самом деле мы просто откатили последние несколько коммитов, а затем создали новый коммит, который собирает в себе все их изменения. При этом изначальные коммиты никуда не делись. Их не будет видно в истории, но если вы помните их хэш, то в любой момент сможете переключиться на любой из них с помощью команды git checkout.
4.3. Удаление последних нескольких коммитов.
Пожалуй, самая простая ситуация. Чтобы удалить последние n коммитов, выполните
$ git reset --hard HEAD~n
Где n – это, конечно, какое-то конкретное число. После этого удаленные коммиты не будут выводиться в истории (совсем, даже с флагом —all).
4.4. Отмена изменений команды git reset.
Если вы каким-то образом случайно выполнили git reset и решили все вернуть, просто переместите указатель ветки обратно, использовав команду
Эта команда вернет указатель ветки на коммит, с которого вы делали git reset, и вы вернете все изменения, даже если использовали ключ —hard.
Подведем итог
Итак, мы выучили новую команду git reset. Она используется, чтобы перемещать указатель ветки по графу Git. На практике ее в основном применяют для
- Редактирования или удаления последнего сделанного коммита.
- Объединения последних нескольких коммитов в один.
- Удаления последних нескольких коммитов.
После использования git reset, Git создаёт указатель ORIG_HEAD, который ссылается на коммит, с которого мы сделали reset.
Не стоит забывать, что даже если вы использовали git reset —hard, вы всегда можете вернуть все к изначальному состоянию, выполнив git reset ORIG_HEAD. Это вернет указатель ветки на коммит, с которого вы делали reset.
Различие команд git reset и git checkout и git revert
Сегодня мы изучили несколько команд, которые на первый взгляд очень похожи друг на друга. Давайте разберемся, в чем разница между ними. Для начала приведем краткую справку, чтобы напомнить, что делает каждая из команд.
Изученные команды
Команда git revert
Данная команда создает новый коммит, который отменяет действие одного из предыдущих коммитов. То есть новый коммит появится в истории, а предыдущие коммиты не изменятся.
Команда git reset
Работа этой команды зависит от вызова. Если вызвать git reset <ссылка>, то команда переместит ветку, на которую указывает HEAD, на переданную ссылку. Затем, в зависимости от опций —soft/—hard/—mixed, команда:
- либо останавливается,
- либо обновляет индекс и рабочую копию так, чтобы они соответствовали текущему коммиту,
- либо обновляет только индекс соответственно.
То есть она изменит историю, исключив из нее все коммиты, оставшиеся после HEAD. Говоря проще, команда действует так:
- Перемещает ветку, на которую указывает HEAD, или только HEAD (если он находится в состоянии detached HEAD). Останавливается на этом шаге, если передан ключ —soft.
- Делает индекс таким же, как в коммите, на который указывает HEAD. Останавливается на этом шаге, если не передан ключ —hard.
- Делает рабочую копию такой же, как коммит, на который указывает HEAD
Вы также можете вызвать основную команду (без ключей —hard и —soft), передав ей путь к файлу: git reset <ссылка> <имя файла>. В этом случае команда будет действовать также, но эффект будет отличаться, поэтому этот случай стоит рассмотреть отдельно.
В случае, если вы не передали ссылку, вместо нее будет подставлен HEAD. То есть она обновит индекс так, чтобы он соответствовал коммиту, на который указывает HEAD. Иначе говоря, скопирует файл из HEAD в индекс. То есть ее действие противоположно git add <имя файла>: она отменит добавление файла в индекс.
Если же вы указали ссылку, то в вашем индексе окажется файл из одного из предыдущих коммитов. То есть в рабочей копии файл не изменится, но если вы сразу же сделаете коммит, то в новом коммите будет файл из одного из предыдущих коммитов.
Команда git checkout
Действие этой команды тоже зависит от вызова.
- Если вызвать git checkout <ссылка>, то команда либо перемещает указатель HEAD на переданную ссылку (т.е. на другую ветку или коммит). Историю данная команда не меняет, если только не забыть использовать ключ —all: git log —all.
- Если выполнить git checkout <ссылка> <имя файла>, то команда скопирует содержимое файла из переданного коммита в рабочую копию и индекс.
Команда git restore
Копирует файл из переданной ссылки в рабочую копию, индекс или сразу и туда, и туда. Изначально эта команда появилась, как аналог git checkout <ссылка> <имя файла>, но ее функционал немного шире. Данная команда также не меняет историю.
Между git checkout, git reset и git restore есть несколько различий:
1. Команда git checkout <ссылка> похожа на git reset —hard <ссылка>: в обоих случаях перемещается HEAD и меняется рабочая копия. Однако есть и разница:.
- Первое отличие состоит в том, что git checkout перемещает только HEAD, в то время как git reset перемещает HEAD и ветку, на которую указывает HEAD.
- Еще одно отличие заключается в том, что git checkout проверяет, что у вас в рабочей копии нет измененных файлов, в то время как git reset просто заменяет все файлы без разбора.
В случае, когда мы находимся в состоянии detached head и у нас нет измененных файлов, эти две команды идентичны.
2. Команда git checkout <ссылка> <имя файла> также имеет общие черты с git reset <ссылка> <имя файла>. Отличие состоит в следующем:
git checkout <ссылка> <имя файла> изменяет и рабочую копию, и индекс, в то время, как git reset <ссылка> <имя файла> меняет только индекс. Кстати, идентичной командой для git checkout <ссылка> <имя файла> была бы команда git reset —hard <ссылка> <имя файла>, если бы ее можно было так использовать: это очень небезопасно для рабочей копии.
3. Команда git restore задумывалась как аналог git checkout <ссылка> <имя файла>. Тем не менее, между ними есть следующие отличия:
- Используя git checkout <ссылка> <имя файла>, в качестве ссылки вы можете передать только определенный коммит, в то время как git restore может скопировать файл в рабочую копию прямо из индекса
- Команда git checkout всегда копирует файлы одновременно и в рабочую копию, и в область индекса, что не всегда удобно. В свою очередь, git restore может принять ключи —staged и —worktree, определяющие, скопировать файл только в область индекса, только в рабочую копию, или сразу в оба места.
Для закрепления, давайте приведем таблицу сравнения команд git revert, git reset, git checkout и git reset по их взаимодействию с указателем HEAD, историей, индексом и рабочей копией.
ПРАКТИЧЕСКИЙ БЛОК
Задание. Библиотека Geometric Lib.
Сегодня было много теории, пора закрепить ее на практике. Мы снова воспользуемся командой git clone (как и в прошлом уроке). Напомним, что она копирует удаленный репозиторий к вам на компьютер.
Также напомним, что наш удаленный репозиторий представляет собой библиотеку на Python, которая позволяет высчитывать площади и периметры некоторых геометрических фигур.
Ссылка на библиотеку Geometric Lib на GitHub: https://github.com/smartiqaorg/geometric_lib
В этот раз в нашем репозитории появились еще две ветки, теперь его структура выглядит следующим образом:
Структура библиотеки geometric_lib
# Ветка main (Основная стабильная ветка)
geometric_lib
├── circle.py
├── square.py
└── docs
└── README.md
# Ветка develop (Ветка разработки)
geometric_lib
├── circle.py
├── square.py
└── docs
└── README.md
└── calculate.py
# Ветка feature (Ветка для новых функций)
geometric_lib
├── circle.py
├── square.py
└── docs
└── README.md
└── rectangle.py
К файлам добавилось три новых: triangle.py, rectangle.py и calculate.py. Первый содержит функции для вычисления периметра и площади треугольника, второй – то же для квадрата. В свою очередь, calculate.py объединяет функционал этих файлов: он предназначен для расчета площади и периметра переданной пользователем фигуры.
Задание. Условие.
1. Клонирование репозитория и знакомство с его структурой
1.1. Выполните git clone https://github.com/smartiqaorg/geometric_lib. Эта команда создаст директорию geometric_lib/ на вашем компьютере и скопирует наш удаленный репозиторий. Не забудьте перейти в эту директорию командой cd geometric_lib, когда клонирование будет завершено.
Кстати, когда вы склонируете к себе наш репозиторий, у вас будет только одна локальная ветка: main. Чтобы создать остальные, нужно выполнить git checkout <имя ветки>. Эта команда переключит вас на коммит, на который указывает удаленная ветка и создаст там локальную ветку с таким же именем. Эту команду нужно запустить для каждой ветки отдельно. То есть у вас получится два запуска: для ветки feature и ветки develop.
1.2. Постройте полный граф истории, чтобы познакомиться со структурой комитов.
2. Работа с веткой feature
В последнем коммите ветки feature допущена ошибка. Откатите этот неудачный коммит.
3. Работа с веткой develop
Теперь заметьте, что у нас есть два коммита в ветке develop одной и той же тематики: «L-04: Add calculate.py», «L-04: Update docs for calculate.py». Объедините их в один коммит и напишите к нему пояснение.
4. Эксперименты. Работа с файлами calculate.py и rectangle.py в ветке experiments
Ветку develop мы привели в порядок. Теперь давайте представим, что мы хотим протестировать совместную работу файлов calculate.py и rectangle.py. Чтобы не мешать работе других файлов, создадим отдельную ветку experiment, которая будет брать начало в конце ветки main. Новая ветка будет хранить коммиты с результатами наших экспериментов. Задания:
4.1. Создайте новую ветку с именем experiment. Как было сказано выше, она пригодится нам, чтобы хранить наши экспериментальные коммиты.
4.2. Мы хотим провести эксперименты с файлом calculate.py, но текущая документация (файл docs/README.md) устарела. Добавьте в нашу рабочую копию документацию, которая содержит информацию о файле calculate.py. Такая есть, например, в последнем коммите ветки develop. Для этого скопируйте файл docs/README.md из последнего коммита ветки develop в рабочую копию. Подсказка: указатель develop находится на последнем коммите ветки develop.
4.3. Добавьте в индекс и рабочую копию файл calculate.py из последнего коммита ветки develop.
4.4. Добавьте все нужные файлы в индекс и сделайте коммит.
4.5. Мы поняли, что файлы circle.py и square.py могут помешать чистоте наших экспериментов. Удалите их и сделайте коммит.
Задание. Решение.
# п 1. Клонируем репозиторий и активируем локальные ветки
$ git clone https://github.com/smartiqaorg/geometric_lib
$ cd geometric_lib/
$ git branch --all
$ git checkout develop
$ git checkout feature
$ git branch --all
# п 2. Работаем с веткой feature: откатываем коммит
$ git checkout feature
$ git log
$ git revert HEAD
$ git log
# п 3. Работа с веткой develop: Объединение коммитов
$ git checkout develop
$ git log
$ git reset --soft HEAD~2
$ git log
$ git status
$ git commit -c ORIG_HEAD
$ git log
# п 4. Работа с веткой experiment: Перенос и удаление файлов
$ git checkout main
$ git checkout -b experiment
$ git restore --worktree docs/README.md --source develop
$ git status
$ git restore --worktree --staged calculate.py --source develop
$ git status
$ git add docs/README.md
$ git commit -m "L-04: Experimental commit"
$ git rm circle.py square.py
$ git status
$ git commit -m "L-04: Deleted circle and square"
Более подробный разбор задания
Также на странице Задания и Ответы по курсу Git мы даем более подробный разбор текущего задания.
Приводим в нем не только команды Git, но и их консольный вывод, а также даем комментарии к каждой команде.
Как вам материал?
Читайте также
With its complex commands and not-so-intuitive user interface, Git has a very steep learning curve. While trying to master this version control system, you may encounter some perplexing scenarios and unwanted states such as Git detached HEAD. Though the message You are in a detached HEAD state may sound somewhat strange to newcomers, it is a perfectly valid state of the Git repository and recovering from it is not that difficult.
In today’s article, our web development agency in Chicago explores the meaning of detached HEAD in Git and various situations that can cause this state. We will also demonstrate ways to recover from this situation rapidly.
What is HEAD in Git?
The term HEAD in Git refers to the latest commit of your currently checked-out branch. A Git repository is composed of different objects and references. While objects are in various relations, references point to objects or other references. Commits are the most important objects in a Git repository. Other objects include blobs and trees.
On the other hand, the main references in Git are branches. HEAD is also an essential type of reference that tracks the current point in a Git repository and tells you where you are. For example, when you use the git log command, HEAD tells the version control system to display results from a specific commit. Each time you create a new commit, HEAD will point to its parent.
What does Git detached HEAD mean?
HEAD usually points to a branch name, so when you create a new commit, though the branch reference will be updated to point to it, HEAD will remain unchanged. When you switch to another branch, HEAD will be updated to point to it. This means that HEAD is attached to a branch. You may visualize it in the following way.
This diagram shows us that HEAD points to the master branch, which in turn points to the last commit. That’s the attached HEAD state. Now, let’s try to replicate detached HEAD in Git by checking out one of the commits:
git checkout 87ec91d
As you can see below, HEAD no longer points to the master branch. Instead, it points directly to a commit. This is known as detached HEAD in Git.
Another way to enter a detached HEAD state is to check out to a remote branch before previously fetching it. If you check out to the origin (main) branch, which is read-only, you will get notified that you are in the detached HEAD state. There are other scenarios as well. For instance, checking out to a specific tag name or adding ^0 on any given branch will result in Git detached HEAD state.
Benefits of detached HEAD in Git
Detached HEAD in Git is a useful state since it gives you the option to navigate to a previous point in the history of your project. Let’s say there is an issue in your project that you are attempting to resolve. A bug appeared, so you want to check if it was already there a few days ago. By using the git log command and filtering the results by the date, you can start the relevant commit hash. From there, you can check out the commit in question and run tests to check the state of the application you are developing.
Besides simply returning to the previous project’s state, another benefit of Git detached HEAD is that you can actually make changes to the history of your project. Here is what the detached HEAD message looks like:
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.
As you can see, this way, you are basically creating an alternate history of your project. Now, let’s explore this state by creating additional commits and afterward checking what our repository looks like:
echo “Alternate history” > new-file.txt
git add .
git commit -m “Create new file”
echo “Another line” >> new-file.txt
git commit -a -m “Add a new line to the file”
This results in two additional commits descending from our second commit shown in the diagram above. Now, let’s check what happens when we run git log — oneline:
Git log –oneline
c8f4f8e (HEAD) Add a new line to the file
ec0118f Create new file
87ec91d Add a line to the file
3d11eb0 Create file
That means our diagram currently looks like this:
How to fix Git detached HEAD?
First of all, fixing is not quite the term we should use here. If we are attempting to fix something, something is broken. However, as we have mentioned in the introduction to this article, a detached HEAD is a valid state in Git, and as such, it doesn’t require fixing. That’s why we’ll dedicate this section of our article to ways to revert to the original state.
There are three ways to get back to the original state depending on the reasons you found yourself in detached HEAD mode. Let’s check them out.
You are there unintentionally
In case you had no intention to check out a commit and reach the detached HEAD state in Git, all you have to do in order to revert to a previous state is to check out the branch you were in before. You do this by entering:
git checkout <branch-name>
Git versions 2.23.0 and newer allow you to substitute the checkout command with the switch command. That means you can also type:
git switch <branch-name>
You have experimented with some changes, but now you wish to discard them
Perhaps you wanted to try something new with a couple of commits, but the experiment didn’t produce satisfying results. By switching back to your original branch, you will also discard any changes you have made in the Git detached HEAD state. So, just like in our previous example, all you need to do is enter the following command:
git checkout <branch-name>
You have experimented with some changes, and you wish to keep them
Let’s say you did the same thing as in our previous example, but you wish to keep the changes you made this time. To do so, you need to create a new branch and navigate to it. Whether you choose to create a new branch right after entering Git detached HEAD state or upon making some commits, the results will remain the same, and the changes will be saved. However, you need to ensure that you have a new branch created before switching back to your original branch. Let’s explore this case:
git branch alt-history
git checkout alt-history
You’ll notice that git log –oneline returns the same results as before. The only change is the branch name indicated in the last commit. Let’s take a look:
git log –oneline
c8f4f8e (HEAD -> alt-history) Add a new line to the file
ec0118f Create new file
87ec91d Add a line to the file
3d11eb0 Create file
Here is what our diagram looks like in this case:
How to command Git not to show the detached HEAD message?
Now that we have become familiar with Git detached HEAD state, some of our readers may find it tiresome having to view that message each time they check out a commit. But, there is a way to avoid that message with the following command:
git config advice.detached head false
That way, the change applies only to your current repository. You can add the -global modifier if you wish it to apply to each repository.
Final thoughts
As you can see throughout this article, the detached HEAD state is nothing to worry about. It is not an error message but rather a notification about the less common state in which you currently are. Furthermore, it provides additional opportunities and ways to work in this version control system.
We hope you have enjoyed today’s article and learned new ways to work in Git. In case you require any assistance with your projects or if your business needs custom-coded websites to boost online presence and improve brand reputation, feel free to schedule a call with our agency.
- Создание локального репозитория и синхронизация с удаленным
- Клонирование и субмодули
- Как не коммитить изменения прав на файлы?
- HEAD
- HEAD detached
- Сводка коммитов
- Хэш последнего коммита
Создание локального репозитория и синхронизация с удаленным
Для начала нужно создать пустой репозиторий на аккаунте хостинга git-репозиториев.
Github и GitLab в пустом репозитории показывают информацию о том что делать дальше.
Затем инициализировать локальный репозиторий, выбрать директорию на диске где будет хранилище локального репозитория и ввести команду:
$ git init
Далее надо добавить нужные файлы в репозиторий и закоммитить:
$ git add .
$ git commit -m 'init repository'
Теперь нужно добавить удаленный репозиторий (на аккаунте хостинга):
$ git remote add origin <url>
Но если нужно сменить ранее добавленный адрес на новый, тогда нужно:
$ git remote set-url origin <url>
Результат добавления можно посмотреть при помощи команды:
$ git remote -v
Должно быть что-то типа:
origin <url> (fetch)
origin <url> (push)
Затем нужно перенести все изменения на удаленный репозиторий так:
git push -u origin main
Узнать о текущем статусе локального репозитория:
$ git status
На ветке main
Ваша ветка обновлена в соответствии с «origin/main».
Неотслеживаемые файлы:
(используйте «git add <файл>…», чтобы добавить в то, что будет включено в коммит)
content/development/127_git.md
ничего не добавлено в коммит, но есть неотслеживаемые файлы (используйте «git add», чтобы отслеживать их)
Клонирование и субмодули
Можно создать репозиторий в аккаунте хостинга, затем клонировать его себе. После чего вставить нужные файлы в директорию репозитория и закоммитить изменения.
Стоит заметить, что после клонирования, в директории где вызвана командная строка, будет создана директория с именем репозитория, надо в нее перейти прежде чем осуществлять действия.
$ git clone <url>
$ cd <dir>
$ git add .
$ git commit -m 'init repository'
$ git push
В репозитории могут быть субмодули, это тоже репозитории, которые являются зависимостями целевого репозитория. Вложенность субмодулей может быть глубокая (субмодули субмодулей и т.д.), а при простом клонировании
git clone
происходит клонирование только указанного вurl
репозитория, без клонирования субмодулей.
Чтобы клонировать репозиторий и все субмодули необходимо:
$ git clone --recursive <url>
Чтобы обновить все субмодули репозитория можно сделать так:
$ git submodule update --recursive --remote
Инициализировать и/или обновить все субмодули репозитория за одну команду:
$ git submodule update --init --recursive
Добавить субмодуль в репозиторий:
$ git submodule add <url> <path>
Где:
-
url
— адрес для клонирования репозитория с сервера -
path
— путь относительно текущего каталога где будет располагаться субмодуль
Вариантов удаления субмодулей через командную строку мне не удалось найти, поэтому пришлось руками удалить субмодуль из репозитория:
- удалить информацию о субмодуле из
.gitmodules
и.git/config
(секции вида submodule<path>
) - удалить директорию субмодуля
- закомитить изменения
Как не коммитить изменения прав на файлы?
На linux
часто происходит ситуация когда происходит клонирование репозитория, а затем изменение прав на файлы и эти изменения фиксируются в локальном репозитории.
Ситуация неприятная потому что теперь нужно коммитить изменение прав, а фактически правок в коде не было.
Решается это при помощи команды:
$ git config core.filemode false
HEAD
HEAD
это специальный указатель на определенный (текущий) коммит. Этот коммит может быть в какой-то ветке, например в main
, или быть вне ветки — это и есть detached HEAD.
HEAD
указывает на ту ревизию репозитория, которая сейчас на данный момент в репозитории, т.е. на что указываетHEAD
, то сейчас и лежит в репозитории. Коммит, на который указываетHEAD
, будет являться родительским для нового коммита.
При помощи checkout
можно смещать HEAD
в нужный коммит (ходить по истории коммитов) или в нужную ветку — последний коммит в ветке. При смещении HEAD
(между ветками или между коммитами) произойдет изменение текущего содержимого директории репозитория на то, чтобы было в этом коммите.
HEAD detached
При рекурсивном клонировании одного репозитория, по неопытности можно забыть и не переключить текущую рабочую ветку в сабмодуле и начал работать в нем, в итоге, при попытке push
можно получить ошибку:
$ git push
fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)
state now, use
git push origin HEAD:<name-of-remote-branch>
Конечно же git
предупреждал ранее в команде status
:
$ git status
HEAD detached at 6b4f07f
...
НАДО БЫЛО в этот момент переключиться на нужную ветку (например на main
) и все было бы без проблем (при отсутсвии коммитов):
$ git checkout master
Относительно проблемы, если коммит вне ветки (detached HEAD
), то прямым образом он никак не может быть отправлен на сервер, один из способов решения:
- создать новую ветку, на которую будет указывать
HEAD
и куда автоматически попадет вневеточный коммит —git branch temp-branch
- переключиться на нужную ветку (например на
main
) —git checkout main
- влить созданную ветку
temp-branch
в текущую —git merge temp-branch
- отпраить ветку
main
на сервер —git push origin main
Еще почитать можно здесь или тут или там.
Сводка коммитов
В самом простом варианте, историю коммитов можно посмотреть так:
$ git log --oneline
68d24e3 (HEAD -> main, origin/main) updated flatness, fixed service page
28fa96e added post 68
d3fa0c0 added integrations category, added posts 48, 67, 71, 80, 89, 93
508407f added posts 61, 88
c7ef705 fixed volume php_error.log
Но есть другие варианты.
Смотрим как работать с историей коммитов (вот еще), находим там интересную опцию --pretty
. Эта опция позволяет настроить вывод информации о коммите. Например так:
$ git log --pretty=format:"%h - %an/%cn, %ar : %s"
506dca7 - Byurrer/Byurrer, 4 часа назад : adapted table names for tests
edd43d0 - Byurrer/Byurrer, 7 часов назад : small fixeds
18575eb - Byurrer/Byurrer, 7 часов назад : added select db redis
Или так:
$ git log --pretty=oneline
506dca7724f2c94511f0128d34b57c919abd70f0 (HEAD -> dev, origin/dev) adapted table names for tests
edd43d00722a835a6ec24df14c38e103894d4128 (master) small fixeds
18575eb3c91f6ac298b9a7e0113471f0f5ddff59 added select db redis
Теперь нужно выбрать коммиты, по которым будем показывать информацию. Это можно сделать так:
$ git log --pretty=format:'%h - %an/%cn, %ar : %s' from..to
Где [from, to]
это коммиты «от и до» куда (включительно) нужно показать историю.
Хэш последнего коммита
Чтобы показать историю с прошлого коммита до самого свежего (задеплоенного), нужно перед принятием изменений из репозитория pull
получить указатель на HEAD
. Сделать это можно несколькими способами.
Получить полный SHA-1
хэш коммита:
$ git rev-parse HEAD
edd43d00722a835a6ec24df14c38e103894d4128
Или так:
$ git log -n 1 --pretty=format:"%H"
edd43d00722a835a6ec24df14c38e103894d4128
Получить короткий хэш:
$ git log -n 1 --pretty=format:"%h"
edd43d0
Получить хэш последнего коммита в нужной ветке:
$ git show-ref --heads --hash master
edd43d00722a835a6ec24df14c38e103894d4128
Before showing how to reconcile detached HEAD with master/origin, let’s figure out what is HEAD. The HEAD is a symbolic reference to the branch you are currently on. It contains a pointer to another reference. It is the last checked out point in a repository.
Let’s create a branch pointing to the commit currently pointed to by detached HEAD.
Creating a branch
Run the following to reattach your HEAD to the new temp git branch:
git branch temp
git checkout temp
or abbreviat the above commands as:
Comparing
Compare the current commit and its history with the normal branch you are going to work on (suppose, the name of remote is origin, which is by default):
git log --graph --decorate --pretty=oneline --abbrev-commit master origin/master temp
git diff master temp
git diff origin/master temp
Updating master
Update master to point to it like this:
git branch -f master temp
git checkout master
or
git checkout -B master temp
Deleting the branch
Delete the branch by executing the following:
Pushing
Push the re-established history with the git push command:
If the remote branch can not be fast-forwarded to the new commit, attach the —force option to git push:
Update master to point to it like this:
git branch -f master temp
git checkout master
or
git checkout -B master temp
Delete the branch by executing the following:
Push the reestablished history with the git push command:
If the remote branch can not be fast-forwarded to the new commit, attach the —force option to git push:
The Detached HEAD state warns that your activity is “detached” from the project’s development. It allows checking out commits and examining the repository’s older state without creating a local branch. The HEAD updates the git checkout command to point the specified branch or commit. There’s no problem when HEAD points to a branch, but when it points to a commit, it moves to a detached HEAD state.
When HEAD is not detached, it points to a branch’s reference, and the branch points to the commit. Thus, HEAD is attached to a branch. When you create a new commit, the branch that HEAD points to is updated so as to point to that newly created commit. HEAD will follow automatically because it just points to the branch.