Git: Deleted current branch and lost reflog - macos

I ran into an unusual git issue earlier that I've since resolved, but I'm still curious as to why it happened.
The problem occurred when I accidentally deleted the branch I was currently working on. Normally git wouldn't allow this, but due to case-insensitivity on OSX I got myself into a situation where I thought I had two branches, one named feature/ONE and another named feature/one. Thinking these were two separate branches (coming from a mostly linux/case-sensitive background) and that I was working on feature/ONE I attempted to delete feature/one using git branch -D.
I quickly noticed what I had done, tried to retrieve my lost work from git reflog, which gave me the error fatal: bad default revision 'HEAD'. I attempted to get back into a normal state using git checkout -f develop, which worked. However, when I looked at git reflog after this unfortunately it only had one entry stating checkout: moving from feature/ONE to develop. No previous actions appeared in the log.
I've compiled some steps to replicate this kind of scenario (presumably this is only possible on case-insensitive filesystems):
mkdir test
cd test
git init
echo 'hi' > file1
git add file1
git commit -m 'test commit 1'
git checkout -b new-branch
echo 'test2' > file2
git add file2
git commit -m 'test commit 2'
git branch -D NEW-branch
git checkout -f master
git reflog
I've since been able to find my lost commits by checking git-fsck, but my question is this:
Why did this sequence of actions break reflog? Shouldn't reflog still know the history of the HEAD ref, even though the branch was deleted?

In normal circumstances, HEAD either points to a SHA1 (in which case it's called detached) or it points to an existing branch reference (in which case the named branch is considered to be checked out).
When you check out new-branch (HEAD points to refs/heads/new-branch) and then somehow manage to delete the new-branch branch, Git simply deletes the branch's ref file (.git/refs/heads/new-branch) and the branch's reflog file (.git/logs/refs/heads/new-branch). Git does not delete HEAD, nor does it update it to point somewhere else (such as the SHA1 that new-branch used to point to), because there shouldn't be a need—you're not supposed to be able to delete the current branch. So HEAD still references the now-deleted branch, which means that HEAD no longer points to a valid commit.
If you then do git checkout -f master, Git updates HEAD to point to refs/heads/master, a new entry is added to HEAD's reflog file (.git/logs/HEAD), the files are checked out, and the index is updated. All of this is normal—this is what Git always does when you check out another branch.
The issue you encountered arises from how the reflog file is updated and how git reflog processes the updated reflog file. Each reflog entry contains a "from" and "to" SHA1. When you switch from the non-existent new-branch branch to master, Git doesn't know what the "from" SHA1 is. Rather than error out, it uses the all-zeros SHA1 (0000000000000000000000000000000000000000). The all-zeros SHA1 is also used when a ref is created, so this most recent reflog entry makes it look like HEAD was just created, when in fact it was never deleted. Apparently the git reflog porcelain command stops walking the reflog when it encounters the all-zeros SHA1 even if there are more entries, which is why git reflog only prints one entry.
The following illustrates this:
$ git init test
Initialized empty Git repository in /home/example/test/.git/
$ cd test
$ echo hi >file1
$ git add file1
$ git commit -m "test commit 1"
[master (root-commit) 3c79ff8] test commit 1
1 file changed, 1 insertion(+)
create mode 100644 file1
$ git checkout -b new-branch
Switched to a new branch 'new-branch'
$ echo test2 >file2
$ git add file2
$ git commit -m "test commit 2"
[new-branch f828d50] test commit 2
1 file changed, 1 insertion(+)
create mode 100644 file2
$ cat .git/HEAD
ref: refs/heads/new-branch
$ cat .git/refs/heads/new-branch
f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1
$ git update-ref -d refs/heads/new-branch
$ cat .git/HEAD
ref: refs/heads/new-branch
$ cat .git/refs/heads/new-branch
cat: .git/refs/heads/new-branch: No such file or directory
$ cat .git/logs/HEAD
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email#example.com> 1411018898 -0400 commit (initial): test commit 1
3c79ff8fc5a55d7c143765b7f749db4dd8526266 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email#example.com> 1411018898 -0400 checkout: moving from master to new-branch
3c79ff8fc5a55d7c143765b7f749db4dd8526266 f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1 Your Name <email#example.com> 1411018898 -0400 commit: test commit 2
$ git checkout -f master
Switched to branch 'master'
$ cat .git/logs/HEAD
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email#example.com> 1411018898 -0400 commit (initial): test commit 1
3c79ff8fc5a55d7c143765b7f749db4dd8526266 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email#example.com> 1411018898 -0400 checkout: moving from master to new-branch
3c79ff8fc5a55d7c143765b7f749db4dd8526266 f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1 Your Name <email#example.com> 1411018898 -0400 commit: test commit 2
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email#example.com> 1411018898 -0400 checkout: moving from new-branch to master
$ git reflog
3c79ff8 HEAD#{0}: checkout: moving from new-branch to master
As you can see, HEAD's reflog still has all of the old entries—they're just not shown by git reflog. I consider this to be a bug in Git.
Side note: When you delete a ref, the corresponding log is also deleted. I consider this to be a bug, as there's no way to completely undo an accidental ref deletion unless you have a backup of the log.

Deleted current branch and lost reflog
Two years later, this issue should be alleviated in Git 2.13 (Q2 2017).
See commit 39ee4c6, commit 893dbf5, commit de92266, commit 755b49a (21 Feb 2017) by Kyle Meyer (kyleam).
(Merged by Junio C Hamano -- gitster -- in commit c13c783, 27 Feb 2017)
branch: record creation of renamed branch in HEAD's log
Renaming the current branch adds an event to the current branch's log
and to HEAD's log.
However, the logged entries differ.
The entry in the branch's log represents the entire renaming operation (the old and new hash are identical), whereas the entry in HEAD's log represents
the deletion only (the new sha1 is null).
Extend replace_each_worktree_head_symref(), whose only caller is
branch_rename(), to take a reflog message argument.
This allows the creation of the new ref to be recorded in HEAD's log.
As a result, the renaming event is represented by two entries (a deletion and a creation entry) in HEAD's log.
It's a bit unfortunate that the branch's log and HEAD's log now represent the renaming event in different ways.
Given that the renaming operation is not atomic, the two-entry form is a more
accurate representation of the operation and is more useful for debugging purposes if a failure occurs between the deletion and creation events.
It would make sense to move the branch's log to the two-entry form, but this would involve changes to how the rename is carried out and to how the update flags and reflogs are processed for deletions, so it may not be worth the effort.

Related

Xcode 14 "switched" to older commit, how to get back more recent commits? [duplicate]

In Git, I was trying to do a squash commit by merging in another branch and then resetting HEAD to the previous place via:
git reset origin/master
But I need to step out of this. How can I move HEAD back to the previous location?
I have the SHA-1 fragment (23b6772) of the commit that I need to move it to. How can I get back to this commit?
Before answering, let's add some background, explaining what this HEAD is.
First of all what is HEAD?
HEAD is simply a reference to the current commit (latest) on the current branch.
There can only be a single HEAD at any given time (excluding git worktree).
The content of HEAD is stored inside .git/HEAD and it contains the 40 bytes SHA-1 of the current commit.
detached HEAD
If you are not on the latest commit - meaning that HEAD is pointing to a prior commit in history it's called detached HEAD.
On the command line, it will look like this - SHA-1 instead of the branch name since the HEAD is not pointing to the tip of the current branch:
A few options on how to recover from a detached HEAD:
git checkout
git checkout <commit_id>
git checkout -b <new branch> <commit_id>
git checkout HEAD~X // x is the number of commits to go back
This will checkout the new branch pointing to the desired commit.
This command will checkout to a given commit.
At this point, you can create a branch and start to work from this point on.
# Checkout a given commit.
# Doing so will result in a `detached HEAD` which mean that the `HEAD`
# is not pointing to the latest so you will need to checkout branch
# in order to be able to update the code.
git checkout <commit-id>
# Create a new branch forked to the given commit
git checkout -b <branch name>
git reflog
You can always use the reflog as well.
git reflog will display any change which updated the HEAD and checking out the desired reflog entry will set the HEAD back to this commit.
Every time the HEAD is modified there will be a new entry in the reflog
git reflog
git checkout HEAD#{...}
This will get you back to your desired commit
git reset --hard <commit_id>
"Move" your HEAD back to the desired commit.
# This will destroy any local modifications.
# Don't do it if you have uncommitted work you want to keep.
git reset --hard 0d1d7fc32
# Alternatively, if there's work to keep:
git stash
git reset --hard 0d1d7fc32
git stash pop
# This saves the modifications, then reapplies that patch after resetting.
# You could get merge conflicts if you've modified things which were
# changed since the commit you reset to.
Note: (Since Git 2.7) you can also use the git rebase --no-autostash as well.
git revert <sha-1>
"Undo" the given commit or commit range.
The revert command will "undo" any changes made in the given commit.
A new commit with the undo patch will be committed while the original commit will remain in history as well.
# Add a new commit with the undo of the original one.
# The <sha-1> can be any commit(s) or commit range
git revert <sha-1>
This schema illustrates which command does what.
As you can see there, reset && checkout modify the HEAD.
First reset locally:
git reset 23b6772
To see if you're on the right position, verify with:
git status
You will see something like:
On branch master Your branch is behind 'origin/master' by 17 commits,
and can be fast-forwarded.
Then rewrite history on your remote tracking branch to reflect the change:
git push --force-with-lease // a useful command #oktober mentions in comments
Using --force-with-lease instead of --force will raise an error if others have meanwhile committed to the remote branch, in which case you should fetch first. More info in this article.
Quickest possible solution (just 1 step)
Use git checkout -
You will see Switched to branch <branch_name>. Confirm it's the branch you want.
Brief explanation: this command will move HEAD back to its last position. See note on outcomes at the end of this answer.
Mnemonic: this approach is a lot like using cd - to return to your previously visited directory. Syntax and the applicable cases are a pretty good match (e.g. it's useful when you actually want HEAD to return to where it was).
More methodical solution (2-steps, but memorable)
The quick approach solves the OP's question. But what if your situation is slightly different: say you have restarted Bash then found yourself with HEAD detached. In that case, here are 2 simple, easily remembered steps.
1. Pick the branch you need
Use git branch -v
You see a list of existing local branches. Grab the branch name that suits your needs.
2. Move HEAD to it
Use git checkout <branch_name>
You will see Switched to branch <branch_name>. Success!
Outcomes
With either method, you can now continue adding and committing your work as before: your next changes will be tracked on <branch_name>.
Note that both git checkout - and git checkout <branch_name> will give additional instructions if you have committed changes while HEAD was detached.
The question can be read as:
I was in detached-state with HEAD at 23b6772 and typed git reset origin/master (because I wanted to squash). Now I've changed my mind, how do I go back to HEAD being at 23b6772?
The straightforward answer being: git reset 23b6772
But I hit this question because I got sick of typing (copy & pasting) commit hashes or its abbreviation each time I wanted to reference the previous HEAD and was Googling to see if there were any kind of shorthand.
It turns out there is!
git reset - (or in my case git cherry-pick -)
Which incidentally was the same as cd - to return to the previous current directory in *nix! So hurrah, I learned two things with one stone.
When you run the command git checkout commit_id then HEAD detached from 13ca5593d(say commit-id) and branch will be on longer available.
Move back to previous location run the command step wise -
git pull origin branch_name (say master)
git checkout branch_name
git pull origin branch_name
You will be back to the previous location with an updated commit from the remote repository.
Today, I mistakenly checked out on a commit and started working on it, making some commits on a detach HEAD state. Then I pushed to the remote branch using the following command:
git push origin HEAD: <My-remote-branch>
Then
git checkout <My-remote-branch>
Then
git pull
I finally got my all changes in my branch that I made in detach HEAD.
This may not be a technical solution, but it works. (if anyone of your teammate has the same branch in local)
Let's assume your branch name as branch-xxx.
Steps to Solve:
Don't do update or pull - nothing
Just create a new branch (branch-yyy) from branch-xxx on his machine
That's all, all your existing changes will be in this new branch (branch-yyy). You can continue your work with this branch.
Note: Again, this is not a technical solution, but it will help for sure.
Move last non-pushed commits to a new branch
If your problem is that you started committing on the WRONG_BRANCH, and want to move those last non-pushed commits to the RIGHT_BRANCH, the easiest thing to do is
git checkout WRONG_BRANCH
git branch RIGHT_BRANCH
git reset —-hard LAST_PUSHED_COMMIT
git checkout RIGHT_BRANCH
At this point, if you run git log HEAD you will see that all your commits are there, in the RIGHT_BRACH.
Data
WRONG_BRANCH is where your committed changes (yet to push) are now
RIGHT_BRANCH is where your committed changes (yet to push) will be
LAST_PUSHED_COMMIT is where you want to restore the WRONG_BRANCH to

Xcode won't commit to master branch after Cherry-Picking [duplicate]

I was doing some work in my repository and noticed a file had local changes. I didn't want them anymore so I deleted the file, thinking I can just checkout a fresh copy. I wanted to do the Git equivalent of
svn up .
Using git pull didn't seem to work. Some random searching led me to a site where someone recommended doing
git checkout HEAD^ src/
(src is the directory containing the deleted file).
Now I find out I have a detached head. I have no idea what that is. How can I undo?
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 called tmp.
Run git checkout master
If you would like to incorporate the changes you made into master, run git merge tmp from the master branch. You should be on the master branch after running git checkout master.
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
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> …
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
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:
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.
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.
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.
Since "detached head state" has you on a temp branch, just use git checkout - which puts you on the last branch you were on.
you probably did git reset --hard origin/your-branch.
Try to just git checkout your-branch
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}
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.
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.
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
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.
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.
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>
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.
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.
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
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
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.
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 and git 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.
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 head
git checkout {{your_working_branch}} -- checkout==switch branch
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
git pull origin master
worked for me. It was just about giving remote and branch name explicitly.
This works for me, It will assign a new branch for detached head :
git checkout new_branch_name detached_head_garbage_name
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
If you hate head and wanna go back to main instead:
git checkout main
If you love head but just wish main tracked it:
git checkout -B main HEAD
(This works with any branch name, not just main, and for any commit pointer, not just HEAD.)

How to identify if a recent pull had changes inside a directory

I currently have a git project with the structure:
z.txt
foo/a.txt
foo/b.txt
using bash how can I identify after running $ git pull that either a.txt and/or b.txt (i.e anything under the foo directory) have been altered?
A. If you already pulled
git diff
You can use git diff and specifically:
git diff commit1..commit2 --name-only; or
git diff commit1..commit2 --name-status
The following descriptions are from the doco.
--name-only
Show only names of changed files.
--name-status
Show only names and status of changed files. See the description of the --diff-filter option on what the status letters mean.
git pull tells you the commit ids it merges/fast-forwards:
/mnt/c/git/repo666 (develop)>git pull
Updating f86907f7a..a708dcfe8
In this case the command would be:
git diff f86907f7a..a708dcfe8 --name-status
git log
To see differences per commit you could use git log with --name-only or --name-status.
B. Before a pull
If you haven't pulled and you want a peek at the potential changes you can git fetch the branch (not pull) and compare the local copy of the remote branch your current branch.
/mnt/c/git/repo666(develop)>git fetch // not git pull
(...)
/mnt/c/git/repo666(develop)>git status
On branch develop
Your branch is behind 'origin/develop' by 3 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
/mnt/c/git/Platform (develop)>git diff develop origin/develop --name-status
(Please note I used git diff branch origin/branch and not git diff origin/branch so that is shown in the desired order i.e. if the file was added in origin/develop we want to see it as added not deleted.)
Note on git pull output
Please note that the output of git pull contains added and renamed files twice
Fast-forward
...
src/Folder1/Services/File1.cs | 30 +
src/Folder1/Services/File2.cs | 7 +
...
src/Folder1/ViewModels/XViewModel.cs | 8 +-
...
src/{Abc.Common/Services => Abc/Contracts/Area1}/Area1File1.cs | 7 +-
...
89 files changed, 7254 insertions(+), 4897 deletions(-)
create mode 100644 src/Folder1/Services/File1.cs
create mode 100644 src/Folder1/Services/File2.cs
...
rename src/{Abc.Common/Services => Abc/Contracts/Area1}/Area1File1.cs (83%)
...
We can get closer than what #tymtam suggests, if you're willing to do things before you run git pull.
Get the SHA for your current head and save it:
sha="$(git rev-parse HEAD)"
Next, run the pull:
git pull
Finally, compare foo on your new HEAD to what you had before:
git diff --stat $sha -- foo
Note, if you have uncommitted changes, those will be included in what git diff reports, so you can always run git stash before your diff command to hide them, and then git stash pop to get them back.

How to merge a stash after removing a file locally and commiting the change?

Currently had issues with 'UserInterfaceState.xcuserstate' while working with another developer and using Git as our source control. I realized early on that .gitignore wasn't ignoring that file and I decided to remove/untrack it with 'git rm --cached'. Before executing the command, I stashed my current changes to:
Keep my current changes
Create a quick commit that reflects the removal of UserInterfaceState.xcuserstate
I created the commit the reflected the removal of 'UserInterfaceState.xcuserstate' and proceeded with 'git stash apply'. This was the error that I received:
needs merge
unable to refresh index
I'm aware that the stash still has UserInterfaceState.xcuserstate but I was under the impression that Git would merge the newly committed state of the master branch with the stash and force me to resolve the merge conflict.
How would I be able to merge the stash to regain my previous work to the newly committed master branch the no longer has nor tracks UserInterfaceState.xcuserstate?
Whenever you have a stash that won't apply, I recommend turning that stash into a branch.
To do that, first make sure you have no uncommitted work. This means you should now commit whatever you're doing, or stash it—the latter is a bit ironic and weird but works fine, as long as you remember that the stash you're converting has now been renumbered. Now that everything is "clean", pick the troublesome stash by its ID—stash#{1} or stash#{6} or whatever if necessary, or just the default if it's the current or only stash—and use that ID (or the default) as an argument to the git stash branch command:
$ git stash branch newbranch stash#{3}
You're now in a state where you can run git status, then maybe git commit, or git add and git commit, etc—your stash#{3} stash has been re-loaded into the index, as if you had run git stash apply --index, and into your work-tree as if by the same command. Once you make the commit(s), your new branch is ready to be used however you like.
(In fact, Git really did run the command: first, Git ran git checkout of the appropriate commit. Then Git ran git stash apply --index on the stash, which restored the saved index and the saved work-tree. Then, having succeeded at the applying, Git ran git stash drop on that saved stash.)
In this case, after git stash branch name stash, you'll probably want one git add and git commit, which will save all of your work including the pesky not-removed, maybe-modified-maybe-not UserInterfaceState.xcuserstate file. But now you can git checkout the branch you were working on, apply and drop the temporary stash you made if you made one, and git cherry-pick -n the commit on that new branch you made from your troublesome stash. If that commit has no changes to the pesky file, and the pesky file doesn't exist here, the cherry-pick is likely to go pretty smoothly and you'll be all set.
Discussion
This all makes a certain amount of sense once you realize that (a) a git stash object is just a clump of commits; (b) commits always record their parent commits; and (c) a branch is just a series of commits designated by a branch name identifying the last commit that is to be considered part of the branch.
If the way git stash made its little clump-of-commits were normal, you'd have:
...--o--o--*--o <-- your-branch
\
i--w <-- stash
and hence the stash would really be just like any branch, except for the fact that refs/stash does not start with refs/heads/ (compare with refs/heads/your-branch, which obviously does start with refs/heads/). Here, * is the commit that you were on when you ran git stash. You added one more commit, which is the one just to the right of the *. The i and w commits here would contain the index and work-tree states as of when you ran git stash.
The tricky part is that git stash doesn't make i and w like this at all. Instead, it makes:
...--o--o--*--o <-- your-branch
|\
i-w <-- stash
That is, the saved work-tree state appears to be a merge commit, saving the result of merging your index commit i and the commit you were on when you ran git stash (commit *). It isn't really a merge, in that it's not what you'd get by running git merge: the stash code is just abusing the merge-commit format, to make it easier for the stash code later.
Fortunately, git stash branch knows just how to de-abuse the format. It:
checks out commit *;
makes a new branch name to remember where you are now; and
applies the stash, keeping its index and work-tree components separate (just as they were when you saved the stash) in case that's what you intended.
So now you have:
o <-- your-branch
/
...--o--o--* <-- new-branch
with i in your index (ready to be committed) and w back in your work-tree (ready to be git add-ed to update your index, after which w will be in your index and ready to be committed). Commit *, of course, has the pesky file in it, so leave the pesky file where it is with no changes, add and commit everything else, and you get:
o <-- your-branch
/
...--o--o--*--W <-- new-branch
where W is a regular ordinary commit of the work-tree state you had, ready to be given to git cherry-pick (probably with -n) or used however you like.
Eventually, you can simply delete the branch new-branch (which you probably want to call by some other name) and commit W will be abandoned and will eventually disappear entirely.

git submodules and detached HEAD

So, I've been learning a bit more about git submodules, and everywhere that I read tells me that I end up with a detached HEAD after I add my submodule. This makes sense as I want my superproject to know specifically which commit should be used. However, it isn't what I am seeing in practice (on Mac OS X).
Consider the following sequence of commands that create a quick repo (called sub) with a single file in it, and then adds that as a submodule to another repo called blah.
/tmp> git version
git version 1.7.12.4 (Apple Git-37)
/tmp> git init sub
Initialized empty Git repository in /private/tmp/sub/.git/
/tmp> cd sub
/tmp/sub> touch a.txt
/tmp/sub> git add a.txt
/tmp/sub> git commit -m "add a file"
[master (root-commit) c527790] .
0 files changed
create mode 100644 a.txt
/tmp/sub> cd ..
/tmp> git init blah
Initialized empty Git repository in /private/tmp/blah/.git/
/tmp> cd blah
/tmp/blah> git submodule add /tmp/sub sub
Cloning into 'sub'...
done.
/tmp/blah> cd sub
/tmp/blah/sub> git status
# On branch master
nothing to commit (working directory clean)
Why is the submodule on the master branch? I would have expected it to say that it is not on a branch. A bit more digging suggests that git is referencing the correct commit hash, but is somehow on the master branch, instead of a detached HEAD.
/tmp/blah/sub> cd ../../sub
/tmp/sub> git reflog
97b97b3 HEAD#{0}: commit (initial): add a file
/tmp/sub> cd ../blah
/tmp/blah> git submodule status
97b97b349cfae8da490c2cad3b3f4fc3af6a53c7 sub (heads/master)
What am I missing? Thanks a lot.
Running this command:
git submodule add /tmp/sub sub
Results in a normal clone operation, so you end up on the HEAD of the master branch. However, if you examine the resulting commit, you'll see that git records the explicit commit hash in your repository.
From inside your blah repository:
$ git commit -m 'added submodule'
[master (root-commit) 13e36eb] added submodule
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 sub
$ git log --oneline
13e36eb added submodule
$ git cat-file -p 13e36eb
tree 5d205c2e2ce63d8087b3b6644e3ac183cd49c644
author Lars <lars#> 1363184265 -0400
committer Lars <lars#> 1363184265 -0400
added submodule
$ git cat-file -p 5d205c2e2ce63d8087b3b6644e3ac183cd49c644
100644 blob 30c9a7559a85f36bcedaabb8bdfaf43363966b85 .gitmodules
160000 commit 2122e5378b7940afae8e49ad9179c815c7711610 sub
That last line (160000 commit ...) shows the commit hash that git has recorded for your submodule.
If you were now to clone your repository with the submodule...
cd /tmp
git clone --recursive blah cloned-blah
You would find that sub is now not on a branch, because instead of a normal clone operation this checked out the explicit commit.

Resources