- Git:Mastering Version Control
- Ferdinando Santacroce Aske Olsson Rasmus Voss Jakub Nar?bski
- 3477字
- 2021-07-08 10:46:57
Chapter 4. Git Fundamentals – Niche Concepts, Configurations, and Commands
This chapter is a collection of short but useful tricks to make our Git experience more comfortable. In the first three chapters, we learnt all the concepts we need to take the first steps into versioning systems using the Git tool; now it's time to go a little bit more in depth to discover some other powerful weapons in the Git arsenal and see how to use them (without shooting yourself in your foot, preferably).
Dissecting the Git configuration
In the first part of this chapter, we will learn how to enhance our Git configuration to better fit our needs and speed up the daily work; now it's time to become familiar with the configuration internals.
Configuration architecture
The configuration options are stored in plain text files. The git config
command is just a convenient tool to edit these files without the hassle of remembering where they are stored and opening them in a text editor.
Configuration levels
In Git we have three configuration levels which are:
- System
- User
- Repository
There are different configuration files for every different configuration level.
You can basically set every parameter at every level according to your needs. If you set the same parameters at different levels, the lowest-level parameter hides the top level parameters; so, for example, if you set user.name
at global level, it will hide the one eventually set up at system level; if you set it at repository level, it will hide the one specified at global level and the one eventually set up at system level.

System level
The system level contains system-wide configurations; if you edit the configuration at this level, every user and its repository will be affected.
This configuration is stored in the gitconfig
file usually located in:
- Windows -
C:\Program Files (x86)\Git\etc\gitconfig
- Linux -
/etc/gitconfig
- Mac OS X -
/usr/local/git/etc/gitconfig
To edit the parameters at this level, you have to use the --system
option; please note that it requires administrative privileges (for example, root permission on Linux and Mac OS X). Anyway, as a rule of thumb, the edit configuration at system level is discouraged in favor of per user configuration modification.
Global level
The global level contains user-wide configurations; if you edit the configuration at this level, every user's repository will be affected.
This configuration is stored in the .gitconfig
file usually located in:
- Windows -
C:\Users\<UserName>\.gitconfig
- Linux -
~/.gitconfig
- Mac OS X -
~/.gitconfig
To edit the parameters at this level, you have to use the --global
option.
Repository level
The repository level contains repository only configurations; if you edit the configuration at this level, only the repository in use will be affected.
This configuration is stored in the config
file located in the .git
repository subfolder:
- Windows -
C:\<MyRepoFolder>\.git\config
- Linux -
~/<MyRepoFolder>/.git/config
- Mac OS X -
~/<MyRepoFolder>/.git/config
To edit parameters at this level, you can use the --local
option or simply avoid using any option as this is the default one.
Listing configurations
To get a list of all configurations currently in use, you can run the git config --list
option; if you are inside a repository, it will show all the configurations from repository to system level. To filter the list, append --system
, --global
, or --local
options to obtain only the desired level configurations, as shown in the following screenshot:

Editing configuration files manually
Even if it is generally discouraged, you can modify Git configurations by directly editing the files. Git configuration files are quite easy to understand, so when you look on the Internet for a particular configuration you want to set, it is not unusual to find just the right corresponding text lines; the only little foresight to maintain in those cases is that you always need to back up files before editing them. In the next paragraphs, we will try to make some changes in this manner.
Setting up other environment configurations
Using Git can be a painful experience if you are not able to place it conveniently inside your work environment. Let's start to shape some rough edges using a bunch of custom configurations.
Basic configurations
In the previous chapters, we saw that we can change a Git variable value using the git config
command with the <variable.name> <value>
syntax. In this paragraph, we will make use of the config
command to vary some Git behaviors.
Typos autocorrection
So, let's try to fix an annoying question about the typing command named typos. I often find myself re-typing the same command two or more times; Git can help us with embedded autocorrection, but we first have to enable it. To enable it, you have to modify the help.autocorrection
parameter, defining how many tenths of a second Git will wait before running the assumed command; so by giving a help.autocorrect 10
command, Git will wait for a second, as shown in the following screenshot:

To abort the autocorrection, simply type Ctrl + C.
Now that you know about configuration files, you can note that the parameters we set by the command line are in this form: section.parameter_name
. You can see the sections' names within []
if you look in the configuration file; for example, you can find them in C:\Users\<UserName>\.gitconfig
, as shown in the following screenshot:

Push default
We already talked about the git push
command and its default behavior. To avoid such annoying issues, it is a good practice to set a more convenient default behavior for this command.
There are two ways we can do this. The first one is to set Git to ask to us the name of the branch we want to push every time, so a simple git push
will have no effects. To obtain this, set push.default
to nothing
, as shown in the following screenshot:

As you can see, now Git pretends that you specify the target branch at every push.
This may be too restrictive, but at least you can avoid common mistakes like pushing some personal local branches to the remote, generating confusion in the team.
Another way to save yourself from this kind of mistake is to set the push.default
parameter to simple
, allowing Git to push only when there is a remote branch with the same name as that of the local one, as shown in the following screenshot:

This action will push the local tracked branch to the remote.
Defining the default editor
Some people really don't like vim
, even only for writing commit messages; if you are one of those people, there is good news for you in that you can change it instead by setting the core.default
config parameter:
$ git config --global core.editor notepad
Obviously you can set all text editors on the market. If you are a Windows user, remember that the full path of the editor has to be in the PATH
environment variable; basically, if you can run your preferred editor typing its executable name in a DOS shell, you can use it even in a Bash shell with Git.
Other configurations
You can browse a wide list of other configuration variables at http://git-scm.com/docs/git-config.
Git aliases
In will suggest only a few more to help you make things easier.
Shortcuts to common commands
One thing you can find useful is to shorten common commands like git checkout
and so on; therefore, these are some useful aliases:
$ git config --global alias.co checkout $ git config --global alias.br branch $ git config --global alias.ci commit $ git config --global alias.st status
Another common practice is to shorten a command by adding one or more options that you use all the time; for example set a git cm <commit message>
command shortcut to alias git commit –m <commit message>
:
$ git config --global alias.cm "commit -m"

Creating commands
Another common way to customize the Git experience is to create commands you think should exist, as we did in Chapter 2, Git Fundamentals – Working Locally with the git tree
command.
git unstage
The classic example is the git unstage
alias:
$ git config --global alias.unstage 'reset HEAD --'
With this alias, you can remove a file from the index in a more meaningful way as compared to the equivalent git reset HEAD <file>
syntax:
$ git unstage myfile.txt $ git reset HEAD myfile.txt
git undo
Do you want a fast way to revert the last ongoing commit? Create a git undo
alias:
$ git config --global alias.undo 'reset --soft HEAD~1'
You will be tempted to use the --hard
option instead of the --soft
option, but simply don't do it as it's generally a bad idea to make it too easy to destroy information, and sooner or later, you will regret for deleting something important.
git last
A git last
alias is useful to read about your last commit, which is shown here:
$ git config --global alias.last 'log -1 HEAD'

git difflast
With git difflast
alias, you can indeed see a difference from your last commit, as shown here:
$ git config --global alias.difflast 'diff --cached HEAD^'

Advanced aliases with external commands
If you want the alias to run external shell commands instead of a Git subcommand, you have to prefix the alias with a !
:
$ git config --global alias.echo !echo
Suppose you are annoyed by the canonical git add <file>
plus git commit <file>
sequence of commands, and you want to do it in a single shot; here you can call the git
command twice in sequence creating this alias:
$ git config --global alias.cm '!git add -A && git commit -m'
With this alias you commit all the files, adding them before, if necessary.
Have you noted that I set again the cm
alias? If you set an already configured alias, the previous alias will be overwritten.
There are also aliases that define and use complex functions or scripts, but I'll leave it to the curiosity of the reader to explore these aliases. If you are looking for inspiration, please take a look at mine at https://github.com/jesuswasrasta/GitEnvironment.
Removing an alias
Removing an alias is quite easy; you have to use the --unset
option, specifying the alias to remove. For example, if you want to remove the cm
alias, you have to run:
$ git config --global --unset alias.cm
Note that you have to specify the configuration level with the appropriate option; in this case, we are removing the alias from the user (--global
) level.
Aliasing the git command itself
I already said I'm a bad typewriter; if you are too, you can alias the git command itself (using the default alias
command in Bash):
$ alias gti='git'
In this manner, you will save some other keyboard strokes. Note that this is not a Git alias but a Bash shell alias.
Git references
We said that a Git repository can be imagined as an acyclic graph, where every node, the commit, has a parent and a unique SHA-1 identifier. But during the previous chapters, we even used some references such as the HEAD
, branches, tags, and so on.
Git manages these references as files in the .git/refs
repository folder:

If you open one of those files, you will find it inside the SHA-1 of the commit they are tied to. As you can see, there are subfolders for tags and branches (called heads
).
Symbolic references
The HEAD
file instead is located in the .git
folder, as shown in the following screenshot:

HEAD
is a symbolic reference; symbolic references are references that point to other references, using the ref: <reference>
syntax. In this case, the HEAD
is currently pointing to the master
branch; if you check out another branch, you will see the file's content change, as shown in the following screenshot:

Ancestry references
In Git you often need to reference the past (for example, the last commit); for this scope, we can use two different special characters which are the tilde ~
and the caret ^
.
The first parent
Suppose you want to completely delete the last x4y5z6 commit:

A way to do this is to move the HEAD
pointer to the a1b2c3 commit, using the --hard
option:
$ git reset --hard a1b2c3
Another way to do this is to move the pointer back to the parent commit. To define the parent, you have to specify a starting point reference, which can be the HEAD
, a specific commit, a tag, or a branch and then one of two special characters: the tilde ~
, for the first parent and the caret ^
for the second one (remember that commits can have two parents when they represent a merge result).
Let's get under the lens of the tilde ~
. With the <ref>~<number>
notation, we can specify how many steps backward we are going to take; going back to the example, an equivalent of the previous command is this:
$ git reset --hard HEAD~1
The HEAD~1
notation tells Git to point to the first parent commit of the actual commit (the HEAD
, indeed). Note that HEAD~1
and HEAD~
are equivalent.
You can also go backward by more than one step, simply incrementing the number; a HEAD~3
reference will point to the third ancestor of the HEAD
:

The second parent
With the ^
caret character, instead we reference the second parent of a commit, but only starting from the number 2; the ref^1
notation references the first parent, as does the ref~1
notation whereas ref^
and ref~1
are equivalent. Also note that ref^1
and ref^
are equivalent.
The ^
and ~
operators can be combined; here's a diagram showing how to reference various commits using HEAD
as the starting point:

World-wide techniques
In this section, you will raise your skills by learning some techniques that will come in handy in different situations.
Changing the last commit message
This trick is for people who don't double-check what they're writing. If you pressed the Enter key too early, there's a way to modify the last commit message, using the git commit
command with the --amend
option:
$ git commit --amend -m "New commit message"
Please note that with the --amend
option, you are actually redoing the commit, which will have a new hash; if you already pushed the previous commit, changing the last commit is not recommended; rather, it is deplorable and you will get in trouble.
Tracing changes in a file
Working on source code in a team, it is not uncommon to need to look at the last modifications made to a particular file to better understand how it evolved over time. To achieve this result, we can use the git blame <filename>
command.
Let's try this inside the Spoon-Knife
repository to see the changes made to the README.md
file during that time:

As you can see in the preceding screenshot, the result reports all the affected lines of the README.md
file; for every line you can see the commit hash, the author, the date, and the row number of the text file lines.
Suppose now you found that the modification you are looking for is the one made in the d0dd1f61 commit; to see what happened there, type the git show d0dd1f61
command:

The git show
command is a multipurpose command, it can show you one or more objects; in this case we have used it to show the modification made in a particular commit using the git show <commit-hash>
format.
The git blame
and git show
commands have quite a long list of options; the purpose of this paragraph is only to point the reader to the way changes should be traced on a file; you can inspect other possibilities using the ever useful git <command> --help
command.
The last tip I want to suggest is to use the Git GUI:

With the help of GUI, things are much more easy to understand.
Cherry picking
The cherry picking activity consists of choosing existing commits from somewhere else and applying them here. I will make use of an example to better explain how you can benefit from this technique.
Suppose you and your colleague Mark are working on two different public branches of the same repository; Mark found and fixed an annoying bug in the feat1
branch that affects even your feat2
branch. You need that fix, but you can't (or don't want to) merge his branch, so how can you benefit from his fix?

It's easy; get the commit that fixes that bug and apply it to your current branch, using the git cherry-pick
command:
$ git checkout feat2 $ git cherry-pick a1b2c3
That's all! Now the commit a1b2c3 performed by Mark in the feat1
branch has been applied to the feat2
branch and committed as a new x4y5z6 commit, as shown in the following screenshot:

The git cherry-pick
command behaves just like the git merge
command. If Git can't apply the changes (for example, you get merge conflicts), it leaves you to resolve the conflicts manually and make the commit yourself.
We can even pick commit sets if we want to by using the <starting-commit>..<ending-commit>
syntax:
$ git cherry-pick feat1~2..feat1~0
With this syntax, you are basically picking the last two commits from the feat1
branch.
Tricks
In this section, I would suggest just a bunch of tips and tricks that I found useful in the past.
Bare repositories
Bare repositories are repositories that do not contain working copy files but contain only the .git
folder. A bare repository is essentially for sharing; if you use Git in a centralized way, pushing and pulling to a common remote (a local server, a GitHub repository, or so on), you will agree that the remote has no interest in checking out files you work on; the scope of that remote is only to be a central point of contact for the team, so having working copy files in it is a waste of space, and no one will edit them directly on the remote.
If you want to set up a bare repository, you have to use only the --bare
option:
$ git init --bare NewRepository.git
As you may have noticed, I called it NewRepository.git
, using a .git
extension; this is not mandatory but is a common way to identify bare repositories. If you pay attention, you will note that even in GitHub every repository ends with the .git
extension.
Converting a regular repository to a bare one
It can happen that you start working on a project in a local repository and then you feel the need to move it to a centralized server to make it available to other people or locations.
You can easily convert a regular repository to a bare one using the git clone
command with the same --bare
option:
$ git clone --bare my_project my_project.git
In this manner, you have a 1:1 copy of your repository, but in a bare version, ready to be shared.
Backup repositories
If you need a backup, there are two commands you can use, one of which is for archiving only files and the other is for backing up the entire bundle including the versioning information.
Archiving the repository
To archive the repository without including the versioning information, you can use the git archive
command; there are many output formats of which ZIP is the classic one:
$ git archive master --format=zip --output=../repbck.zip
Please note that using this command is not the same as backing up folders in the filesystem; as you noticed, the git archive
command can produce archives in a smarter way, including only files in a branch or even in a single commit. By doing this, you are archiving only the last commit, as shown in the following code:
$ git archive HEAD --format=zip --output=../headbck.zip
Archiving files in this way can be useful if you have to share your code with people that don't have Git installed.
Bundling the repository
Another interesting command is the git bundle
command. With git bundle
you can export a snapshot from your repository, and you can then restore it.
Suppose you want to clone your repository on another computer, and the network is down or absent; with this command, you create a repo.bundle
file of the master
branch:
$ git bundle create ../repo.bundle master
With these other commands, we can then restore the bundle in the other computer using the git clone
command, as shown here:
$ cd /OtherComputer/Folder $ git clone repo.bundle repo -b master
Summary
In this chapter, you enhanced your knowledge about Git and its wide set of commands. You finally understood how configuration levels work and how to set your preferences using Git, by adding useful command aliases to the shell. Then we looked at how Git deals with references, providing a way to refer to a previous commit using its degree of relationship.
Furthermore, you added some key techniques to your skill set, as it is important to learn something you will use as soon as you start to use Git extensively. You also learned some simple tricks to help you use Git more efficiently.
- GAE編程指南
- Beginning Java Data Structures and Algorithms
- 算法零基礎一本通(Python版)
- PHP 7底層設計與源碼實現
- Java加密與解密的藝術(第2版)
- 信息技術應用基礎
- Mastering Apache Maven 3
- Node.js Design Patterns
- Unity 2D Game Development Cookbook
- 蘋果的產品設計之道:創建優秀產品、服務和用戶體驗的七個原則
- Android應用開發深入學習實錄
- 一步一步學Spring Boot:微服務項目實戰(第2版)
- 微信公眾平臺開發最佳實踐
- Node.js Web Development
- 生成藝術:Processing視覺創意入門