Chapter 12
Tips and Tricks
We did not want to overload the introductory chapters and limited ourselves to the essential concepts and typical applications. In this chapter, however, you will find a collection of tips and tricks, some of which can be very helpful in certain situations but you or others might never need. Probably it is sufficient if you skim this chapter briefly so you know what is here. You can look up the details if you need them.
Don’t Panic, There Is A Reflog!
Git is like a dog. It can smell your fear.
I smiled when I saw this saying in my twitter timeline. Git is actually somewhat intimidating to beginners. But if Git is really a dog, then it is probably a herding dog trying to protect its (developer) herd.
You should know that Git does not immediately delete objects in the repository. Whenever you change something, Git creates new objects in the repository; the old ones are not deleted. Even garbage collection, for example by using the gc command, only deletes objects with a certain minimum age. The default setting for this is two weeks (configuration option: gc.pruneexpire).
In addition, Git keeps track of all changes in a branch. This so-called reflog resides in the .git/logs directory. With the --walk-reflogs option, the log command can display the local history of a branch.
> git log --walk-reflogs mybranch
If you can find the commit that contains the “lost” changes, then you can bring back the changes again, for example by using the cherry-pick command, the rebase command or with a simple merge.
Attention! For local clones the reflog is normally active. Bare repositories that you normally puts on servers, however, do not have the reflog by default. You can turn it on using this command:
> git config core.logAllRefUpdates true
It may be useful to make it a system-wide default setting:
> git config --system core.logAllRefUpdates true
Ignoring Local Changes Temporarily
Sometimes you change files that are managed by Git and that you do not want to include in versioning. An example: When writing this book, we often commented out chapters so that we can complete the document faster. Naturally, we did not want these changes to be versioned. Another example: To find an error, you generate extra debugging information, which will no longer be needed later.
Entries in .gitignore do not help here, because they only work on files that are not yet managed by Git. You can work around the problem by using selective commits. However, this is a bit tedious because then you have to choose again for each subsequent commit, what changes you do and do not want to accept.
Step by Step
Ignoring versioned files
Git-managed files are temporarily ignored, so that changes to these files will not be accepted.
1. Ignore versioned files
The --assume-unchanged option of the update-index command creates a mark in the staging area to ensure that Git will no longer checks whether the specified file has changed, and simply pretends that the file is unchanged.
> git update-index --assume-unchanged foo.txt
2. Resume work
Now you can resume work. The status and add commands will not show changes to the foo.txt file.
3. Stop ignoring
You can use --no-assume-unchanged to cancel the effect of --assume-unchanged for individual files. Using --really-refresh you can reset the status for all files.
> git update-index --really-refresh
Examining Changes to Text Files
The normal Git diff algorithm compares two files line by line. In your source code, you often change single lines without touching the neighboring lines, which means differences can be easily noticed with diff. In text files, however, it is a different story. Changes are often wrapped, i.e. words move from one line to another. From the output of diff, it is hard to see what exactly has changed in a text file.
> git diff
... -Walter goes every -day to schiil. +Walter goes to +school. ...
For continuous text, the --word-diff option can help as it can show changes word by word.
> git diff --word-diff
... Walter goes [-every -] [-day] to [-schiil.-]{+school.+} ...
With --word-diff=color you can have the differences highlighted in a different color.
alias - Shortcuts for Git Commands
If you use Git from the command line often, it may be useful to define shortcuts for frequently used commands.
> git config --global alias.ci commit
> git config --global alias.st status
Here we set up aliases ci and st for commit and status, respectively. They can be used immediately. For example:
> git st
Aliases are also considered in Git tab completion (if installed). Therefore, it may also be useful to set up aliases for rarely used commands. For example:
> git config --global alias.ignore-temporarily 'update-index --assume-unchanged'
Branches as Temporary Pointers to Commits
If you are looking for errors or trying to fix tricky merge conflicts, you might often remember relevant commits, for example a point where everything was still good, or a merge base. You may start by listing commit hashes on a piece of paper, but you should not do this! You can easily create a branch, once you discover a commit of interest.
> git branch tmp/a-silly-error 8b167
From now on you can refer to the commit by name. The tab completion now “knows” that name. You can, for instance, inquire which tags contain the commit in question.
> git tag --contains tmp/a-silly-error
1.0.2 1.0.3
The following command creates a branch named tmp/merge-base as a pointer to the merge base of the master and feature branches.
> git branch tmp/merge-base 'git merge-base master feature'
The tmp/ prefix is a naming convention: This is a namespace for temporary branches, to make it easier to distinguish them from normal branches. It is not a technical requirement. You can call the temporary branches whatever you want.
Later you can clear the branches using the following command.
> git branch -D 'git branch --no-color --list tmp/* | grep -v '* ' | xargs'
Moving Commits to Another Branch
When you are making changes, it is of course best to do it on the right branch. But sometimes you make a mistake and do it on the wrong branch. In such a case, you will have to move some commits to another branch.
Figure 12.1: Moving commits from branch A to B
Step by Step
Moving commits to another branch
Some commits from branch A must be moved to branch B.
1. Reorder the commits and mark the dividing point with tmp/SPLIT
In order to be moved, commits must be at the end of a branch.
> git rebase --interactive
In the editor you sort the rows so that on top are rows that will remain in A, and at the bottom are rows that are to be moved to B. In between, you create a temporary branch tmp/SPLIT using the exec command. This temporary branch marks the point of separation.
pick 6a2f459 should stay in A 1 pick 05c2935 should stay in A 2 exec git branch -f tmp/SPLIT pick af22ed6 should go to B 1 pick 4f30adf should go to B 2
The tmp/SPLIT branch now points to the last commit that should remain in A.
2. Do the transfer
First, create a temporary branch named tmp/MOVE and move it. Next, transfer tmp/MOVE to B using the merge command. Finally, the remaining commits are truncated to A.
> git checkout -B tmp/MOVE A > git rebase tmp/SPLIT --onto B > git checkout B > git merge --ff tmp/MOVE > git branch --force A tmp/SPLIT
Attention! Moving commits can confuse other developers. You should either move only commits that you have not shared with other developers, or you have to inform the other developers about it and ask them in turn to move commits that were developed on top of the moved commits.
18.117.74.231