GIT - HowTo's

How to list files modified by a commit ?

git diff-tree --no-commit-id --name-only -r commitId

How to view git log of a renamed file ?

Situation :

git log myFile returns no error, but I can remember this file was initially named my file. I have renamed + committed it, but now, how can I view its history when it was still named my file ?

Details :

Solution :

git log -S my file

will show the history of my file starting at the rename commit, back to the beginning of times.

How to push only some local commits ?

Situation :

My status : git status
On branch master
Your branch is ahead of 'origin/master' by 5 commits.
  (use "git push" to publish your local commits)
nothing to commit, working directory clean
My log : git log --oneline
2261c3a Revert blah blah blah ... This reverts commit 190898e77999bf4041fdc6d57ec18618743302e8
a102d0a FIX (ticket-104, part 2/2)		this commit
2fec5db FIX (ticket-104, part 1/2)		and this one too
ee00f4b Revert blah blah blah ... This reverts commit 7e16fa7173f10a662eecedbe166f42ffabacb41a
190898e ADD 'server_clone' for tests
7f2c929 FIX blah blah blah ...
8007cc6 CHANGE blah blah blah ...
3d835df FIX blah blah blah ...
5b047a8 FIX blah blah blah ...
082f84c CHANGE details
I'd like to push only the 2 marked commits to my origin server.

A wise (best practice !) solution would have been to create a dedicated branch to work on "ticket-104", but if I knew then, I wouldn't be on the verge of learning some git push hack .

Details :

Take me to the solution !

Detailed solution (source) :

  1. Let's setup a test environment :
    before=$PWD; tmpDir=$(mktemp -d --tmpdir=/run/shm playingWithGit.XXXXXXXX); repoName='myRepo'; repoDir="$tmpDir/$repoName.git"; workDir="$tmpDir/workDir"; mkdir -p "$repoDir" "$workDir"; cd "$repoDir"; git init --bare; cd "$workDir"; git clone "file://$repoDir/"; cd "$workDir/$repoName"; for i in {1..5}; do codeFile="file$i"; echo "this is some code : $i" > "$codeFile"; git add "$codeFile"; git commit -m "This is commit #$i ($codeFile)"; done; git log --oneline
    Initialized empty Git repository in /dev/shm/playingWithGit.0teQbNFk/myRepo.git/
    Cloning into 'myRepo'...
    warning: You appear to have cloned an empty repository.
    [master (root-commit) 0e0fe97] This is commit #1 (file1)
     1 file changed, 1 insertion(+)
     create mode 100644 file1
    [master 8488863] This is commit #2 (file2)
     1 file changed, 1 insertion(+)
     create mode 100644 file2
    [master af6011d] This is commit #3 (file3)
     1 file changed, 1 insertion(+)
     create mode 100644 file3
    [master 1503eac] This is commit #4 (file4)
     1 file changed, 1 insertion(+)
     create mode 100644 file4
    [master 828be7d] This is commit #5 (file5)
     1 file changed, 1 insertion(+)
     create mode 100644 file5
    828be7d (HEAD -> master) This is commit #5 (file5)
    1503eac This is commit #4 (file4)
    af6011d This is commit #3 (file3)
    8488863 This is commit #2 (file2)
    0e0fe97 This is commit #1 (file1)
  2. Let's push :
    git push origin master
  3. Now we have a local + a remote repository with some commits. Both are up-to-date :
    git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
  4. Now let's add some more commits :
    for i in {6..8}; do codeFile="file$i"; echo "this is some code : $i" > "$codeFile"; git add "$codeFile"; git commit -m "This is commit #$i ($codeFile)"; done; git log --oneline
    [master 725708b] This is commit #6 (file6)
     1 file changed, 1 insertion(+)
     create mode 100644 file6
    [master 0021c6d] This is commit #7 (file7)
     1 file changed, 1 insertion(+)
     create mode 100644 file7
    [master 58ce856] This is commit #8 (file8)
     1 file changed, 1 insertion(+)
     create mode 100644 file8
    58ce856 (HEAD -> master) This is commit #8 (file8)
    0021c6d This is commit #7 (file7)
    725708b This is commit #6 (file6)
    828be7d (origin/master) This is commit #5 (file5)
    1503eac This is commit #4 (file4)
    af6011d This is commit #3 (file3)
    8488863 This is commit #2 (file2)
    0e0fe97 This is commit #1 (file1)
  5. Now I'll try to push, providing the ID of "commit #7" :
    git push origin 0021c6d:master && git log --oneline
    58ce856 (HEAD -> master) This is commit #8 (file8)
    0021c6d (origin/master) This is commit #7 (file7)
    725708b This is commit #6 (file6)
    828be7d This is commit #5 (file5)
    1503eac This is commit #4 (file4)
    af6011d This is commit #3 (file3)
    8488863 This is commit #2 (file2)
    0e0fe97 This is commit #1 (file1)

    I've actually pushed the commits This is commit #6 (file6) and This is commit #7 (file7)

    git push remoteName commitId:remoteBranchName
    actually pushes the full history from the beginning of times until commitId included.

  6. Let's check this with more commits to push :
    for i in {9..13}; do codeFile="file$i"; echo "this is some code : $i" > "$codeFile"; git add "$codeFile"; git commit -m "This is commit #$i ($codeFile)"; done; git log --oneline
    07b6bb8 (HEAD -> master) This is commit #13 (file13)
    65100fc This is commit #12 (file12)
    48e1df2 This is commit #11 (file11)
    a51d31f This is commit #10 (file10)
    4abcf38 This is commit #9 (file9)
    58ce856 This is commit #8 (file8)
    0021c6d (origin/master) This is commit #7 (file7)
    725708b This is commit #6 (file6)
    828be7d This is commit #5 (file5)
    1503eac This is commit #4 (file4)
    af6011d This is commit #3 (file3)
    8488863 This is commit #2 (file2)
    0e0fe97 This is commit #1 (file1)
  7. Now we'll push commits 11 and 9, in this order (!!!). To do so, we'll have to stack them on top of the latest commit known by "origin" : #7
    git squash 7
    • In the command above, 7 is the number of commits we'll considering while squashing. This appears to be a 7 again, like the "head" of the remote branch, but this is purely coincidental.
    • The display below is the re-ordered list of commits during git rebase. It's in the opposite order of git log.
    pick 0021c6d This is commit #7 (file7)
    pick 48e1df2 This is commit #11 (file11)
    pick 4abcf38 This is commit #9 (file9)
    pick 58ce856 This is commit #8 (file8)
    pick a51d31f This is commit #10 (file10)
    pick 65100fc This is commit #12 (file12)
    pick 07b6bb8 This is commit #13 (file13)
  8. Checking the re-ordering :
    git log --oneline -8
    02c9072 (HEAD -> master) This is commit #13 (file13)
    8082015 This is commit #12 (file12)
    cf3cda0 This is commit #10 (file10)
    e794a70 This is commit #8 (file8)
    926ad50 This is commit #9 (file9)
    117bb7f This is commit #11 (file11)
    0021c6d (origin/master) This is commit #7 (file7)
    725708b This is commit #6 (file6)
  9. And also :
    git status
    On branch master
    Your branch is ahead of 'origin/master' by 6 commits.
      (use "git push" to publish your local commits)
  10. Now, let's push the 2 commits sitting on top of "origin/master", by giving the commit ID of commit #9 :
    git push origin 926ad50:master
    Counting objects: 6, done.
    Delta compression using up to 2 threads.
    Compressing objects: 100% (4/4), done.
    Writing objects: 100% (6/6), 579 bytes | 579.00 KiB/s, done.
    Total 6 (delta 2), reused 0 (delta 0)
    To file:///run/shm/playingWithGit.0teQbNFk/myRepo.git/
       0021c6d..926ad50  926ad50 -> master
  11. Checking locally :
    git status
    On branch master
    Your branch is ahead of 'origin/master' by 4 commits.
      (use "git push" to publish your local commits)
    And :
    git log --oneline
    02c9072 (HEAD -> master) This is commit #13 (file13)
    8082015 This is commit #12 (file12)
    cf3cda0 This is commit #10 (file10)
    e794a70 This is commit #8 (file8)
    926ad50 (origin/master) This is commit #9 (file9)
    117bb7f This is commit #11 (file11)
    0021c6d This is commit #7 (file7)
    725708b This is commit #6 (file6)
    828be7d This is commit #5 (file5)
    1503eac This is commit #4 (file4)
    af6011d This is commit #3 (file3)
    8488863 This is commit #2 (file2)
    0e0fe97 This is commit #1 (file1)
  12. Checking on the "origin" side :
    cd "$repoDir"; git log --oneline; cd "$workDir/$repoName"
    926ad50 (HEAD -> master) This is commit #9 (file9)
    117bb7f This is commit #11 (file11)
    0021c6d This is commit #7 (file7)
    725708b This is commit #6 (file6)
    828be7d This is commit #5 (file5)
    1503eac This is commit #4 (file4)
    af6011d This is commit #3 (file3)
    8488863 This is commit #2 (file2)
    0e0fe97 This is commit #1 (file1)
  13. Done playing, let's clean up :
    cd "$before"; [ -d "$tmpDir" ] && rm -rf "$tmpDir"

Solution :

  1. Find the remote (origin) HEAD. Several methods to do so :
    • git status may display things like : Your branch is ahead of 'origin/master' by n commits.
    • on the remote side : git log --oneline will display this (by default on recent Git versions)
  2. Re-order commits so that the commits to be pushed are sitting right on top of the latest commit known to the remote (i.e. "remote HEAD")
  3. Push the n local commits by giving the commit ID of the last commit (in current history order) to be pushed (source) :
    git push remoteName commitId:remoteBranchName
    Which gives :
    git push origin 720aebc:master

How to discard local uncommitted changes ?

If you need to unstage changes first :
git reset
Undo all local uncommitted changes :
Undo local uncommitted changes for a specific file :
git checkout someFile

Why doesn't git status show whether I'm up-to-date with my remote counterpart ?

Situation :

git status
On branch master
nothing to commit (use -u to show untracked files)
I expected something like :
On branch master
Your branch is ahead of 'origin/master' by 2 commits.

Details :

Try these :
  1. git branch -vv
    * master 6f3d70e (a very interesting commit message)
    should be :
    * master 35ca599 [origin/master] (another highly interesting commit message)
  2. git rev-parse --symbolic-full-name "@{u}"
    fatal: No upstream configured for branch 'master'
    should be :
    refs/remotes/origin/master

This is because the master local branch has no upstream origin/master remote branch to be compared to.

The presence or absence of an upstream setting mainly affects whether git status can tell you if you are ahead or behind, and whether git merge and git rebase can do their job with no additional parameters. So it's basically just a convenience setting.

Solution :

  1. Define an upstream for the current branch :
    git branch --set-upstream-to origin/master
    Branch master set up to track remote branch master from origin.
  2. Retry the commands above :
    1. git branch -vv
      * master 6f3d70e [origin/master: ahead 7] (a very interesting commit message)
    2. git rev-parse --symbolic-full-name "@{u}"
      refs/remotes/origin/master
  3. And :
    git status
    On branch master
    Your branch is ahead of 'origin/master' by 7 commits.
    	...
  4. Should you need to unset the upstream :
    git branch --unset-upstream

How may I push to a non-bare repository ?

This is dirty1000 and is actually not recommended.

Before things are configured : can not push to non-bare repo

git push origin master
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 697 bytes | 0 bytes/s, done.
Total 6 (delta 4), reused 0 (delta 0)
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository				the error
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error:
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into			the solution
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error:
remote: error: To squelch this message and still keep the default behaviour, set				a workaround
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To gitServer:/path/to/repository
 ! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to '@gitServer:/path/to/repository'

Solution

Get the initial configuration value
The error message says that, on the gitServer side, we just have to change the value of the receive.denyCurrentBranch directive to ignore or warn. Alright but -just in case- what's the original value of this Git configuration directive ?
git config --get receive.denyCurrentBranch
(nothing but UNIX FAILURE exit code)
This directive is currently unset.
Change the configuration value (source) :
  1. git config receive.denyCurrentBranch ignore
  2. check :
    git config --get receive.denyCurrentBranch
    ignore

With Git 2.3.0+, it is possible to safely push to a non-bare repository with :

git config receive.denyCurrentBranch updateInstead

Let's try to push again :
git push origin master
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 697 bytes | 0 bytes/s, done.
Total 6 (delta 4), reused 0 (delta 0)
To gitServer:/path/to/repository
   f5c8ce7..629a87e  master -> master
Hack you have to do after each push to a non-bare repository :
On the gitServer side :
git reset --hard
  • As for why you have to do so, refer to the large Git error message above.
  • On gitServer side, since the index and the working tree are out-of-sync, "addition changes" made by a git push will be reported as "remove changes" by git status (hence the need of the git reset).
  • If there are some uncommitted changes on gitServer, there is a risk of data loss.

How to get a Git configuration value ?

How to checkout / clone an existing project ?

checkout is the SVN command to get a local copy of a project from its main repository (when joining a team, for instance).
Git also has a checkout command, but it has a different meaning.
The Git equivalent of SVN checkout is git clone.

clone from a remote host over SSH (I can log in there with ssh @server) :
git clone @server:/path/to/repository /local/path
  • on server, there is /path/to/repository/.git/
  • locally, I'll get /local/path/.git/
clone over SSH and "git checkout" the myDevelopmentBranch branch :
git clone @host:/path/to/myGreatProject.git -b myDevelopmentBranch /path/to/local/directory
If /path/to/local/directory is not specified, the files will be extracted as ./myGreatProject/*

How to save a WIP ?

You may have to save a WIP (i.e. changes made after the previous commit are not ready for a new commit yet) before changing branch. To do so :

  1. git stash
  2. git status should return no modified file
  3. Create a new branch "newBranch", work hard, commit. Then go back to the original branch : git checkout previousBranch
  4. restore WIP in previousBranch : git stash apply
  5. OR drop "stashed" content : git stash drop

How to untrack a file ... ?

... without deleting it from the local filesystem :
git rm --cached file
... and delete it from the local filesystem :
git rm file, then commit file
To untrack an entire directory and delete it :
  1. git rm -r path/to/directory/*
  2. then make a simple commit : git commit -m 'cleaning'

How to unstage a file ?

To "unstage" a file means "remove it from the index / staging area". To do so :

git reset HEAD path/to/file

How to cancel a commit ?

Cancel the latest commit :

Available methods :
git revert commitId
create a new commit that "unchanges" what was changed by commit commitId (example). This can be used on any commit, not only the latest one.
git reset HEAD^
remove a commit from history and leave changes made by this commit as unstaged changes (example)
Examples :
git revert commitId :

tempDir=$(mktemp -d --tmpdir XXXXXXXX); commitMessage='This is commit '; cd "$tempDir"; git init; echo 'Hello World' > myFile; git add myFile; git co -m 'Initial version'; for i in {1..2}; do echo "This line will be committed by commit #$i" >> myFile; git add myFile; git co -m "$commitMessage#$i"; done; git log --oneline; idOfLatestCommit=$(git log --oneline | awk "/$commitMessage#$i/ {print \$1}"); echo -e "\nID of latest commit : '$idOfLatestCommit'\nREVERTING...\n"; git revert --no-edit "$idOfLatestCommit"; git log --oneline; echo; git s; echo; cat myFile; cd -; [ -d "$tempDir" ] && rm -rf "$tempDir"

Outputs :
...
aa94778 This is commit #2
5915e56 This is commit #1
9baee48 Initial version
ID of latest commit : 'aa94778'
REVERTING...
[master 5ba510a] Revert "This is commit #2"
 1 file changed, 1 deletion(-)
5ba510a Revert "This is commit #2"
aa94778 This is commit #2
5915e56 This is commit #1
9baee48 Initial version
On branch master
nothing to commit (use -u to show untracked files)	nothing unstaged
Hello World
This line will be committed by commit #1		changes are gone : working copy altered
git reset HEAD^ :

tempDir=$(mktemp -d --tmpdir XXXXXXXX); cd "$tempDir"; git init; echo 'Hello World' > myFile; git add myFile; git co -m 'Initial version'; for i in {1..2}; do echo "This line will be committed by commit #$i" >> myFile; git add myFile; git co -m "This is commit #$i"; done; git log --oneline; echo -e '\nRESETTING...\n'; git reset HEAD^; git log --oneline; git s; cat myFile; cd -; [ -d "$tempDir" ] && rm -rf "$tempDir"

Outputs :
...
4caee4f This is commit #2
b78d9b4 This is commit #1
e4dd720 Initial version
RESETTING...
Unstaged changes after reset:
M       myFile
b78d9b4 This is commit #1
e4dd720 Initial version
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
 modified:   myFile
no changes added to commit (use "git add" and/or "git commit -a")
Hello World
This line will be committed by commit #1
This line will be committed by commit #2	changes are still there : working copy unaltered
  • The commit This is commit #2 no longer exists
  • myFile is back to its state right before doing the This is commit #2 commit, i.e. with some unstaged changes. This is confirmed by git s as well as by the final cat.

Jump several commits back (source) :

  • HEAD^ : 1 commit back
  • HEAD^^ or HEAD~2 : 2 commits back
  • d6d98923868578a7f38dea79833b56d0326fcba1 : full commit ID
  • d6d9892 : short commit ID. Works as long as this doesn't collide with another commit ID

How to correct mistakes in old commits ?

If you want to fix an error in the latest commit, git commit --amend should do the trick. Read more.
Otherwise, keep reading here

Only rewrite that part of history which you alone possess (source) : don’t amend any commit you have already pushed (source) !

Situation :

First things first, here's my history : git log
commit 7ef72d73aa0ee4c67007e66a3351ed1ee2df6998
Author: Thomas ANDERSON <thomas.anderson@metacortex.com>
Date:   Fri Apr 24 15:31:11 2015 +0200
    Cosmetics
commit 2b9bbbf3f7463e0c0526a69edec10a708dacada3
Author: root <root@localhost>				OOOPS !
Date:   Fri Apr 24 15:21:21 2015 +0200
    BUGFIX: make sure configuration files use the Unix line ending format, not DOS/Windows
commit f93811c3dcaabed86df54916ea84fbbfcf521435
Author: Thomas ANDERSON <thomas.anderson@metacortex.com>
Date:   Fri Apr 24 11:44:25 2015 +0200
    ...
==> I should not have committed as .

Solution :

  1. Put away pending changes (if any) : git stash
  2. Get the current branch name : git branch
    * master
  3. Checkout the bad commit : git checkout 2b9bbbf3f7463e0c0526a69edec10a708dacada3, which outputs :
    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 performing another checkout.
    ...
    HEAD is now at 2b9bbbf... BUGFIX: make sure configuration files use the Unix line ending format, not DOS/Windows
  4. Make the necessary changes :
    • In this example, since I just committed with the wrong user, there's nothing to do, the fix will be made at the next step.
    • If the change I'd like to commit (1 single change in 1 single file) is currently stashed, I have to extract it first (source) : git checkout stash@{0} -- path/to/file
      path/to/file is what is reported by git s.
  5. Commit changes :
    • In the specific case of this example (changing the author of a commit) : git commit --amend --reset-author
    • In the general case : git commit --amend
  6. Now let's rebase with : git rebase --onto HEAD badCommitId branchName
    git rebase --onto HEAD 2b9bbbf3f7463e0c0526a69edec10a708dacada3 master
    First, rewinding head to replay your work on top of it...
    Applying: Cosmetics
  7. Restore pending changes (if any) : git stash apply

How to view details of past commits ?

General rule

the diff only
git diff olderCommit..newerCommit
the diff + commit ID + author + commit message

To highlight the changes made between commits, use --word-diff (works with diff / show / log) :

git diff olderCommit..newerCommit --word-diff

Changes made to myFile in the latest n commits

git show HEAD~n..HEAD myFile
Refering to parents and ancestors
HEAD
a reference to the latest commit of the current branch
commitId~n
a reference to the nth ancestor of commitId (source):
  • commitId~1 : father of commitId
  • commitId~2 : grandfather of commitId
  • ...
commitId^n
a reference to the nth parent of commitId. Parents are ancestors of the same level, since they are from the same "generation". (source):
  • commitId^1 : father of commitId
  • commitId^2 : mother of commitId
  • ...
commitId_oldest..commitId_newest
range of commits between commitId_oldest and commitId_newest (source)

How to remove sensitive data (such as passwords, private keys, ...) from the Git history ?

Keep in mind that any private information that has been committed to a public repository MUST BE CONSIDERED AS COMPROMISED. So, clean up your mess, but forget about your passwords, keys, ... and generate new ones !!!

  1. Navigate to the repository's working directory :
    cd home/of/my/git/repository
  2. Run git filter-branch, forcing (--force) Git to process -but not check out (--index-filter)- the entire history of every branch and tag (--tag-name-filter cat -- --all), removing the specified file ('git rm --cached --ignore-unmatch path/to/fileContainingSensitiveData) and any empty commits generated as a result (--prune-empty) :
    git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch path/to/fileContainingSensitiveData' --prune-empty --tag-name-filter cat -- --all
    Specify path/to/fileContainingSensitiveData, not just the file name.
    Example:
    git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch ./test' --prune-empty --tag-name-filter cat -- --all
    Rewrite de1291f7f10da919f611358539e3d2e0acfd632f (1/9)rm 'test'
    Rewrite 023a969f60328055b6cab2e1a82e307760b7a308 (2/9)rm 'test'
    Rewrite aaffedfe16c297d02c2fee5eb72b740c91cae942 (3/9)rm 'test'
    Rewrite 8a766a9226e360ba0f147ae9c890cec200f958d7 (4/9)rm 'test'
    Rewrite 1f9bd643aa3495f46ca9470c021a56f8a0765c62 (5/9)rm 'test'
    Rewrite 2e22a397f2283f398038d9a44f451914c0a58f68 (6/9)rm 'test'
    Rewrite 15f247b46d558b057ccead7f7980c7f24b58879c (7/9)rm 'test'
    Rewrite e54ff82df52d8e7d02749b17e4fac4aa9386122e (8/9)rm 'test'
    Rewrite 5bbcaf42adac870f13295b2bbe2673807bf1e059 (9/9)rm 'test'
    Ref 'refs/heads/master' was rewritten
    Ref 'refs/remotes/origin/master' was rewritten
  3. If fileContainingSensitiveData used to exist with a different path or name (git mv ...), repeat the previous step for each previous path or name.
  4. Add fileContainingSensitiveData to .gitignore to prevent committing again by mistake :
    echo fileContainingSensitiveData >> .gitignore; git add .gitignore; git commit -m "I won't commit fileContainingSensitiveData again."
  5. Make sure your history is clean :
    • git log --name-status
    http://stackoverflow.com/questions/1338728/delete-commits-from-a-branch-in-git
    git reset --hard commitId	WARNING, this deletes all commits from the current point back to commitId. Use with care !!!
  6. Force-push your local changes to overwrite your public repository, as well as all the branches you've pushed up :
    git push origin --force --all
  7. In order to remove the sensitive file from your tagged releases, you'll also need to force-push against your Git tags :
    git push origin --force --tags
  8. Ask others to rebase instead of merging to avoid sending back the compromised data
  9. After a while, once sure we've not broken anything (and with Git ≥ 1.8.5) :
    git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
    git reflog expire --expire=now --all
    git gc --prune=now
==========================================8&=========================================================
IN CASE OF SENSITIVE DATA PUSHED TO GITHUB :
SOLUTION 1 :
	untrack sensitive file from local git (see above)
	delete sensitive file from local git history
		http://help.github.com/remove-sensitive-data/
		git filter-branch --index-filter 'git rm --cached --ignore-unmatch hosts.csv' --prune-empty -- --all
		==> Doesn't seem to work on directory containing uncommited modifications.
		If error message :
			Cannot create a new backup. A previous backup already exists in refs/original/
			==> git filter-branch -f --index-filter 'git rm --cached --ignore-unmatch output/hosts.cfg' --prune-empty -- --all
	commit to local repo
	commit to github
	<to be checked>
	==> sensitive file still visible in history 
	[Grok mode] delete github repo + recreate it (within web UI) + RE-SETUP above
	then push local repo to github :
		git push -u origin master --force
	[Normal mode]
	force push local repo to github :
		git push -u origin master --force
	</to be checked>

How to stage / unstage a patch ?

Solution 1 : interactive staging (source) :

  1. Enter Git interactive staging : git add -i (or git add --interactive)
  2. Type p for patch
  3. Type the number of the file you'd like to take a patch from, then (twice : ???)
  4. This will display the hunks of the current file and offer keys :
    • y (yes) : stage this hunk
    • n (no) : do not stage this hunk
    • a (all) : stage this and all the remaining hunks in the file
    • d (don't) : do not stage this hunk nor any of the remaining hunks in the file
    • ? (help)
  5. When all the desired changes are staged, leave with q
  6. If something went wrong while staging changes, you can unstage some changes
  7. Then commit the staged changes : git commit -m "What a beautiful commit"

Solution 2 : git add in patch mode : -p / --patch (source 1, 2) :

git add --patch myFile, then for each hunk :

  • y (yes) : stage this hunk
  • n (no) : do not stage this hunk
  • a (all) : stage this and all the remaining hunks in the file
  • d (don't) : do not stage this hunk nor any of the remaining hunks in the file
  • s (split) : split the current hunk into smaller hunks
  • / : search for a hunk matching the given regex
  • ? (help)
  • (there are more
This -p option is a shortcut to the -i above + patch (source).

It is possible to do the opposite; i.e. interactively select hunks to unstage with git reset -p.

BONUS : How to stage a patch while having contiguous changed lines (source) ?

Let's start by simulating the situation :
  1. Create the Git repo, add some code and commit : myDir=~/testDir; myFile='testFile'; mkdir "$myDir"; cd "$myDir"; git init; echo 'line 1' >> "$myFile"; git add "$myFile"; git commit -m 'Initial version'
  2. Improve the code : echo -e "line 2\nline 3" >> "$myFile"
  3. Our program now looks like : cat "$myFile"
    line 1
    line 2
    line 3
  4. And Git sees some difference : git diff
    diff --git a/testFile b/testFile
    index 89b24ec..a92d664 100644
    --- a/testFile
    +++ b/testFile
    @@ -1 +1,3 @@
     line 1
    +line 2
    +line 3

And the question is : how may I stage line 2 split from line 3 so that I can make 2 separate commits ?

  • With git add --patch, the split command is useless since the changed lines are contiguous
  • Still with git add --patch, the regex search function doesn't work so far (returns all lines, whatever pattern I enter)
Now the solution :
  1. Prepare to stage changes in patch mode : git add --patch
    diff --git a/testFile b/testFile
    index 89b24ec..a92d664 100644
    --- a/testFile
    +++ b/testFile
    @@ -1 +1,3 @@
     line 1
    +line 2
    +line 3
    Stage this hunk [y,n,q,a,d,/,e,?]?
  2. Then type e to edit the hunk. This will open your default text editor (Vim ?)
  3. Once in the editor, follow the instructions to specify which lines to stage or not :
    # Manual hunk edit mode -- see bottom for a quick guide
    @@ -1 +1,3 @@
     line 1
    +line 2
    #+line 3
    # ---
    # To remove '-' lines, make them ' ' lines (context).
    # To remove '+' lines, delete them.
    # Lines starting with # will be removed.
    #
    # If the patch applies cleanly, the edited hunk will immediately be
    # marked for staging. If it does not apply cleanly, you will be given
    # an opportunity to edit again. If all lines of the hunk are removed,
    # then the edit is aborted and the hunk is left unchanged.
    To not stage a ... line Do :
    +
    • add a leading #
    • OR delete this line
    - change the leading - into a [SPACE]
    Save and exit.
  4. View staged changes : git diff --staged
    diff --git a/testFile b/testFile
    index 89b24ec..7bba8c8 100644
    --- a/testFile
    +++ b/testFile
    @@ -1 +1,2 @@
     line 1
    +line 2
  5. Commit. Enjoy.

How to setup a Git distributed workflow ?

  1. As :
    1. create a user belonging to the git group.
    2. create accounts for the Git users.
      Make these users belong to the primary group git : adduser -g git ... other options ... to create accounts, or usermod -g git bob afterwards.
  2. As :
    1. Create a directory for a new repository : mkdir /home/git/myRepo.git
      this will be a bare repository, so don't forget the trailing .git in the name of the directory.
    2. Create a new bare repository there :
      cd /home/git/myRepo.git; git init --bare
      which will output :
      Initialized empty Git repository in /home/git/myRepo.git/
  3. As :
    1. Make your 'development' directory tree (if any), and cd there
    2. git clone file:///home/git/myRepo.git/
      Which will output :
      Initialized empty Git repository in /home/bob/development/myRepo/.git/
      warning: You appear to have cloned an empty repository.
      • the warning is normal since it's a new repo
      • this creates the directory /home/bob/development/myRepo/ This is your working copy : it has a .git/
      • this also creates the remotes automatically
    3. Code then push your work to the central repository : git push origin master -u To let this work, you may have to chmod -R g=rwx /home/git/myRepo.git
    4. Get updates from the central repository : git pull origin master -f

How to shrink the .git directory ?

git gc --aggressive --prune

Resulted in a ~10% volume decrease.

How to view staged changes ?

How to create and apply a patch ?

To proceed, you must have a few commits and a distant copy/clone/remote of your repository. To make things even easier, it's a good practice to create a dedicated branch for a fix / new feature.

  1. Create a patch containing changes between the current status and the branch master : git format-patch master --stdout > theNameOfMy.patch
  2. You can inspect the patch with your favorite text editor. Send it to whoever needs it. The following steps take place on the "patch receiver" side
  3. List changes provided by the patch : git apply --stat theNameOfMy.patch
  4. Check/dry-run the patch : git apply --check theNameOfMy.patch
  5. If the previous command output no error, you can now apply the patch : git am --signoff < theNameOfMy.patch
  6. Check the logs : git log will output
    commit 7d7a786c1353b5433b7e39a6b50c7ac8d9b5159f
    Author: Thomas ANDERSON <thomas.anderson@metacortex.com>	who made the commit
    Date:	Fri Jul 18 09:35:36 2014 +0200
    	Fixed a glitch in the Matrix
    	Signed-off-by: Smith <smith@thematrix> <== who applied the patch

How to squash several commits into a single one ?

Usage :

NEVER EVER rebase commits which you have already pushed to a remote repository !

Example :

Preliminary :

testFile='./myFile'; testDir='./myDir'
mkdir "$testDir"; cd "$testDir"
git init; touch "$testFile"; git add "$testFile"; git co "$testFile" -m "Initial version (empty)"; git log
for i in {1..4}; do echo "This is edit number $i" >> "$testFile"; git co "$testFile" -m "Commit #$i"; done; git log

Merge 4th commit (latest) into 3rd :

git rebase -i HEAD~2 outputs :
pick 897931d Commit #3
pick 25d3585 Commit #4
Edit to squash the 4th commit into the 3rd (forget about commit messages for the moment) :
pick 897931d Commit #3
s 25d3585 Commit #4
Save and exit. Then you'll be prompted for the final commit message :
# This is a combination of 2 commits.
# The first commit's message is:
Commit #3
# This is the 2nd commit message:
Commit #4
Enter your own commit message :
Squashed #4 into #3
Save and exit. Check the result with git log :
Squashed #4 into #3
Commit #2
Commit #1
Initial version (empty)

Squash commits 2, 3 and 4 into a single one :

Make sure you've run the cleaning then preliminary steps before going further.

git rebase -i HEAD~3 outputs :
pick 04f56e2 Commit #2
pick 0887de3 Commit #3
pick 5410e2c Commit #4
Edit to fixup the commits 2, 3 and 4 and use the message of the 2nd commit (this method won't prompt for a commit message):
pick 04f56e2 Commit #2
f 0887de3 Commit #3
f 5410e2c Commit #4
Save and exit. Check with git log :
Commit #2
Commit #1
Initial version (empty)
See changes made with this "new commit number 2" (that is also the latest so far) : git show HEAD~1..HEAD $testFile :
	Commit #2
diff --git a/myFile b/myFile
index 32671c8..4e96082 100644
--- a/myFile
+++ b/myFile
@@ -1 +1,4 @@
 This is edit number 1
+This is edit number 2
+This is edit number 3
+This is edit number 4

Merge commits 2 and 3 into a single one :

Make sure you've run the cleaning then preliminary steps before going further.

git log shows :
commit 402957e89a359ce2bd4c9c24cd658dbe25a4f155
	Commit #4
commit 331174ab513dda7861100997ae6e4dbf66d18eae
	Commit #3
commit de0db9b72fad2e52b23cbfdd51f8714561d9e0e6
	Commit #2
commit 8c93f6861a17388d691cfb5a985bbb9161c4a8f1
	Commit #1
commit 8ee9ad23f95c146d48c7c7a7f58d03a3140e5455
	Initial version (empty)
We're at Commit #4 (HEAD) and we want to make changes right after Commit #1, no matter where these changes stop. In other words, we want to start working from Commit #1 (excluded), which can be refered to as HEAD~3 (details)
git rebase -i HEAD~3 outputs :
pick de0db9b Commit #2
pick 331174a Commit #3
pick 402957e Commit #4
# These lines can be re-ordered; they are executed from top to bottom.
Edit (this is where the magic takes place Forget about commit message for the moment :
pick de0db9b Commit #2
s 331174a Commit #3
pick 402957e Commit #4
Save and exit. You'll be prompted for a new commit message :
# This is a combination of 2 commits.
# The first commit's message is:
Commit #2
# This is the 2nd commit message:
Commit #3
Change this into :
Squashed #3 into #2
And check the result : git log :
commit ff477d71a329265bf2033d0d0ccd5a1c7e60c37c
	Commit #4
commit 394bc1c28075e6457a795bd69d58deda20f00a20
	Squashed #3 into #2
commit 8c93f6861a17388d691cfb5a985bbb9161c4a8f1
	Commit #1
commit 8ee9ad23f95c146d48c7c7a7f58d03a3140e5455
	Initial version (empty)
See changes made with this "new commit" : git show HEAD~2..HEAD~1 $testFile :
commit 394bc1c28075e6457a795bd69d58deda20f00a20
	Squashed #3 into #2
diff --git a/myFile b/myFile
index 32671c8..987d059 100644
--- a/myFile
+++ b/myFile
@@ -1 +1,3 @@
 This is edit number 1
+This is edit number 2
+This is edit number 3

Cleaning :

rm -rf .git; cd ..; [ -d "$testDir" ] && rm -r "$testDir"; unset testFile testDir

How to merge father/son into father (both having distincts repositories) ?

cd father
mv son{,_remote}			the directory son will be re-created later, so let's avoid collisions
git remote add -f son ./son_remote/	add and fetch remote
git merge -s ours --no-commit son/master
git read-tree --prefix=son/ -u son/master
git commit -m "Subtree merged father/son into father"
			
Some manually cleaning is needed :
  • Only the tracked files of son_remote have been duplicated into the new son directory.
  • You may now rename directories : git mv son_remote son

How to contribute / perform a pull request ?

  1. Fork source project from GitHub UI
  2. Get the URL of your forked version of the project : https://github.com/myGitHubLogin/projectName.git
  3. Get a local copy of your forked repository : git clone https://github.com/myGitHubLogin/projectName.git
  4. Enter your local work directory : cd projectName
  5. Register the remote repo (named upstream by GitHub) : git remote add upstream https://github.com/projectOwner/projectName.git
  6. git fetch upstream
  7. Code, and commit to the local repository. Then push to your GitHub repository : git push origin master
  8. On GitHub UI, open your fork's webpage, then Pull-request
  9. Add title and details to the pull request, then submit.
  10. Done !
Stuff below could be obsolete / wrong
git clone https://github.com/sni/Thruk.git
git remote add --track master upstream git://github.com/Httqm/Thruk.git
git branch newFeature
git checkout newFeature
[do some code modifications, commit]
git push upstream newFeature (?)

How to specify the private key file to use while doing Git over SSH ?

Add in ~/.ssh/config :

Host hostAlias
	Hostname hostName
	User userName
	IdentityFile ~/.ssh/myPrivateKeyFile

How to merge a branch on the master ?

  1. switch to the destination branch : git checkout master
  2. merge the branch into the current one : git merge branchName
  3. delete merged branch : git branch -d branchName

How to hide untracked files from git status ?

Once this is defined as an alias in ~/.gitconfig, you may list untracked files with : git status --untracked-files

How to create a new, empty, Git repository and store it on GitHub ?

Starting from GitHub :

  1. Create a new repository within GitHub web UI
  2. On the workstation, enter the parent directory of the project : cd /path/to/devDirectory/allMyProjects
  3. git clone https://github.com/myGitLogin/projectName
  4. Work hard, add / modify files, then commit
  5. Then push to GitHub : git push -u origin master
  6. If this prompts for login/pass, edit /path/to/devDirectory/allMyProjects/projectName/.git/config :
    change : url = https://github.com/myGitLogin/projectName
    into : url = git@github.com:myGitLogin/projectName
    And it should work (source : 1, 2)

Starting from the workstation :

  1. mkdir projectName;cd projectName
  2. git init
  3. Declare remote (GitHub) repository (unless already created in the GitHub Web UI) :
    git remote add origin https://github.com/login/projectName