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

Chapter 2. Git Fundamentals – Working Locally

In this chapter, we will go deep into some of the fundamentals of Git. It is essential to understand how Git thinks about files, its way of tracking the history of commits, and all the basic commands that we need to master in order to become proficient.

Repository structure and file status life cycle

The first thing to understand while working with Git is how it manages files and folders within the repository. This is the time to analyze a default repository structure.

The working directory

In created an empty folder and initialized a new repository using the git init command (in C:\Repos\MyFirstRepo). Starting from now, we will call this folder the working directory. A folder that contains an initialized Git repository is a working directory. You can move the working directory around your file system without losing or corrupting your repository.

Within the working directory, you also learned that there is a .git directory. Let's call it the git directory from now on. In the git directory there are files and folders that compose our repository. Thanks to this, we can track the file status, configure the repository, and so on.

File statuses

In used two different commands: git add and git commit. These commands allowed us to change the status of a file, making Git change its status from "I don't know who you are" to "You are in a safe place".

When you create or copy a new file in the working directory, the first state of the file is untracked. This means that Git sees that there is something new, but it won't take care of it (it would not track the new file). If you want to include the file in your repository, you have to add it using the add command. Once it is added, the state of the file becomes unmodified. It means that the file is new (Git says it is unmodified because it never tracked changes earlier) and ready to be committed, or it has reached the staging area (also called index). If you modify a file that is already added to the index, it changes its status to modified.

The following screenshot explains the file status life cycle:

The staging area

The staging area or index is a virtual place that collects all the files you want to include in the next commit. You will often hear people talk about staged files with regard to Git, so take care of this concept. All the files (new or modified) you want to include in the next commit have to be staged using the git add command. If you staged a file accidentally, you have to unstage it to remove it from the next commit bundle. Unstaging is not difficult; you can do it in many ways. Let me explain a few concepts. This several ways to do the same thing is an organic problem of Git. Its constant and fast evolution sometimes increases confusion, resulting in different commands that do the same thing. This is because it will not penalize people used to working in a particular manner, allowing them the time for some Git revision to understand the new or better way. Fortunately, Git often suggests the best way to do what you want to do and warns you when you use obsolete commands. When in doubt, remember that there are man pages. You can obtain some useful suggestions by typing git <command> --help (-h for short) and seeing what the command is for and how to use it.

Unstaging a file

Well, back to our main topic. Before continuing, let's try to understand the unstaging concept better. Open the repo folder (C:\Repos\MyFirstRepo) in Bash and follow these simple steps:

  1. Be sure to be in a clean state by typing git status. If Git says "nothing to commit, working directory clean," we are ready to start.
  2. Create a new file touch NewFile.txt.
  3. Using git status again, verify that NewFile.txt is untracked.
  4. Add it to the index so that Git can start tracking it. So, use the git add NewFile.txt command and go on.
  5. When done, use the suggested git reset HEAD <file> command to back the file in the untracked status.

It should be clear now. It worked as expected: our file returned in the untracked state, as it was before the add command.

Tip

Git reset is a powerful command, and it can completely destroy your actual work if used improperly. Do not play with it if you don't know exactly what you are doing.

Another way to unstage a file that you just added is to use the git rm command. If you want to preserve the file on your folder, you can use the --cached option. This option simply removes it from the index, but not from the filesystem. However, remember that git rm is to remove files from the index. So, if you use the git rm command on an already committed file, you actually mark it for deletion. The next commit will delete it.

The time metaphor

Using the git reset command, we also get in touch with another fundamental of Git, the HEAD pointer. Let's try and understand this concept.

A repository is made of commits, as a life is made of days. Every time you commit something, you write a piece of the history.

The past

The past is represented by the previous commits that we did, as shown by C1 and C2 in the following diagram:

The HEAD pointer is the reference to the last commit we did or the parent of the next commit we will do, as shown in the diagram:

So, the HEAD pointer is the road sign that indicates the way to move one step back to the past.

The present

The present is where we work. When a previous commit is done, it becomes part of the past, and the present shows itself like this diagram:

We have a HEAD reference that points out where we came from (the C2 commit). Resetting to HEAD as we did earlier is a manner of going back in this initial state, where there are no modifications yet. Then, we have the working directory. This directory collects files added to the repository in the previous commits. Now, it is in the untouched state. Within this place, we do our work in files and folders, adding, removing, or modifying them.

Our work remains in the working directory until we decide to put it in the next commit we will perform. Using the git add command, we add what we want to promote to the next commit, marking them into the index, as shown in this diagram:

With git rm --cached <file or folder>, you can unstage a file by removing it from the index, as shown here:

With git reset --hard HEAD, we will go back to the initial state, losing all the changes we made in the working directory.

At the end, once you commit, the present becomes part of the past. The working directory comes back to the initial state, where all is untouched and the index is emptied, as shown in this diagram:

The future

What about the future? Well, even though Git is a very powerful tool, it can't predict the future, not for now at least.

Working with repositories

Let's get our hands dirty!

If you are reading this book, you are probably a programmer, as I am. Using a programming language and its source and binary files to exercise could be fine. However, I don't want to distract you from understanding a language you probably don't use every day. So, let's settle things once and for all. When needed, I will use a nice markup language called Markdown (see http://daringfireball.net/projects/markdown/).

Apart from being simple yet powerful, Markdown silently became the favorite choice while typing readme files (for example, GitHub uses it extensively) or comments in forums or other online discussion places, such as StackOverflow. Mastering it is not our goal, but to be able to do the basic things is surely a skill that can be useful in the future.

Before you proceed, create a new folder for exercises, for example, C:\Repos\Exercises. We will use different folders for different exercises.

Unstaging a file

Consider the following scenario. You are in a new repository located in C:\Repos\Exercises\Ch1-1. The working directory actually contains two files: first.txt and second.txt. You have accidentally put both first.txt and second.txt in the staging area, while you actually want to only commit first.txt. So, now:

  • Remove the second.txt file from the index
  • Commit the changes

The result of this is that only first.txt will be a part of the commit.

Follow these simple steps to solve this simple task:

  1. Create the C:\Repos\Exercises\Ch1-1 folder and open Bash inside it.
  2. Use the git init command on the repository as we learned.
  3. Create the first.txt and second.txt files and add them to the staging area, as shown in the following screenshot:

At this point, remove the second.txt file and commit. This time, try to use the handy git rm --cached command, as suggested here:

Well done!

Viewing the history

Let's continue where we left off in Chapter 1, Getting Started with Git. Walk into C:\Repos\MyFirstRepo and start a new Bash shell using the right-click shortcut. Now, use the git log command to see the history of our repository, as shown in this screenshot:

The git log command is very useful and powerful. With this command, we can get all the commits that we did one by one, each one with its most important details. It's now time to become more familiar with them.

Anatomy of a commit

Commits are the building blocks of a repository. Every repository is nothing more than an ordered sequence of commits. If you have a math background, you have probably already identified an acyclic direct graph in it.

The commit snapshot

Every time we commit something, Git wraps all the files included in a binary blob file. This is one of the most important characteristics of Git. While other versioning systems save files one by one (perhaps using deltas), Git produces a snapshot every time you commit, storing all the files you have included in it. You can assume that a snapshot is a commit, and vice versa.

The commit hash

The commit hash is the 40-character string we saw in logs. This string is the plate of the commit, the way we can refer to it unequivocally. We will not fail to use it in our little exercises.

Author, e-mail, and date

Git is for collaborating, too. So, it is important that every author signs every commit it does to make it clear who did what.

In every commit, you will find the author's friendly name and their e-mail to get in touch with them when necessary. If you are not happy with the actual author you see, change it with the git config command, as shown here:

Configuring Git is quite simple. You have to know the name of the object to configure (user.name in this example) and give it a git config <object> "<value>" command. To change the e-mail, you have to change the user.email configuration object.

If you want to see the value of an object, simply type git config <object> and press Enter to get it.

There are three configuration levels: global, user, and repository. We will soon deal with Git configuration in more detail.

Commit messages

In the first chapter, we made two commits, and every time, we added a commit message. Adding a commit message is not only a good practice, it is mandatory. Git would not accept commits without a relative commit message. This is a thing I really appreciate. Let me explain it briefly.

In other versioning control systems, such as SVN, commit messages are optional. Developers, we all know, are lazy people. When they are in a hurry or under stress, the temptation to commit code without telling what feature of the software they are going to add, modify, improve or delete is strong. In no time, you build a mute repository with no change history, and you have to deal with commits that are difficult to understand. In other words, welcome to hell.

Committing a bunch of files

At this point, probably, you are wondering if there is a way to add more than one file at a time. Of course there is! With git add --all (-A), you add all of the files you have in your working directory to the index. You can also use <filepattern> to add only certain types of files; for example, with git add *.txt, you can add all text files to the index.

Ignoring some files and folders by default

Often, we work with temp or personal files that we don't want to commit in the repository. So, when you commit all files, it is useful to skip certain kinds of files or folders.

To achieve this result, we can create a .gitignore file in the repository. Git will read it and then skip the files and folders we listed inside it.

Let's try to do this in our repository, C:\Repos\MyFirstRepo. Perform the following steps:

  1. Browse to C:\Repos\MyFirstRepo.
  2. Create a .gitignore file using your preferred editor.
  3. Put this text inside it:
    # === This is a sample of .gitignore file ===
    # Ignore temp files
    *.tmp
  4. Save the file.
  5. Add the file to the index.
  6. Commit the .gitignore file.
  7. Create a temp file fileToIgnore.tmp with a simple touch command.
  8. Try to add all of the files in your working directory to the index and verify that Git will not add anything.

Note that .gitignore file is not retroactive. If you have added some *.tmp files to the index before introducing the .gitignore file, they will stay under revision control. You have to remove them manually if you want to skip them.

The syntax of a .gitignore file is quite simple. Lines starting with # are comments, and they will be ignored. In each line of the file, you can add something to skip. You can skip a single file or folder, certain files by extension, as we did with *.tmp files, and so on.

For the complete syntax of .gitignore see http://git-scm.com/docs/gitignore.

Tip

To add a file in the .gitconfig file even if it is marked to be ignored, you can use the git add -f (--force) option.

Highlighting an important commit – Git tags

We said that commits have an ID and a lot of other useful information bundled with binary blobs of files included. Sometimes, we want to mark a commit with a tag to place a milestone in our repository. You can achieve this by simply using the git tag –a <tag name> command, using –m to type the mandatory message:

$ git tag –a MyTagName –m "This is my first tag"

Tags will become useful in the future to keep track of important things such as a new software release, particular bug fixes, or whatever you want to put on evidence.

Taking another way – Git branching

Now, it's time to introduce one of the most used features of a versioning system: branching. This is a thing that you will use extensively and a thing that Git does well.

Anatomy of branches

A branch is essentially another way your repository takes. While programming, you will use branches to experiment with changes, without breaking the working code. You will use them to keep track of some extensive work, such as the development of a new feature, to maintain different versions or releases of your project. To put it simply, you will use it always.

When in a Git repository, you are always in a branch. If you pay attention to the Bash shell, you can easily find the branch that you are on at the moment. It is always at the prompt, within brackets, as shown here:

The master branch is, conventionally, the default branch. When you do your first commit in a brand new repo, you actually do the first commit in the master branch.

During the course of the book, you will learn that the master branch will often assume an important role. We will only deploy code from that point, and we will never work directly in the master branch.

Looking at the current branches

Let's start using the git branch command alone. If you do this, you will get a list of the branches included in the current repository:

$ git branch

We will get the following response:

As expected, Git tells us that we have only the master branch. The branch is highlighted with a star and green font just because this is our actual branch, the branch in which we are located at the moment.

Creating a new branch

Now, let's start creating a new branch for a new activity.

Open the Bash shell in C:\Repos\MyFirstRepo and create a new branch from where you are using the git branch command, followed by the name of the branch we want to create. Let's assume that the name of the branch is NewWork:

$ git branch NewWork

Well, it seems nothing happened and we are still in the master branch as we can see in the following screenshot:

Git sometimes is a man of few words. Type the git branch command again to get a list of the actual branches:

Hey, there's a NewWork branch now!

Switching from branch to branch

The only thing we have to do now is to switch to the NewWork branch to begin working on this separate argument. To move around from one branch to another, we will use the git checkout command. Type this command followed by the branch name to switch to:

$ git checkout NewWork

This time, Git gave us a short message, informing us that we have switched to the NewWork branch. As you can see, NewWork is within brackets in place of master, indicating the actual branch to us:

Super easy, isn't it?

Understanding what happens under the hood

At this point, nothing special seems to have happened. However, you will soon change your mind. Just to give you a visual aid, consider this figure:

Assume that our last commit on the master branch was the C3 commit. At this point, we now have two other pointers: the master branch pointer and the NewWork branch pointer.

If you have used Subversion or a similar versioning system earlier, at this point, you would probably look for a NewWork folder in C:\Repos\MyFirstRepo. However, unfortunately, you will not find it. Git is different, and now, we will see why.

Now, to better understand what a branch is for, add NewWorkFile.txt, stage it, and commit the work, as shown here:

At this point, we have a new commit in the NewWork branch. This commit is not a part of the master branch, because it has been created in another branch. So, the NewWork branch is ahead of the master branch, as shown in the following figure:

Working with Git in this kind of a situation is ordinary administration, just like it is in the day-to-day life of a developer. Creating a new branch is cheap and blazing fast even in large repositories, because all the work is done locally. You can derive multiple branches from a unique point, branch from a branch that branched into another branch, and so on, creating all the ramifications you can manage without going crazy.

Moving from a branch to another is easy. So, let's turn back to the master branch and see what happens:

If you have used Subversion earlier, at this time, you are probably wondering at least two things: why is there no NewWork folder containing files from that branch and, most of all, what was the fate of the NewWorkFile.txt file.

In Apache Subversion (SVN), every branch (or tag) you checked out resides in a separate folder (bloating your repository, after some time). In Git, there is a huge difference. Every time you check out a branch, files and folders of the previous branch are replaced with those contained in the new branch. Files and folders only on the destination branch are restored, and those only on the previous branch are deleted. Your working directory is constantly a mirror of the actual branch. To have a look at another branch, you basically have to switch to it.

This aspect might look bad to you the first time, because seeing files and folders disappear is scary at first. Then, you can't differentiate the two branches as we probably did earlier by simply comparing the content of the two folders with your preferred tool. If I can give you a piece of advice, don't lose heart about this (as I did at the beginning): soon you will forget what you lost and you will fall in love with what you gained.

A bird's eye view to branches

So, let's go back once again to the NewWork branch and have a look at the situation with the aid of a visual tool, Git GUI. Open the C:\Repos\MyFirstRepo folder and right-click inside it. Choose Git GUI from the contextual menu. A dialog box will pop up as shown in the following screenshot:

Git GUI is not my preferred GUI tool, but you have it for free when installing Git. So, let's use it for the moment.

Go to Repository | Visualize All Branch History. A new window will open, and you will see the status of our current branches, as shown in the following screenshot:

We do not have the time to go into all the details here. As you become more confident with Git fundamentals, you will learn little by little all the things you see in the preceding picture.

If you don't want to leave the console to take a look, you could get a pretty output log even on the console. Try this articulated git log command:

$git log --graph --decorate --pretty=oneline --abbrev-commit

This is a more compact view, but it is as clear as what we saw earlier.

Typing is boring – Git aliases

Before continuing, just a little warning. The last command we used is not the easiest thing to remember. To get around this nuisance, Git offers the possibility of creating your own commands, aliasing some of the verbose sequences.

To create an alias, we will use the git config command. So, let's try to create a tree alias for this command:

$ git config --global alias.tree 'log --graph --decorate --pretty=oneline --abbrev-commit'

So, the syntax to create aliases is as follows:

git config <level> alias.<alias name> '<your sequence of git commands>'

Using the --global option, we told Git to insert this alias at the user level so that any other repository for my user account on this computer will have this command available. We have three levels where we can apply config personalization:

  • Repository level configs are only available for the current repo
  • Global configs are available for all the repos for the current user
  • System configs are available for all the users/repositories

To create a repository user-specific config, we used the --global option. For the system level, we will use the --system option. If you don't specify any of these two options, the config will take effect only in the repository that you are in now.

Merging branches

Now that we finally got in touch with branches, let's assume that our work in the NewWork branch is done and we want to bring it back to the master branch.

To merge two branches, we have to move to the branch that contains the other branch commits. So, if we want to merge the NewWork branch into the master branch, we would first have to check out the master branch. As seen earlier, to check out a branch we have to type the following command:

$ git checkout <branch name>;

If you want to check out the previous branch you were in, it's even simpler. Type this command:

$ git checkout –

With the git checkout – command, you will move in the previous branch without having to type its name again as explained in the following screenshot:

Now, let's merge the NewWork branch into master using the git merge command:

$ git merge NewWork

As you can see, merging is quite simple. With the git merge <branch to merge> command, you can merge all the modifications in a branch into the actual branch as shown in the following screenshot:

When we read console messages, we see that Git is telling us something interesting. Let's take a look.

When Git says Updating 986a6dc..a0c7d61, it is telling us that it is updating pointers. So now, we will have master, NewWork, and HEAD all pointing to the same commit:

Fast-forward is a concept that we will cover soon. When we tell Git to not apply this feature, in brief, this fast-forward feature (enabled by default) permits us to merge commits from different branches, as they were done subsequently in the same branch, obtaining a less-pronged repository.

Last, but not least, we have a complete report of what files are added, deleted, or modified. In our case, we had just one change (an insertion) and nothing else. When something is added, Git uses a green plus symbol +. When a deletion happens, Git uses a red dash symbol -.

Merge is not the end of the branch

This is a concept that sometimes raises some trouble. To avoid this, we will have a quick glimpse of it. You don't have to wait for the work to be done before merging another branch into your branch. Similarly, you don't have to think about merging as the last thing you will do before cutting off the other branch on which you worked.

On the contrary, it's better if you merge frequently from branches you depend on, because doing it after weeks or months can become a nightmare: too many changes in the same files, too many additions or deletions. Don't make a habit of this!

Exercises

To understand some common merging scenarios, you can try to resolve these small exercises.

Exercise 2.1

In this exercise we will learn how Git can handle automatically file modifications when they are not related to the same lines of text.

What you will learn

Git is able to automerge modifications on the same files.

Scenario

  1. You are in a new repository located in C:\Repos\Exercises\Ch2-1.
  2. You have a master branch with two previous commits: the first commit with a file1.txt file and the second commit with a file2.txt file.
  3. After the second commit, you created a new branch called File2Split. You realized that file2.txt is too big, and you want to split its content by creating a new file2a.txt file. Do it, and then commit the modifications.

Results

Merging back File2Split in the master branch is a piece of cake. Git takes care of deletion in file2.txt without spotting your conflicts.

Exercise 2.2

In this exercise we will learn how to resolve conflicts when Git cannot merge files automatically.

What you will learn

Git will result in conflicts if automerging is not possible.

Scenario

  1. You are in the same repository used earlier, C:\Repos\Exercises\Ch2-1.
  2. On the master branch, you add the file3.txt file and commit it.
  3. Then, you realize that it is better to create a new branch to work on file3.txt, so you create the File3Work branch. You move in this branch, and you start to work on it, committing modifications.
  4. The day after, you accidentally move to the master branch and make some modifications on the file3.txt file, committing it.
  5. Then, you try to merge it.

Results

Merging the File3Work branch in master gives rise to some conflicts. You have to solve them manually and then commit the merged modifications.

Deal with branches' modifications

As mentioned earlier, if you come from SVN at this point, you would be a little confused. You don't "physically have on the disk" all the branches checked out on different folders. Because of this, you cannot easily differentiate two branches to take into account what a merge will cost in terms of conflicts to resolve.

Well, for the first problem, there is not an SVN-like solution. However, if you really want to differentiate two checked out branches, you could copy the working directory in a temp folder and check out the other branch. This is just a workaround, but the first time can be a less traumatic way to manage the mental shift Git applies in this field.

Diffing branches

If you want to do it in a more Git-like way, you could use the git diff command. Let's give it a try by performing the following steps:

  1. Open C:\Repos\MyFirstRepo and switch to the master branch.
  2. Add some text to the existing NewFile.txt, and then save it.
  3. Add a NewMasterFiles.txt file with some text within it. At the end, add both files to the index and then commit them, as shown in the following screenshot:

Now, try to have a look at the differences between the master and NewWork repositories. Finding out the difference between the two branches is easy with the git diff command:

$ git diff master..NewWork

The syntax is simple: git diff <source branch>..<target branch>. The result, instead, is not probably the clearest thing you have ever seen:

However, with a little imagination, you can understand that the differences are described from the point of view of the NewWork branch. Git is telling us that some things on master (NewFile.txt modifications and NewMasterFile.txt) are not present in the NewWork branch.

If we change the point of view, the messages change to those shown in the following screenshot:

Note

To better understand these messages, you can take a look at the diff output at http://en.wikipedia.org/wiki/Diff_utility.

Another way to check differences is to use git log:

$ git log NewWork..master

This command lets you see commits that differ from the NewWork branch to the master branch.

There is even a git shortlog command to give you a more compact view, as shown in the following screenshot:

Using a visual diff tool

All these commands are useful for short change history. However, if you have a more long change list to scroll, things would quickly become complicated.

Git lets you use an external diff tool of choice. On other platforms (for example, Linux or Mac), a diff tool is usually present and configured, while on the Windows platform, it is generally not present.

To check this, type this command:

$ git mergetool

If you see a message like this, you would probably have to set your preferred tool:

Resolving merge conflicts

As we have seen, merging branches is not a difficult task. However, in real-life scenarios, things are not that easy. We have conflicts, modifications on both branches, and other weird things to fight. In this section, we will take a look at some of them. However, first, remember one important thing: it needs a little bit of discipline to make the most of Git.

You have to avoid at least two things:

  • Working hard on the same files on different branches
  • Rarely merging branches

Edit collisions

This is the most common kind of conflict: someone edited the same line in the same file on different branches, so Git can't auto merge them for you. When this happens, Git writes special conflict markers to the affected areas of the file. At this point, we have to manually solve the situation, editing that area to fit our needs.

Let's try this by performing the following steps:

  1. Open your repository located in C:\Repos\MyRepos.
  2. Switch to the NewWork branch and edit NewFile.txt by modifying the first line Added some text to the existing NewFile in Text has been modified.
  3. Add and commit the modification.
  4. Switch back to master and merge the NewWork branch.

As you can see, Git highlighted the conflict. A conflict-marked area begins with <<<<<<< and ends with >>>>>>>. The two conflicting blocks themselves are divided by a sequence of =======. To solve the conflict, you have to manually edit the file, deciding what to maintain, edit, or delete. After that, remove the conflict markers and commit changes to mark the conflict as resolved.

Once you resolve the conflicts, you are ready to add and commit:

Merge done, congratulations!

Resolving a removed file conflict

Removed file conflicts occur when you edit a file in a branch and another person deletes that file in their branch. Git does not know if you want to keep the edited file or delete it, so you have to take the decision. This example will show you how to resolve this both ways.

Keeping the edited file

Try again using NewFile.txt. Remove it from the NewWork branch and then modify it in master:

After that, merge NewWork in the master branch. As said earlier, Git spots a conflict. Add NewFile.txt again in the master branch and commit it to fix the problem:

When resolving merge conflicts, try to commit without specifying a message, using only the git commit command. Git will open the Vim editor, suggesting a default merge message:

This can be useful to spot merge commits looking at the repository history in the future.

Resolving conflicts by removing the file

If you agree with the deletion of the file in the other branch, instead of adding it again, remove it from your branch. So, use the git rm NewFile.txt command even in the master branch and then commit to mark the conflict resolved.

Stashing

Working of different features in parallel does not make a developer happy, but sometimes it happens. So, at a certain point, we have to break the work on a branch and switch to another one. However, sometimes, we have some modifications that are not ready to be committed, because they are partial, inconsistent, or even won't compile. In this situation, Git prevents you from switching to another branch. You can only switch from one branch to another if you are in a clean state:

To quickly resolve this situation, we can stash the modifications, putting them into a sort of box, ready to be unboxed at a later time.

Stashing is as simple as typing the git stash command. A default description will be added to your stash, and then modifications will be reverted to get back in a clean state:

To list actual stashes, you can use the list subcommand:

Once you have done the other work, you can go back to the previous branch and apply the stash to get back to the previous "work in progress" situation:

What we have seen is the most common scenario and most used approach, but stashing is a powerful Git tool. You can have multiple stashes, apply a stash to a different branch, or reverse apply a stash. You can even create a branch starting from a stash. You can learn more about this on your own.

Summary

In this chapter, you learned the core concepts of Git: how it handles files and folders, how to include or exclude files in commits that we do, and how commits compose a Git repository.

Next, we explored the most used and powerful feature of Git, its ability to manage multiple parallel branches. For a developer, this is the feature that saves you time and headache while working on different parts of your project. This feature lets you and your colleagues collaborate without conflicts.

At the end, we touched on Git's stashing ability, where you can freeze your current work without having to commit an unfinished change. This helps you develop good programmer habits, such as the one that tells you to not commit an unfinished change.

In the next chapter, we will complete our journey of Git fundamentals, exploring ways and techniques to collaborate with other people.

主站蜘蛛池模板: 静宁县| 通化县| 二连浩特市| 平安县| 武汉市| 潼关县| 玉山县| 乐山市| 镇原县| 图木舒克市| 揭东县| 剑川县| 金寨县| 苍梧县| 昭通市| 陇西县| 古浪县| 丰镇市| 丰城市| 安丘市| 古田县| 罗山县| 扬州市| 和硕县| 阿瓦提县| 大港区| 阳曲县| 苗栗市| 宜良县| 诸暨市| 沛县| 晋城| 五华县| 景德镇市| 凉山| 夹江县| 张家口市| 钦州市| 德州市| 丰宁| 古田县|