Original: "Pro Git"
Almost every version control system supports branching in some way. Using branching means that you can detach from the development main line and then continue to work without affecting the mainline. In many version control systems, this is an expensive process and often requires creating a full copy of the source code directory, which can take a long time for large projects.
Some people refer to Git's branching model as a "must-kill feature", and it is precisely because it distinguishes git from the version control system family. What's so special about Git? The Git branch is incredibly lightweight, and its new operations can be done almost instantaneously, and it's almost as fast to switch between the different branches. Unlike many other version control systems, Git encourages frequent use of branching and merging in the workflow, even if it does not matter many times within a day. Once you understand the concept of branching and use it skillfully, you will realize why Git is such a powerful and unique tool that really changes the way you develop.
What is a branch
To understand how GIT branches are implemented, we need to review how Git stores data. Maybe you remember the first chapter, Git doesn't save file differences or changes, it's just a series of file snapshots.
When committed in Git, a commit object is saved that contains a pointer to a snapshot of the staging content, including related ancillary information such as the author of the submission, and contains 0 or more pointers to the parent object of the commit: the first commit is not a direct ancestor, and the ordinary commits have an ancestor, A commit that is produced by merging two or more branches has multiple ancestors.
For the sake of intuition, we assume that there are three files in the working directory that are ready to be submitted after they are staged. The staging operation calculates checksums for each file (that is, the SHA-1 hash string mentioned in the first chapter), and then saves the current version of the file snapshot to the Git repository (git uses blob-type objects to store the snapshots) and officer and joins the staging area:
$ git add README test.rb LICENSE
$ git commit-m ' initial commit of my project '
When you use Git commit to create a new commit object, Git calculates the checksum for each subdirectory (in this case, the project root) and then saves those directories as tree objects in the Git repository. After that, Git creates a commit object that contains pointers to the tree object (the project root), in addition to the relevant commit information, so that it can reproduce the snapshot's contents in the future.
Now, there are five objects in the Git repository: Three Blob objects representing the contents of the file snapshot, a tree object that records the contents of the directory tree and the index of each file corresponding to the Blob object, and a commit object that contains the index and other commit information metadata that points to the tree object (root directory). Conceptually, the data and relationships stored by each object in the warehouse look as shown in Figure 3-1:
Figure 3-1. Data structure of a single Commit object in the warehouse
Make some changes and commit again, this time the commit object will contain a pointer to the last commit object (the parent object in the image below). After two commits, the warehouse history will look like Figure 3-2:
Figure 3-2. Link relationships between multiple commit objects
Now let's talk about branching. A branch in Git is essentially a mutable pointer to a commit object. Git uses master as the default name for the branch. After several commits, you actually have a master branch that points to the last commit object, which automatically moves forward each time it commits.
Figure 3-3. Branching is actually a history of looking back from a committed object.
So, how does Git create a new branch? The answer is simple, create a new branch pointer. For example, to create a new testing branch, you can use the git branch command:
$ git Branch Testing
This creates a new branch pointer on the current commit object (see Figure 3-4).
Figure 3-4. Multiple branches point to the history of the submitted data
So, how does Git know which branch you're currently working on? In fact, the answer is simple, it holds a special pointer called HEAD. Note that it is very different from the HEAD concept in many other version control systems you know, such as Subversion or CVS. In Git, it's a pointer to the local branch you're working on: think of the HEAD as an alias for the current branch. )。 Running the git branch command simply creates a new branch, but does not automatically switch to this branch, so in this case we still work in the master branch (see Figure 3-5).
Figure 3-5. HEAD points to the branch you are currently in
To switch to another branch, you can execute the git checkout command. We now switch to the new testing branch:
$ git checkout Testing
The HEAD then points to the testing branch (see Figure 3-6).
Figure 3-6. HEAD points to the new branch as you switch branches
What would be the benefit of such a way of implementation? Well, it's time to submit it again:
$ vim test.rb
$ git commit-a-m ' made a change '
Figure 3-7 shows the results after the submission.
Figure 3-7. HEAD moves forward along with the branch after each commit
Very interesting, now the testing branch moves forward one cell, while the master branch still points to the commit object where the original Git checkout was. Now let's go back to the master branch and see:
$ git Checkout Master
Figure 3-8 shows the results.
Figure 3-8. HEAD moves to another branch after a checkout
This order has done two things. It moves the HEAD pointer back to the master branch and replaces the files in the working directory with the snapshot content pointed to by the Master branch. In other words, the changes that start now will begin with an older version of this project. Its main function is to temporarily cancel the changes made in the testing branch so that you can develop in another direction.
We make some changes and submit again:
$ vim test.rb
$ git commit-a-m ' made other changes '
Now our project submission history has been forked (as shown in Figure 3-9) because we just created a branch, transformed it into something, and then went back to the original main branch and did some other work. These changes are isolated in separate branches: we can switch back and forth in different branches and merge them together when the time is ripe. And all this work, just need to branch and checkout these two commands can be completed.
Figure 3-9. Branching history of different flows
Because a branch in Git is actually just a file containing the checksum of the object (40 character length SHA-1 string), creating and destroying a branch becomes very inexpensive. To put it bluntly, creating a new branch is simply writing a 41-byte (plus a newline) to a file, and of course it's fast.
This is in stark contrast to most version control systems, which mostly take the form of backing up all project files to a specific directory, so depending on the number and size of the project files, the amount of time that may be spent can vary considerably, fast for a few seconds, and a few minutes slower. While the implementation of Git is not related to the complexity of the project, it can always be created and toggled within milliseconds. At the same time, because the ancestor information is recorded on each commit (that is, the parent object), in the future to merge branches, the search for the proper merging basis (i.e., the common ancestor) has naturally been placed there, so it is very easy to implement. Git encourages developers to use branches frequently, because they are guaranteed by these features.
Next, let's see why we should use branches frequently.
New and merged branches
Now let's take a look at a simple example of branching and merging, where the work is generally used: developing a website. To implement a new requirement, create a branch. Work on this branch.
Assuming you suddenly receive a phone call saying that there is a serious problem that requires urgent patching, you can do so in the following way: Return to the branch that was previously published on the production server. Establish a new branch for this emergency repair and fix the problem in it. After testing, go back to the branch where the production server is located, merge the patch branches in, and then push to the production server. Switch to the branch that previously implemented the new requirement and continue working. new and switching of branches
First, we assume that you are working happily in the project and have submitted several updates (see Figure 3-10).
Figure 3-10. A Brief history of submissions