官术网_书友最值得收藏!

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.

主站蜘蛛池模板: 和政县| 华蓥市| 聂拉木县| 库车县| 龙胜| 龙游县| 宣恩县| 泾阳县| 平遥县| 登封市| 定安县| 临沧市| 武宁县| 邵东县| 崇州市| 米易县| 玛多县| 曲松县| 从江县| 绥宁县| 汪清县| 肃宁县| 岚皋县| 汉寿县| 铁力市| 遵义县| 德兴市| 抚顺县| 文安县| 宁阳县| 汤原县| 南通市| 阆中市| 遂平县| 汉沽区| 上高县| 张家港市| 镇远县| 明溪县| 临邑县| 游戏|