git submodules and detached HEAD - macos

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.

Related

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.

Git diff doesn't work within submodule

I'm on Windows 10, using Git command line and have WinMerge set up as my difftool.
I have a repository with several submodules. When running "git diff" and "git difftool" within the main repo, it correctly uses WinMerge to analyze the files. When I navigate to a submodule and run "git diff" or "git difftool" it does nothing.
Here's my .gitconfig:
[diff]
tool = winmerge
[difftool "winmerge"]
cmd = winmergeu -e \"$LOCAL\" \"$REMOTE\"
This is what I get when I git diff in the parent repo:
$ git diff
diff --git a/content/themes/baseline-theme b/content/themes/baseline-theme
--- a/content/themes/baseline-theme
+++ b/content/themes/baseline-theme
## -1 +1 ##
-Subproject commit 30901f86ba56be092f1ed5c7095204abe7f64b27
+Subproject commit 30901f86ba56be092f1ed5c7095204abe7f64b27-dirty
I get no output when I run the same command in the submodule.
How can I resolve this?
You would need at least Git 2.11 for git diff --submodule=diff to work.
And 2.14 for it to work in submodules of submodules (ie recursively).
See my answer "see diff of commit on submodule".

Git: Deleted current branch and lost reflog

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.

Gitignore doesn't work properly

I have follwoing dir structure
and here is content of .gitignore
conf.php
The problem is, when I change contents conf.php and try to commit something, GIT detects conf.php as changed file again. In fact it must be ignored.
I have commited it once. I want to ignore it from now. What am I missing?
Update
I tried following:
Added conf.php to gitignore
removed it from cache git rm --cached conf.php.
But now, when I rescan project, it wants to stage conf.php as removed.
Its not what I want.
I want to keep it on remote repo and ignore future (from now) changes in local repo.
Git will not ignore tracked files
Git will not ignore changes to already-committed files.
What Git ignore is for
Git ignore, ignores noise that's in your repo, for example:
$ git init
$ mkdir tmp
$ touch tmp/file1
$ touch tmp/file2
$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# tmp/
nothing added to commit but untracked files present (use "git add" to track)
If the .gitignore file is modified to ignore the tmp directory:
$ echo "tmp" > .gitignore
$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# .gitignore
nothing added to commit but untracked files present (use "git add" to track)
the tmp dir contents are nolonger listed - but the .gitignore file IS listed (because I just created it and the file itself is not ignored).
Committing that, there are no changes listed:
$ git add .gitignore
$ git commit -v -m "addding git ignore file"
[master (root-commit) d7af571] addding git ignore file
1 file changed, 1 insertion(+)
create mode 100644 .gitignore
$ git status
# On branch master
nothing to commit (working directory clean)
Now any changes to the .gitignore file will show up as pending changes, any new files outside the tmp dir will show up as untracked files and any changes inside the tmp dir will be ignored.
Don't commit files that you don't want to track
If you've already added conf.php to your git repo, git is tracking the file. When it changes git will say that it has pending changes.
the solution to such things is to NOT commit files you don't want to track. Instead what you can do though, is to commit a default file, e.g.:
$ mv conf.php conf.php.default
$ # Edit conf.php.default to be in an appropriate state if necessary
$ git commit -v -m "moving conf.php file"
$ cp conf.php.default conf.php
Now any changes you make to conf.php - will not show up as unstaged changes.
if you want to add a empty config file to your project and then not track any additonal changes then you can do this:
edit conf.php to be in the state you want it to stay
add and commit the config.php
run the following git command:
git update-index --assume-unchanged conf.php
You will no longer see conf.php as having pending changes
To start tracking changes again, run this command:
git update-index --no-assume-unchanged conf.php
Just run git rm --cached conf.php
If you have more commited files to ignore:
git rm -r --cached .
git add .
Then commit and push your changes.

git mv and only change case of directory

While I found similar question I didn't find an answer to my problem
When I try to rename the directory from FOO to foo via git mv FOO foo I get
fatal: renaming 'FOO' failed: Invalid argument
OK. So I try git mv FOO foo2 && git mv foo2 foo
But when I try to commit via git commit . I get
# On branch master
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# foo
nothing added to commit but untracked files present (use "git add" to track)
When I add the directory via git add foo nothing changes and git commit . gives me the same message again.
What am I doing wrong? I thought I'm using a case-sensitive system (OSX) why can't I simply rename the directory?
You are in a case insensitive environment. Further, adding without the -A will not take care of the remove side of the mv as Git understands it. Warning! Ensure that no other changes or untracked files are around when you do this or they will get committed as part of this change! git stash -u first, do this and then git stash pop after. Continuing: To get around this, do the following:
mv foo foo2
git add -A
git commit -m "renaming"
mv foo2 FOO
git add -A
git commit --amend -m "renamed foo to FOO"
That's the drawn out way of changing the working directory, committing and then collapsing the 2 commits. You can just move the file in the index, but to someone that is new to git, it may not be explicit enough as to what is happening. The shorter version is
git mv foo foo2
git mv foo2 FOO
git commit -m "changed case of dir"
As suggested in one of the comments, you can also do an interactive rebase (git rebase -i HEAD~5 if the wrong case was introduced 5 commits ago) to fix the case there and not have the wrong case appear anywhere in the history at all. You have to be careful if you do this as the commit hashes from then on will be different and others will have to rebase or re-merge their work with that recent past of the branch.
This is related to correcting the name of a file: Is git not case sensitive?
You want to set the option core.ignorecase to false, which will make Git pay attention to case on file systems that don't natively support it. To enable in your repo:
$ git config core.ignorecase false
Then you can rename the file with git mv and it'll work as expected.
I was able to resolve this, using git 1.7.7 by using a temporary filename:
$ git mv improper_Case improve_case2
$ git mv improve_case2 improve_case
$ git commit -m "<your message>"
(git mv-free variant.)
I ran into this problem in Git on Mac OS X 10.9. I solved it as follows:
git rm -r --cached /path/to/directory
That stages the directory for deletion in Git but does not actually remove any physical files (--cached). This also makes the directory, now with the proper case, show up in untracked files.
So you can do this:
mv /path/to/directory /path/to/DIRECTORY
git add -A /path/to/DIRECTORY
Git will then recognize that you have renamed the files, and when you do git status you should see a number of renamed: lines. Inspect them and ensure they look correct, and if so, you can commit the changes normally.
This is a quick and bug-safe solution:
git mv -f path/to/foo/* path/to/FOO/
Warning! Always rename all files in the renamed folder (use /*).
Do not rename single files. This leads to a bug, described in this answer.
If you first want to see the outcome first, use -n:
git mv -f -n path/to/foo/* path/to/FOO/
After you've made an mv:
Commit changes
Checkout to any other revision
Checkout back.
Now Git should have renamed the folder BOTH in its internal files and in file system.
Force it with -f option:
git mv -f FOO foo
I had one related issue.
One folder named 'Pro' (created first) and another 'pro' (created by mistake). In Mac, it is the same thing, but different according to git.
$ git config core.ignorecase false
the git config rename the files to the right folder(thanks), and also created ghost files in 'pro' (No!!). I could not add ghost file changes to the track and I could not checkout other branches unless carry those those files with me, and i also could not reset it somehow.
Instead of that, i did
$ git rm -r --cached pro
$ git status // => pro files removed, new Pro files untracked
$ git add Pro
To make it extra safe, i did it in a separated fix branch, and then i merged back to main branch
For the ghost file issue created by , can any guru explain How and Why?
Thanks in advance.
This worked great for me on Windows. Used powershell with the following:
mv .\Folder-With-Wrong-Casing .\temp
git add -A
git commit -m "renamed folder with wrong casing to temp"
mv .\temp .\Folder-with-Correct-Casing
git add -A
git commit --amend -m "Renamed to proper casing"
(optional) git push
Thanks to Adam's answer above.
You're not using a case-sensitive filesystem in OS X unless you explicitly choose such. HFS+ can be case-sensitive, but the default is case-insensitive.
Here's a really simple solution around all the gitfoo on this page.
Copy the files out of your project manually.
git rm all the files.
git commit like normal.
add the files back manually.
git add all the files.
git commit like normal.
profit.
Improving Adam Dymitruk's answer (silly that SO doesn't let me comment his answer), using "git mv" will automatically stage exactly the moved files. No stashing is needed and the risky "git add -A" can be avoided:
old="abc"; new="ABC";
tmp="$old-renamed";
git mv "$old" "$tmp";
git commit -m "Renamed '$old' to '$tmp'.";
git mv "$tmp" "$new";
git commit --amend -m "Renamed '$old' to '$new'.";
Here is a simple way of doing it.
Make sure your working directory is empty.
Temporarily disable git ignore case
git config core.ignorecase false
Rename any directories (e.g. Folder => folder)
Add changes to working directory
git add --all
Stash your changes.
git stash
The original directories should be now deleted. Make a local commit.
git add --all
git commit -m "Rename directories"
Pop changes
git stash pop
Amend this to your previous commit.
git add --all
git commit --amend
You should now have a commit with directories renamed. You may now restore the original ignorecase config:
git config core.ignorecase true

Resources