Git

Count commits

Source

Count your commits, maybe to determine when it's time for a new release:

git rev-list --count [branch]

Extract subdirectory into separate repository

Source

This might require the installation of a package git-filter-repo.rpm or similar.

cp -r repo_original repo_copy
cd repo_copy
git filter-repo --subdirectory-filter path/to/dir --force

git pull

Source When the question comes to rebase or not rebase I tend to rebase. This sets the rebase as default for all branches in all repositories.

# Force all new branches to automatically use rebase
git config branch.autosetuprebase always

# Force existing branches to use rebase.
git config branch.*branch-name*.rebase true

This results in this line either in ~/.gitconfig or in .git/config.

cat ~/.gitconfig
[branch]
  autosetuprebase = always

git-up

Source

The blokker with git pull --rebase... is that it only updates and rebases the currently checkout branch. The ruby-gem git-up addreses this issue by introducing a new command: git up.

This will pull and rebase all available branches (or all fetched branches) in the current repository.

Tracking files

For removing tracked files from the repository, you can use

git update-index --assume-unchanged [path]

To track the files again:

git update-index --no-assume-unchanged [path]

This will allow to add files to the repository, but ignore any changes to cthem.

In general use the .gitignore file to keep files out of the repository.


Tracking branches

Adding tracking to an existing branch

git branch -u upstream/branch

is the same as

git branch --set-upstream-to=upstream/branch

git commit

Breakup changes into different commits

Source

If you have done several changes at once, but you just want to commit a part of them and keep some of them out of the repository for now, then you can break up the changes into several commits as well. Maybe it is just to keep the commit history clear, maybe you just like it that way better.

Commit your changes with:

git add -p

Then git will open an interactive session and go step by step through all the changes you have done to the file and ask you if you want to commit them or not. All changes you have committed once will - of course - not be asked for again later.


SSH and Github

Source

You might want to use different SSH keys for accessing different repositories on github. What you need to know is that git relies on the local ssh configuration to know which ssh key to use. By default it always uses the id_rsa key. To make it seperate between ssh keys, you need to modify you ssh configuration file in ~/.ssh/config.

Host me.github.com
    HostName github.com
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/me_rsa

Host work.github.com
    HostName github.com
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/work_rsa

But that's not enough. You also have to tell SSH to use the new key you've created:

ssh-add ~/.ssh/work.rsa

Make also sure that GIT actually uses the hostnames you have specified in your ssh configuration:

git remote -v | grep origin
origin  git@me.github.com:me/repository.github.com (fetch)
origin  git@me.github.com:me/repository.github.com (push)

Cleanup

  • git clean -n -d <path>: Remove untracked files from the repository (dry-run)
  • git clean -n -d -x <path>: Remove untracked files and files from .gitignore from the repository (dry-run).

Undo stuff

Source General guideline how to undo actions.

git revert

  • Scenario: Changes have been sent to e.g. Github via git push, now there is a problem with the last commit you want to undo.
  • Command: git revert <commit>
  • Explanation: git revert will create a new commit on top of the last commit that is th opposite of the given SHA. If the old commit is adding something, the new commit will remove it and if it is removing something, it will be added again.

This does not alter the history and just adds a new commit to it to undo the change.

git commit --amend

  • Scenario: A typo in the last commit message before the changes have been pushed anywhere.
  • Command: git commit --amend or git commit --amend -m "Correct description"
  • Explanation: git commit --amend will update and replace the last commit with a new commit text, including all previous commited changes of course. If nothing is currently stages, it will simply update the commit message.

git checkout

git checkout -- <filename>

  • Scenario: Some wrong changes were saved by accident and the editor crashed. The changes haven't been committed yet and the goal is to reset the file to the way it was in the last commit.
  • Command: git checkout -- <filename>
  • Explanation: git checkout resets the file to the status in the last commit. If you want a state that lies further back or is in a different branch, you would provide a branchname or a differen SHA. OTherwise the default for Checkout is HEAD. Those changes you revert from are seriously gone and cannot be restored.

git checkout :/"Some commit comment"

  • Scenario: You need to reset the repository to a certain commit, but you don't like to use commit hashes.
  • Command: `git checkout :/"$commit_message"
  • Explanation: Instead of using a commit hash (or the first couple of hash identifiers) to address the commit, a commit message can be search for. This will reset the repository to the first commit that matches that message.

git checkout "@{10 minutes ago}"

  • Scenario: You do not want to deal with hashes, but just want to reset the repository to anything that was just ten minutes ago.
  • Command: git checkout "@{10 minutes ago}"
  • Explanation: As stated in git help rev-parse this will reset the repository to the specified state at the given point in time. Alternatives are {yesterday}, {1 month 2 weeks 3 days 1 hour 1 second ago}.

git checkout "@{-5}"

  • Scenario: You do not want to write hashes, but want to set the repository back into a state where it was before the last five commits.
  • Command: `git checkout "@{-5}"
  • Explanation: As stated in git help rev-parse and git checkout, this syntax will reset the repository/branch to the <n>th commit before the current one.

git checkout-index

  • Scenario: You want to write the current state of the repository into the filesystem, without GIT related files like the .git directory.
  • Command: git checkout-index -f -a --prefix=/path/to/folder/
  • Explanation: All files in the index will be written to the filesystem. The directory .git is not part of the index. the file .gitignore on the other hand is. Exisiting files at the destination which are not in den index will not be touched.

git reset

git reset [--hard] <last good SHA>

  • Scenario: Changes have been commmited locally, but not yet pushed. Several commits need to be undone.
  • Command: git reset <last good SHA> or git reset --hard <last good SHA>
  • Explanation: git reset will leave the repository in the state of the specified SHA and rewind the history as well. The undone commit will never have existed. This will reset the commits, but the content of those changes will be still present in the directory tree. If you want to undo the changes in the files as well in one go, use the --hard option.

git reset ORIG_HEAD

  • Scenario: You have set back the repository/branch to some point in the past and forgotten about the commit hash where the HEAD originally has been.
  • Command: git reset ORIG_HEAD
  • Explanation: After a hard reset to any point before the latest in the commit history, GIT stores the original head position in ORIG_HEAD. This allows it to go back forward into the future of the commit history.

git reflog, git reset, git checkout

  • Scenario: Commit have been done, the resetted with git reset --hard and suddenly those commit were not as bad as thought and need to be restored.
  • Command: git reflog, git reset or git checkout
  • Explanation: git reflog accesses a history of changes of HEAD pointing to all sorts of locations. This log of changes of HEAD is not infinitely long and will get cleaned by GIT automatically. Also changes on other systems will not be available. Depening on your goal, you have to use reflog differently.
  • Restoring the history as well as it was, then use git reset --hard <SHA> instead.
  • If you want to recreate one or more files in the current working directory as they were in a different commit, use git checkout <SHA> -- <filename>.
  • If you want to replly exactly one commit into the current repository, use git cherry-pick <SHA>.
  • You can configure a longer grace period for doomed commits with git config gc.pruneexpire "30 days".
  • Automatic invocation of git gc (and then deleting doomed commits) can be disabled with git config gc.auto 0
git reflog
a861fdb HEAD@{0}: merge new feature: Fast forward
e919ec6 HEAD@{1}: checkout: moving from_new feature to master
a861fdb HEAD@{2}: commit: creating awesome feature
e919ec6 HEAD@{3}: checkout: moving from master to new_feature
e919ec6 HEAD@{4}: reset: moving to HEAD^^
7f3d17f HEAD@{5}: commit: Fixed that bug
c1918f2 HEAD@{6}: commit: Whoops, there's a bug
e919ec6 HEAD@{7}: commit: Just about finished
f72be69 HEAD@{8}: commit: More work in progress
4a7f9a9 HEAD@{9}: commit commit (initial): Initial commit

git branch feature, git reset --hard origin/master, git checkout feature

  • Scenario: Commits were added to the wrong branch (e.g. master) and those commits should have been added to a different branch (e.g. feature) instead.
  • Command: git checkout -b <branch>, git reset --hard, git checkout <branch>
  • Explanation:

  • git checkout -b <branch> creates a new branch from the current pointing to the last commit (all changes need to be commited), but leaves you checked in in your current branch (e.g. master).

  • git reset --hard <SHA> will revert the last commits and set the current branch into the state of SHA. All commits are now only available in the new branch (e.g. feature) and lost in the current branch.
  • git checkout <branch> will checkout the new branch and the reverted commits from the previous branch (master) are available here.

git checkout feature, git rebase master

  • Scenario: A new feature has emerged from the branch master, but master was behind origin/master at that time. After syncing master with origin/master, the branch feature should also build on the latest changes from master.
  • Command: git checkout feature, git rebase master
  • Explanation

  • git checkout feature changes into the feature branch after updating the master branch from origin/master.

  • git rebase master will locate the last common ancestor between master and feature and roll the currently checked out branch to that commit, without dropping later commits. Then it advances the current branch to the end of the branch master and will apply all outstanding commits from the current branch feature on top of them.

git rebase -i <earlier SHA>

  • Scenario: Half way through the developement of a new feature another solution appeared to be better. So far many commits have been made, but only some of them are useful now. Others need to be undone.
  • Command: git rebase -i <earlier SHA>
  • Explanation: git rebase -i puts rebase in interactive mode and lets you modify each commit before it applies it. The default text editor will open with the list of commits to apply. There are different ways of doing this:
pick 84c4823 Early work on feature (we know this one was wrong)
pick 0835fe2 More work (this one is OK)
pick 1e6e80f Still more work (also wrong)
pick 31dba49 Yet more work (yet also wrong)
pick 6943e85 Getting there now (starting a better path)
pick 38f5e4e Even better (finally working out well)
pick af67f82 Oops, this belongs with 'Even better'
  • The first two columns are key: the first is the selected command for the commit identified by the SHA in the second column. By default, rebase -i assumes each commit is being applied via the pick command. To drop a commit, just delete that line in the editor. If you want to preserver the content of a comit, but edit the commit message, you use the command reword. Replace the word pick in the first column with the word reword (or just r). Changing the commit message right now in the editor will not work since rebase ignores everything after the second column. After finishing the rebase you will be prompted for the new commit message. In order to combine two commits together use the squash and fixup commands like this:
pick 31dba49 Yet more work (yet also wrong)
squash 6943e85 Getting there now (starting a better path)
pick 38f5e4e Even better (finally working out well)
fixup af67f82 Oops, this belongs with 'Even better'
  • squash and fixup combine up, the marked commit will be merged with the commit immediately before (above) it. In the example above the commits 31dba49 and 6943e85 will be merged, as well as the commits 38f5e4e and af67f82. The difference between squash and fixup is the handling of the commit messages. squash will merge and prompt for a new commit message, fixup will take the commit message from the first commit in the list instead and not prompt. In the example above, we will not be prompted for the merge of af67f82.
  • Saving the editor and closing it and GIT will start to apply the commits from top to bottom. The order in which the commits are applied can be altered by changing the order of the commits before saving it. The commits af67f82 and 31dba49 could have been merged like this:
pick 31dba49 Yet more work (yet also wrong)
fixup af67f82 Oops, this belongs with 'Even better'
pick 6943e85 Getting there now (starting a better path)
pick 38f5e4e Even better (finally working out well)

git commit --allow-empty

  • Scenario: You want to add an empty commit without actually changing anything.
  • Command: git commit --allow-empty -m 'An Empty commit'
  • Explanation: This will add a new commit to the history without actually having changed anything. This can be used to trigger e.g. some process in hooks.

git commit --squash

  • Scenario: A file is missing in an earlier commit and other commits have been made afterwards. --amend is therefore not an option. Changes have not yet been pushed.
  • Command: git commit --squash <SHA> and git rebase --autosquash -i <even earlier SHA>
  • Explanation:

  • git commit --squash will create a new commit and prompt for a new commit message on the earlier commit.

    When using --fixup instead, the old commit message will be re-used. In this example this would make more sense since we only add a file to the commit. * git rebase --autosquash -i will launch and interactive rebase editor, but the entries are adjusted and all squash! and fixup! commits will be already paired to the commit target in the list, like this:

pick 0835ff32 Earlier commit (missing something)
fixup af67f82 fixup! Earlier commit (missing something)
pick 6943e85 Later commit (this is fine)
  • When using --squash and --fixup you might not remember the SHA you want to fix. The GIT operators ^ and ~ are useful here: HEAD^ is one commit in the past, HEAD~4 is four commits before HEAD, meaning five commits back.

git reset ...

  • Scenario: You want to reduce all commits in the current branch into one single commit, keep all the changes, but have none but one commit left.
  • Command: git reset $(git commit-tree HEAD^{tree}) -m "Initial commit.")
  • Explanation: When creating templates in GitHub or GitLab of a project, changes might occur. The history of the project template will be included in the newly created repository, something not necessarily required or desired. This command will reduce all commits that might have been added recently into a single one. Followed by a git push --force to overwrite a template will then update the project template, but add no further commits.

git rm --cached file

  • Scenario: A file has been added by mistake (like a logfile). So now running the application GIT reports unstaged changes in that file. Adding the file to .gitignore does not seem to have any effect.
  • Command: git rm --cached <file>
  • Explanation: A file being tracked by GIT will always overrule the .gitignore configuration file. Removing the file from tracking can be done by git rm --cached <file>. This leaves the file untouched on the disk, but clears the tracking of GIT on it.

git bisect

Finding malicious commits and the commit that introduced a bug.

Some subcommands are important:

  • git bisect start
  • git bisect good
  • git bisect bad
  • git bisect reset

The idea is the following:

  1. You checkout and mark the commit that doesn't not contain the bug.
  2. You checkout and mark the commit that does contain the bug.
  3. git bisect leads you through multiple commits between these commits where you have to test if the bug you're looking for is still present.

Checkout git bisect help for a more details explanation.


Move folders between repositories

Source

When you want to extract a folder and its history from a git repository and put it into a different branch, it is not as simple as it might look. Here is how (using local repositories in the example) you could do it:

  1. Create a copy of the source repository.
  2. Now remove all unrelated stuff in the temporary repository, except the folder you want to keep.
  3. The folder is now in the root directory of the branch, the history intact and anything else removed. If you want to put this folder somewhere else or rename it, this is the time.
  4. Commit your changes without pushing it back to the source-repository.
  5. Branch off the current branch inkluding the history.
  6. Push this branch back to the source-repository.
  7. Switch back to the source-repository, checkout the new branch.
  8. Delete the temp-repository.
git clone source-repository temp-repository
cd temp-repository
git filter-branch --subdirectory-filter path/to/folder -- -- all
git mv folder new/location
git commit -a -m "Extracting data from repository"
git checkout -b new-branch-name
git push origin new-branch-name
cd ../source-repository
git checkout new-branch-name

rm -rf ../temp-repository

git diff

word difference

Running git diff based on word differences:

git diff --word-diff

Time based

git diff "@{yesterday}"
git whatchanged --since="2 weeks ago"

Snippets


Search in previous commits

Source

Search for when a term existed or was introduced:

git log _S $term --oneline

Remove branches

Source

All remote branches except master

git branch -r | grep 'origin' | grep -v 'master$' | grep -v HEAD | cut -d/ -f2- | while read line; do git push origin :heads/$line; done;

All remote branches except master and the current one

git branch | grep -v "master\|$(git branch --show-current)" | xargs git branch -D

Statistics

# Ranking by number of commits
git shortlog -sne --no-merges

# git-stats
# https://gitstats.sourceforge.net/examples/git/
npm install -g git-stats
cd $repo
git-stats -g