因为原文太长超出字数,Lesson 3 就放在另一篇文章里
How to Use Git and GitHub
标签(空格分隔): Udacity
Course Syllabus
Lesson 1: Navigating a Commit History
Lesson 2: Creating and Modifying a Repository
Lesson 3: Using GitHub to Collaborate
[TOC]
Lesson 1: Navigating a Commit History
1.1 Finding Diffs Between Larger Files
in windows
in Linux and Mac
1.2 Reflections
1.3 Properties of a VCS for Code
VCS:Version Control System
1.4 Manual Commits in Git
这个Commit的概念在git中很重要
1.5 Creating a Concept Map
1.6 Using Git to View History
使用git log
来查看history。每一个commit都有一个ID, Author, a data and a message assicioated with it.
用两个commit的ID来代替文件名,通过git diff commit_1_ID commit_2_ID
来查看两个commit之间的差别。这个效果和之前在shell里看到的效果是一样的,而且git中还有高亮。
1.7 Concept Map: diff
1.8 One Commit per Change Instructions
1.8.1 How Often to Commit
A good rule of thumb is to make one commit per logical change. For example, if you fixed a typo, then fixed a bug in a separate part of the file, you should use one commit for each change since they are logically separate. If you do this, each commit will have one purpose that can be easily understood. Git allows you to write a short message explaining what was changed in each commit, and that message will be more useful if each commit has a single logical change.
1.8.2 Commit Size Quiz
1.8.3 One Commit per Logical Change Solution
You commit all the changes required to add a new feature, which you’ve been working on for a week. You haven’t committed since you started working on it.
This commit seems too big. It's easier to understand what each commit does if each only does one thing and is fairly small. Going a week without committing is not the best idea.
You found three typos in your README. You fix and commit the first.
This commit seems too small. It would be better to fix all three typos, then commit. That way, your history won't get too cluttered with typo fixes. Plus, you don’t need to worry about introducing bugs to a README, so bundling changes together is more likely to be a good idea.
You commit all the changes required to add a new feature, which you’ve been working on for an hour.
This is probably a good size for a commit. All the work is on a single feature, so the commit will have a clear logical purpose. After an hour, the diff will probably have a fair amount of content in it, but not too much to understand.
On the other hand, sometimes after working for an hour you’ll have run into more than one natural committing point, in which case you would want to break the feature up into smaller commits. Because of this, “too big” could also be a reasonable answer here.
You fix two small bugs in different functions and commit them both at once.
This commit is probably too big. It would have been better to commit after the first bug fix, since the two bug fixes aren't related to each other.
Judgment Call
Choosing when to commit is a judgment call, and it's not always cut-and-dried. When choosing whether to commit, just keep in mind that each commit should have one clear, logical purpose, and you should never do too much work without committing.
1.9 Tracking Across Multiple Files
1.9.1 Git Commits Across Multiple Files
if you have a project to work on, you'll often have multiple files that you want to tract together. Git calls such collection of files a repository.
when you save a version in git, in other words, when you make a commit, you will save a version of every file in your repository.
在shell中实际操作一下.下面的命令可以检查which files have changed in each commit.
git log --stat
game.js | 5 +++--
表示有5处change,3处addition,2处deletion。
图中高亮的部分就是一个commit有三个文件同时changed.
通过git diff ID1 ID2
能看到两个commit之间三个文件的变化情况。
1.10 Cloning and Exploring The Repo
用git clone
得到本地的repo后,就不用再联网了
- Cloning a Repository
To clone a repository, rungit clone
followed by a space and the repository URL. - Exiting
git log
To stop viewinggit log
output, pressq
(which stands for quit). - Getting Colored Output
To get colored diff output, rungit config --global color.ui auto
- Using git log and git diff
As a reminder, runninggit log
will show a list of the recent commits with information about them, including commit IDs. Runninggit diff
followed by two commit IDs will compare the two versions of the code in those commits. - Entering commit IDs
If it is easier, you may enter the first four or more characters of the commit ID rather than pasting the entire ID.
1.11 Concept Map: repository, clone, log
1.12 Git Errors and Warnings
Git Errors and Warnings Solution
Should not be doing an octopus
Octopus is a strategy Git uses to combine many different versions of code together. This message can appear if you try to use this strategy in an inappropriate situation.
You are in 'detached HEAD' state
HEAD is what Git calls the commit you are currently on. You can “detach” the HEAD by switching to a previous commit, which we’ll see in the next video. Despite what it sounds like, it’s actually not a bad thing to detach the HEAD. Git just warns you so that you’ll realize you’re doing it.
Panic! (the 'impossible' happened)
This is a real error message, but it’s not output by Git. Instead it’s output by GHC, the compiler for a programming language called Haskell. It’s reserved for particularly surprising errors!
1.13 Checking Out Old Versions of Code
Most Recent Commit
The commit ID of the most recent commit is 3884eab839af1e82c44267484cf2945a766081f3
. You can use this commit ID to return to the latest commit after checking out an older commit.
Format of git checkout
The command Caroline types to checkout the "Revert controls" commit is git checkout b0678b161fcf74467ed3a63110557e3d6229cfa6
.
Entering commit IDs
If it is easier, you may enter the first four or more characters of the commit ID rather than pasting the entire ID.
git checkout讲解:
we can also temporarily change our files back to how they were at the time of any commit. This is called a git checkout, and it's sort of like restoring a previous version. In git, checking out a commit means resetting all of your files to how they were at the time that commit was made.
为什么用git checkout:
One reason might be, if a bug was introduced, but you're not sure which commit introduced it. You can test wether a commit has the bug by checking out that commit and running the code.
示意图
打开/home/xu/Udacity/version-control/asteroids/index.html,在网页里可以进行游戏,但是发现飞船的子弹是无限连续射出的,一定是有了bug。所以我们通过git checkout
回到写有Revert controls
的那个commit ID。
回到那个ID后,发现有detached HEAD state
的信息。
I get this strange warning we mentioned before. You are in detached head state. Like we mentioned, head is what git calls the commit that you're currently working on, and you've detached it here by checking out an older commit.
再次打开index.html
,发现子弹是一个一个发射,虽然正常了,但是飞船颜色也没用了。用git log
查看,发现之前的所有ID都没有了,想回到之前的commit只要直到ID就可以了,问题是那个ID,已经没了。现在我们直接用提前记好的ID,之后的可能有办法解决这个问题。
我们要找到子弹的bug,how?
I want to check out each of these commits one at a time until I find the one with the bug. 然后发现是25ede这个ID下有了bug.If we want to know exactly how the bug got introduced, we can use git diff to compare this commits and the previous one.
1.14 Git Workspace
这一节就是设置git的一些高亮,比如修改文件后,会有*
号提示等等一些便于使用git的设置。不过用zsh+oh my zsh,使用git 插件后(选好主题),这些配置都自动设置好了,所以没必要自己再去改配置。
Problem Set 1
1 Old File Plus Diff Quiz
左侧是原始文件,中间是原始文件和另一个文件的比较结果,根据结果,重建出另一个文件。
2 Tracking Versions Using Git Quiz
Using git diff
to compare the two versions would show the same changes as diff -u
did in the previous exercise.
This is true.diff -u
and git diff
show very similar outputs. Even if the exact format was slightly different, the actual changes indicated would be the same.
3 Git Command Review Quiz
Compare two commits, printing each line that is present in one commit but not the other.
git diff
will do this. It takes two arguments - the two commit ids to compare.
Make a copy of an entire Git repository, including the history, onto your own computer.
git clone
will do this. It takes one argument - the url of the repository to copy.
Temporarily reset all files in a directory to their state at the time of a specific commit.
git checkout
will do this. It takes one argument - the commit ID to restore.
Show the commits made in this repository, starting with the most recent.
git log
will do this. It doesn't take any arguments.
4 Behavior of git clone Quiz
If someone else gives you the location of their directory or repository, you can copy or clone it to your own computer.
This is true for both copying a directory and cloning a repository.
As you saw in the previous lesson, if you have a URL to a repository, you can copy it to your computer using git clone
.
For copying a directory, you weren't expected to know this, but it is possible to copy a directory from one computer to another using the command scp
, which stands for "secure copy". The name was chosen because the scp
command lets you securely copy a directory from one computer to another.
The history of changes to the directory or repository is copied.
This is true for cloning a repository, but not for copying a directory. The main reason to use git clone
rather than copying the directory is because git clone
will also copy the commit history of the repository. However, copying can be done on any directory, whereas git clone
only works on a Git repository.
If you make changes to the copied directory or cloned repository, the original will not change.
This is true for both copying a directory and cloning a repository. In both cases, you're making a copy that you can alter without changing the original.
The state of every file in the directory or repository is copied.
This is true for both copying a directory and cloning a repository. In both cases, all the files are copied.
4 Behavior of git log Quiz
git log
lists the most recent commit first, as you can verify by checking the commit dates. The middle commit probably contains the code for the mute button, since the commit message indicates that the mute button was added in that commit. The top commit also probably contain the mute button, since that commit is more recent and nothing suggests the mute button has been removed. The bottom commit probably does not contain the mute button, since that commit was created before the commit that added the mute button.
5 Behavior of git diff Quiz
git diff
是有顺序的.git diff A B
,其实A是old file, B是new file, 这样才会显示B比A多了什么,少了什么。 顺序很重要。
The middle commit, 06d72e
, is the first commit with the mute button, so comparing that commit and the previous commit, 3d4d45
, would show the changes that add the mute button.
In order for the changes adding the mute button to be shown as additions, the commit with the mute button needs to be the second argument to git diff
. That is because git diff
considers the first argument as the "original", and the second argument as the "new" version, so additions are lines present in the second argument but not the first.
Thus, the last command listed, git diff 3d4d45 06d72e
, is correct, and would show the mute button lines as additions. Reversing the arguments and running git diff 06d72e 3d4d45
would instead show the mute button lines as deletions.
6 Behavior of git checkout Quiz
Checking out an earlier commit will change the state of at least one file.
This is sometimes true. Git doesn't allow you to save a new commit if no files have been updated, so you might think this is always true. However, it's possible to do the following:
- Save a commit (call this commit 1).
- Update some files and save another commit (call this commit 2).
- Change all the files back to their state during commit 1, then save again (call this commit 3).
就是说当commit 2引入了bug后,我们checkout到commit 1,在修正bug后提交,这个时候创建的是commit 3。
This sometimes happens if commit 2 contained a bug, and it's important to fix the bug quickly. The easiest thing to do might be to remove all the changes introduced by commit 2 to fix the bug, then figure out how to safely reintroduce the changes later.
At this point, commit 3 is the latest commit, so if you checkout commit 1, none of the files will be changed.
Checking out an earlier commit will change the state of more than one file.
Checking out an earlier commit will change the state of every file in the repository.
Both of these are sometimes true. Since each commit tracks the state of all files in the repository, it is possible that checking out an earlier commit will change the state of multiple files, or even all the files in the repository. However, it is possible to save a new commit after changing only one file, so it is possible only one file will change.
After checking out a commit, the state of all the files in the repository will be from the same point in time.
This is always true. A commit saves a snapshot of all files in the repository at the time the commit was made, so checking out an earlier commit will result in all the files being reverted to their state at the time the commit was made. That is, the files will be in a consistent state.
7 Cloning a New Repository
clone一个新的游戏 Repository,叫Pappu Pakia.用这个来做练习,找bug之类的。
7.1 Identifying a Bug
- Buggy behavior
If you started playing Pappu Pakia, you should have noticed some pretty strange behavior! The game seems empty of any obstacles, so it's pretty boring. Also, the bird (called a "pappu"), seems to flicker in various locations across the screen.
- Solution
The commit that introduced this bug has the ID 547f4171a82ec6429d002c1acef357aec41d3f17. One way to find this out would have been to run git log, which should have shown that the most recent 4 commits, and their commit ids, were:
commit fa4c6bade4970c282b3870ad16f1bde8164663a9
changing flattr link
commit 708bcce690e5faa5739bd471507c102ea16b77f7
pressing down arrow wont cause scroll down anymore
commit 547f4171a82ec6429d002c1acef357aec41d3f17
refactoring collision detection
commit 71d52709ddc4066e7a79a1d0a412e43429a0cdeb
removing old readme
(This output has been shortened to be easier to read.)
Then you could use git checkout to examine old commits and see which ones have the bug. You already know the most recent commit, "changing flattr link" has the bug, so you could run git checkout 708bcce690e5faa5739bd471507c102ea16b77f7
to test the second-most-recent commit . You should find that this commit also has the bug. Next, you'll find the commit "refactoring collision detection" also has the bug, but the commit "removing old readme" does not. That means the commit "refactoring collision detection" with commit ID 547f4171a82ec6429d002c1acef357aec41d3f17
, is the one that introduced the bug.
7.2 Fixing the Bug
To find the lines introduced by the buggy commit, you can use git diff
. You'll need the ID of the buggy commit, which you just found to be 547f4171a82ec6429d002c1acef357aec41d3f17
. Then you'll need the ID of the previous commit, which will be the commit below it in git log
. (That's because git log lists the most recent commit first.) That turns out to be 71d52709ddc4066e7a79a1d0a412e43429a0cdeb
.
Thus, by running git diff 71d52709ddc4066e7a79a1d0a412e43429a0cdeb 547f4171a82ec6429d002c1acef357aec41d3f17
, you can find out that the lines changed by the buggy commit were:
- return !(
- bounds1.end_x < bounds2.start_x ||
- bounds2.end_x < bounds1.start_x ||
- bounds1.end_y < bounds2.start_y ||
- bounds2.end_y < bounds1.start_y
- );
-
+ if (bounds1.end_x < bounds2.start_x) {
+ return true;
+ }
+ if (bounds2.end_x < bounds1.start_x) {
+ return true;
+ }
+ if (bounds1.end_y < bounds2.start_y) {
+ return true;
+ }
+ if (bounds2.end_y < bounds1.start_y) {
+ return true;
+ }
+ return false;
This change represents an "or" expression being separated out into several "if" statements. The number of functions did not change, and no variables were renamed.
- File changed
Near the top of the git diff
output, you can see the lines
--- a/js/utils.js
+++ b/js/utils.js
This indicates that the file changed was js/utils.js
, that is, the file utils.js
within the js
directory.
What caused the bug
Based on the change that was made, a reasonable guess is that the bug is some sort of logic error - maybe the new version does not return true and false at the correct times.
It turns out that this is correct. The new code has true
and false
reversed! Even if you weren't sure exactly why the bug was there, congratulations! You tracked down exactly where the bug was introduced, and knew which lines introduced it, without knowing the code base. All you had to know was how to use Git.
7.3 Identifying a Second Bug
** There is a second bug**
Now you should have a version of the code that works much better - your pappu is not flickering across the screen, and there are plenty of obstacles to avoid. However, there is another, harder to see, bug in the code.
During the game, a cluster of berries appears reasonably often. When the pappu hits those berries, it should split into three pappu clones, but instead, nothing seems to happen.
Finding the bug
This time, instead of checking out old versions of the code, just run git log and look at the 10 most recent commits. Based only on the commit messages, which commit do you think is most likely to have introduced this bug? You can't be sure just by reading the messages, but pick the one you think is most likely.
** Identifying a Second Bug Solution**
One reasonable guess is that the commit with message "speeding clones up", that is, commit 003c8c197cd3b1e5b28b58f53ee14d7ebaa9bb3a
, is likely to be the one causing the bug. The bug is related to clones, and this commit changed the behavior of clones, so it seems plausible that this commit caused the bug.
Of course, the most likely-looking commit won't always be the culprit, so you'll always have to take a closer look at the suspicious commit to see if it actually caused the bug. In this case, the commit "speeding up clones" did in fact cause the bug.
Using this strategy of examining the most likely looking commits doesn't always work, but it often does, and it can save a lot of debugging time. This is one of the reasons it's so useful to make one commit per logical change and give each commit a good message - to make it possible to take shortcuts like this!
7.4 Fixing the Second Bug
Changes introduced
As before, you can use git diff
to find the lines introduced by the buggy commit. Again, you'll need the ID of the buggy commit, which is 003c8c197cd3b1e5b28b58f53ee14d7ebaa9bb3a
, and the ID of the previous commit, which is 746f762e38b5bbb7d0b837464ef6ec3f8ee5bf91
.
Thus, by running git diff 746f762e38b5bbb7d0b837464ef6ec3f8ee5bf91 003c8c197cd3b1e5b28b58f53ee14d7ebaa9bb3a
, you can find out that the change made by the buggy commit was:
- clone.x += utils.randomNumber(5, 10);
- clone.y += utils.randomNumber(-20, 20);
+ clone.x += utils.randomNumber(500, 1000);
+ clone.y += utils.randomNumber(-2000, 2000);
That is, the x
and y
coordinate of each clone is changed by a larger random amount. This will have the effect of making the clones move more quickly, or speed up, since their positions change more quickly.
File changed
Near the top of the git diff
output, you can see the lines:
--- a/js/pappu.js
+++ b/js/pappu.js
This indicates that the file changed was js/pappu.js
, that is, the file pappu.js
in the directory js
.
What caused the bug
Based on the change that was made, one possible bug is that the clones move too quickly - so quickly they have left the screen before you see them. This turns out to be correct. If you change the code to have numbers bigger than the original numbers, but smaller than the new numbers, the clones will move more quickly, but still be visible. Some lines of code that work well are:
clone.x += utils.randomNumber(20, 40);
clone.y += utils.randomNumber(-30, 30);
Again, even if you weren't sure exactly why the bug was there, congratulations! You tracked down which lines introduced a bug without knowing the code base, just by using Git.
Lesson 2: Creating and Modifying a Repository
2.1 What Makes a Repository a Repository?
通过git clone
得到metadata后,有些hidden directory是看不到的。比如.git
.必须用ls -a
才能看到隐藏文件.而这个隐藏的.git
is the thing that Makes a Repository a Repository
2.2 Initializing a Repository
通过git init
来初始化。比如在recipes
directory下初始化,那么这个repository的名字就是recipes
。这个文件夹下的所有文件都会包括在repository里。
Git repositories and directories
Each Git repository is tied to a specific directory - the directory where you ran git init
. Only files from that directory (and subdirectories inside that directory) will be contained in that repository, and you can have different repositories in different directories.
Note: it's often the case that a Git repository in some directory will only contain, or track, some of the files in that directory, rather than all of them. You'll see how this works later this lesson.
QUIZ
git init
不会有commit ID,这个必须自己手动,而且要写commit message.
2.2.1 Examining the New Repository
用git log
返回错误,因为git init
之后,没有commit.但我们可以用git status
查看现在的repository 状态。status shows which files have changed since the last commit.
2.3 Staging Area
利用staging area做一个缓冲区,可以一下子commit多个files。通过git add
把working directory 里的files add to the staging area.
2.4 Concept Map: init, add, staging area
2.5 Writing Good Commit Messages
How to write a commit message
You're about to make your first commit to your reflections repository. When you do this, you'll need to write a commit message describing your changes. If you followed the instructions in the "Setting Up Your Workspace" video for your platform near the end of Lesson 1, the editor you chose will appear as soon as you run git commit
and allow you to write a commit message. If you get an error message, you should try revisiting the instructions in Lesson 1 and make sure your text editor is set up properly.
You can also specify a commit message via the command line by running git commit -m "Commit message"
instead of just git commit
. It's still a good idea to get an editor set up, since this will make it easier to write long commit messages that fully describe the change.
简单一句话:不要用git commit
!要用这个:git commit -m "Commit message"
Commit message style
While commit message style varies from person to person, this style guide describes some common best practices when writing commit messages.
这个guide写的很好,到了自己写message的时候,一定要好好参考。
全部commit后,用git status
检查,显示nothing to commit, working directory clean。
2.6* git diff Revisited(重点)
之前的git diff都是比较commit ID之间的,但是有时候我们也想比较working directory, staging area, and Repository之间的变化。比如刚刚take a break,回来的时候忘记modify了哪些部分。
先回顾一下之前用git diff找asteroids里bug的流程。(step 1 and step 2)
- 打开directory asteroids 的时候,发现当前的ID是之前的一个ID,不是最新的
3884eab839
。而且game.js
也被modify了。发现必须得commit or discard the modification ofgame.js
,才能checkout
回到3884eab839
。于是用git checkout game.js
discard the modification, and thengit checkout 3884eab839
回到初始状态。现在这个状态是最开始clone完的状态,有bug。 - 我们已经知道了有bug的ID是
25ede836
with the message "a couple missing ends with the ipad verison", the previous one isdf035382
。用git diff df035382 25ede836
找到不同的地方。game.js
,加上删去的那一行。用git status
检查,看到modified: game.js
. -
st index.html
打开后随便找一行collapse the line onto the previous one. - 好了。我们现在有两个文件处于modified。一个是
games.js
,这个文件之前有bug,子弹可以连续射出。通过比较bug的ID和previous one, 我们发现the bug one 少了一行,我们加上。再把index里面的随便一行更改一下位置。每个modified file都有一个*
标记,表示虽然更改了,但是没有commit。(我用的注意是最右侧有一个雷电的标识表示有文件modify了)。
现在来看一下working directory, staging area, and Repository的状态。
- The repository contains several commits. And each commit contains several files. 比如现在图中的the most recent commit contain the
game.js
andindex.html
,这些是ID3884eab839
下的文件,也就是说有bug的文件。修改的文件还在working directory. - The staging area is a copy of the most recent commit until I add changes to it. So it has those same files.
- The working directory also has the same files in it, but I've made some updates to
game.js
andindex.html
,which I'll represet using these stars.
We know that we can use git diff
to compare two commits by entering their commit ID's. 但是staging area and working directory are not commits, so they don't have ID's.
- 那么我们该怎么比较staging area and working directory里的modification呢?
Solution: use git diff
to compare working directory and staging area, git diff
不加arguments. This will show any changes you've made that you haven't added to the staging area yet.
(注意两个diff的用法)
试验一下,现在有game.js
and index.html
处于modification状态,run git diff
,结果会把staging area 里的文件当做old file, wroking directory里的文件当做new file:
- add
game.js
to the staging area and rungit diff
again
This time we only see the changes to index.html
,因为现在working directory and staging area里的game.js
是一样的了。(since the game.js
is the same in the staing area and workign directory).Add a star to game*
in the staging area.
现在我们可以用git diff --staged
查看staging area and Repository之间的changes。因为现在只有game.js
不同,所以只会显示the changes of games.js
run git diff --staged
:
-
git commit -m "Add delay back to bullets"
, thengit diff
git commit 之后,创建了新的commit ID 943a54d
,此时只有index.html
处于modefied状态,the game.js
is all same in three stage.
- 但如果我不想要
index.html
里的changes怎么办?
use git reset --hard
, which discards any changes in either the working directory or the staging area. 但是用的时候一定小心。虽然在git里大部分的操作都是reversible,你可以随时resotre previous commits,但是!!!But you've never committed the changes in your working directory or staging area. So if you run this command, you can't get those changes back.
QUIZ
注意第二行,git diff --staged
比较的是staing area and commit1
2.7 Commit the Bug Fix
Leave 'detached HEAD' state
Right now, your HEAD should still be 'detached' from Lesson 1 when you checked out an old commit. To fix that, run the command git checkout master
. You'll learn more about what this command does later this lesson.
Fix the delay bug
Now, if you were following along with Caroline, you may have already fixed the bug in the Asteroids repository. If not, go ahead and make the change and add it to the staging area now.
In game.js
find the statement if (this.delayBeforeBullet <= 0) {
(should be on line 423). On the next line, insert this.delayBeforeBullet = 10;
Instructor Notes
You may notice that our commit id is different from yours, even though we made the same change, while the commit ids up to this point have all been the same. That’s because if there is any difference between two commits, including the author or the time it was created, the commits will have different ids.
2.8 Branches
比起那些不易理解的commit ID,创建branch可以更方便人类理解。比如一个program要做一个中文版,那就创建一个branch,起名叫"chinese". 在git中默认的最主要的branch是master.
master branch has quilty, so it should be stable. 当我们做一些experiment 或是 add new feature的时候,会创建一个新的branch. Branches are also good when not only collaborating publicly, but they really good to collaborate with yourself.
2.8.1 Making a Branch
git branch
show the current branch, git branch easy-mode
create a new branch with the argument name. * master
表示当前所在的branch是master,git checkout easy-mode
switch to the easy-mode branch,
找到game.js
中关于fragement的代码,把3
改为2
,这样游戏里击中陨石后就会变为两半而不是三部分。
We consider this one logical change since it changes the behavior of the game.
git add game.js
, git commit -m "Make asteroids split into 2 smaller pieces instead of 3
git status
显示:
On branch easy-mode
nothing to commit, working directory clean
2.8.2 Branches for Collaboration
通常一个project的workflow是这样的,一开始有master,然后分为两个branch,一个bug-fix, 一个feature.这样可以同时进行debug和add feature的工作。
Then once a feature or bug-fix is complete, the author can either update master to the tip of new branch,
or combine the feature branch with the current master, using git merge feature.
实战
We add a new feature to the game, a new game mechanic. you can collect coins by touching them with your ship.
现在老师A已经创建了branch coin
, 她告诉老师B可以test it. She can get some feedback before adding it into the main branch.
测试发现能通过touch coin得分,但是spaceship没有颜色,git log
后发现在branch coin里没有添加颜色的commit Id。git checkout master
,git log
发现在master里有添加颜色的commit.
Git can help you visualize the branch structure via the command:
git log --graph --oneline master coins
--oneline
means making the output shorter and easier to see.
master coins
means telling the Git which branch I want to visualize
output分为三部分,从下到上分别是:
- These commits existed before the coins branch was created.
- These are commits that Sarah added to the coins branch.
- And these are commits that were added to master after the coins branch was created.
Quiz
2.8.3 Reachability
每一个commit都有自己的parent, 只有一个没有,the initial one. Using arrow to represent parent.
但是有些commit之间是unreachable的
quiz,简图,a and b are branch name.
2.8.4 Detached HEAD Revisited
To understancd the entire detached head message. Remember, to get this message, we checkout a commit, not a branch. Remember the head just means current commit.
Running git checkout -b new_branche_name
is equivalent to running two commands.
First, its just like running git branch new_branch_name
and then, running git checkout
on that new branch.
比如我一直master这条直线上的某个branch做开发,添加了一个new feature, 我想要把这个feature独立出来,就用git checkout -b new_branche_name
把这个new feature 从master主线上独立出来,head也会teach到这个new branch上,图中最下面的new_branch_name 就是new branch, 对于其他branch,这个new branch是unreachable的,不论怎么玩都没关系。
2.8.5 Combining Simple Files(Mering files)
How to conbine braches into single version?
之前讲到了branch coin添加了吃coin的feature,但是spaceship没有颜色,而branch master的spaceship有颜色,但没有吃coin的feature,我们要combine these two branch into a new branch.
An example quiz
Jack和Rachel都有B和D,所以我们希望在final version里有这个连feature.但是其他三个都是只有一个人添加了的feature,我们不确定这个feature在final version 里是否是必须的,所以不确定。
那么怎么来确定需要哪个feature呢?如果我们知道original里有哪些feature的话呢?
quiz two
A:Rachel 有A,没做什么修改,但Jack明确地删除了A,说明不需要A。
C:Original和Jake都没有C,但是Rachel added it. 说明it should be in the final.
E: same with C.
2.8.6 Merging Coins into Master
现在branch coins(这个coin只是一个commit ID的易于理解的名称,一个branch tip,现在这个branch coins 的head teach在ships on conis这个commit上),而master在color这个commit上。我们要把master当做主线,把coins添加到里面去。
利用三个commit,git能实现merge的功能。图中蓝色画圈的三个。
revert controls 是Jack和Rechel开始分道扬镳的commit,
ships on coins 是Jack added coins feature
color 是Rechel在master 主线上的更改,包括添加了color等feature.
这个combined version is also a commit. 而这个new commit会store the information of its parent, 其parent就是color和ships on coins. 这样的merge能保存两条线上所有的commit.
一旦我们成果merge,就不在需要coins这个branch了,因为我们可以用合并后的master直接回溯到之前的commit.So once we're down with the merge, we can delete the coins branch. 注意我们删除的是label,而不是commit.(Note that when we talk about deleting braches, we mean deleting the label. The commmits will still be there in the history.) However, if no branches can reach the commit, deleting a branch does effectively discard its commits.
So if you deleted the coins branch without merging it in first, you would essentially be abandoning these commits since they would all become unreachable.
quiz
merge后,git log
能看到哪些commits?
只有右下角哪个easy是unreachable.
2.8.7 Merging on the Command Line(实操)
Comparing a commit to its parent
The command Caroline mentions to compare a commit to its parent is git show commit_id
.也就是说不用非得找到两个commit ID一起提交,只要找到一个有bug的ID,就能看到它和它parent之间的差别。
Checking out the coins branch
If you haven't already checked out the coins branch, you'll need to do so now with the command git checkout coins
before you'll be able to refer to it. Once you've done that, decide whether you should keep it checked out or check out a different branch before completing the merge.
A note aboutgit merge
下面一大堆就是想说,在git merge
之前,一定要想checkout切换到要merge的branch上,不然会把current branch也merge掉。比如,git checkout branch1
and then type git merge branch2
. The only reason to type git merge branch1 branch2
is if it helps you keep better mental track of which branches you are merging.
git merge
will also include the currently checked-out branch in the merged version. So if you have branch1 checked out, and you run git merge branch2 branch3
, the merged version will combine branch1 as well as branch2 and branch3. That’s because the branch1 label will update after you make the merge commit, so it’s unlikely that you didn’t want the changes from branch1 included in the merge. For this reason, you should always checkout one of the two branches you’re planning on merging before doing the merge. Which one you should check out depends on which branch label you want to point to the new commit.
Since the checked-out branch is always included in the merge, you may have guessed that when you are merging two branches, you don't need to specify both of them as arguments to git merge
on the command line. If you want to merge branch2 into branch1, you can simply git checkout branch1
and then type git merge branch2
. The only reason to type git merge branch1 branch2
is if it helps you keep better mental track of which branches you are merging.
Also, since the two branches are merged, the order in which they are typed into the command line does not matter. The key is to remember that git merge
always merges all the specified branches into the currently checked out branch, creating a new commit for that branch.
Merge conflict
如果出现这个confilict,到时候在查看这个网站,看教程
If you get a message like this
Auto-merging game.js
CONFLICT (content): Merge conflict in game.js
Automatic merge failed; fix conflicts and then commit the result.
必须先checkout到master, 再merge coins. merge后,会有up arrow,提示你可以git push
,把local推送到远端服务器。如果想要Undo this merge, 用命令git reset --hard commit_sha
。commit_ID
是merge前的一个commit ID.
merge后,会有不同的author,不同的branch线,组合在一起,用git log
很难找到某个commit的parent。这个时候我们用本小结一开是介绍的git show commit_ID
来查看某ID和其parent的区别。
merge成功后就可以delete the branch coins, 用git branch -d coins
。
用git log --graph --oneline
查看commit信息。
2.8.8 Merge Conflicts
orinical里B是原版,但Jake里的B'是修改过的,而Rachel里的B''也是修改过的。如果merge的话,最后的文件里究竟是哪个B
结果是不确定
2.8.9 Conflict Detection
how Git know whether there is a merge conflict?
举例来说,有两个文件。第一个文件foo.py
,第二个文件名字不同,实现方法不同,但功能一样。
Consider the following two examples. In both cases, we start with two identical copies of the same file. In the first case, two different contributors add new functions to the bottom of the files. These are different functions that don't interact with each other and have nothing to do with one another. However, in the second case, two different contributors add different implementations of the same function with different names.
Git无法分辨第二个例子里哪个文件应该保留。
In the first case, you pretty clearly want both functions to be included, but in the second situation, you probably only want one version of the function, probably whichever one is either more memory efficient or
faster, depending on what you're going for. But Git can't really tell these two options apart.
所以Git不会管那么多闲事,它会把有confilct的文件提醒给contributor,让这些人自己去决定选择哪个文件。
Git just assumes that if you're merging together two commits that have changes in the same general area, the authors will want to know about it and have the chance to figure out for themselves which change to keep. This decision to ask the user whenever there's any ambiguity at all does sometimes lead to situations where it seems really obvious to you, as an expert on the content, how to resolve the conflict. But Git brings it to your attention anyway. While this may be annoying, it's significantly better than if Git tried to guess too often, which could lead to weird conglomerate changes that don't really make any sense and probably wouldn't compile or run.
2.8.10 Update Easy Mode
Motivation
Master has updated since the easy-mode branch was created. In this case, it has the addition of coins, which you might like to include in the easy-mode branch. In general, it’s very common that if you make a branch, either an experimental branch or to work on a new feature, you want to periodically merge master into that branch. This is because master usually contains the official version of the code, and it’s common to want experimental changes to include all of the changes to master.
Previous version of the diagram
Here’s what the history looks like right now.
Draw an updated diagram
If you merge master into the easy-mode branch, what will the history look like afterward? Take a minute to draw the new history using whatever method you like best. You might want to use pencil and paper, or create a text file with stars and dashes similar to the output of git log --graph
, or maybe use an online diagramming tool like gliffy or yUML. Once you’re finished, watch the solution to compare your diagram to Sarah’s.
Solution
2.8.11 Resolving Merge Conflicts
merge master 和 easy-mode的时候,出现了merge conflict, 因为两个branch都对game.js
做了更改。
打开game.js
,GIT会把有问题的部分标记出来。一些special lines会把内容分为三个部分。
The top section, marked HEAD is my code. The bottom section, marked master is the code that's in master right now. And the middle section is the original version that both branches modified,
which git called the common ancestor.
the HEAD and common ancestor
the master
如果解决?
Now when I'm trying to resolve a merge conflict, the first thing I do is try to understand what changes both branches have made. The difference between the middle section and the top, shows the changes that I made in the easy-mode branch. If I'd forgotten what those changes were, I can spend a few minutes comparing and recall that the difference was changing this three to a two.
The difference between the middle and the bottom shows the changes that were made in master.
这样的conflict在merge的时候很常见,发现后可以直接和另一个contributor交谈看怎么解决问题,但是我们最好先自己看一下这些difference. it looks like Sarah replaced this entire section of code with a call to a function called breakIntoFragments. Now that probably means that she created this function and moved this code into that function. Which is a pretty common way to make code more readable by breaking it up into understandable parts.
Now it might look like this function existed in both versions. Since it's not inside the special merge conflict lines. But actually, that's just because this change didn't create a conflict. Now after I've spent a few minutes comparing this function to the code that was removed by Sarah below.
I could see that she didn't make any changes to the code, other than to move it.
Now, how should I reserve this merge conflict?
For example, if I wanted to undo my changes, I could just delete my version and the original version, as well as these special lines to leave only Sarah's code. Instead, you should create a version of the code that incorporates both of our changes.
2.8.12 Resolving Merge Conflicts Quiz
conflict file
Asteroid = function() {
this.breakIntoFragments = function () {
for (var i = 0; i < 3; i++) {
var roid = $.extend(true, {}, this);
roid.vel.x = Math.random() * 6 - 3;
roid.vel.y = Math.random() * 6 - 3;
if (Math.random() > 0.5) {
roid.points.reverse();
}
roid.vel.rot = Math.random() * 2 - 1;
roid.move(roid.scale * 3); // give them a little push
Game.sprites.push(roid);
}
};
this.collision = function (other) {
SFX.explosion();
if (other.name == "bullet") Game.score += 120 / this.scale;
this.scale /= 3;
if (this.scale > 0.5) {
<<<<<<< HEAD
// break into fragments
for (var i = 0; i < 2; i++) {
var roid = $.extend(true, {}, this);
roid.vel.x = Math.random() * 6 - 3;
roid.vel.y = Math.random() * 6 - 3;
if (Math.random() > 0.5) {
roid.points.reverse();
}
roid.vel.rot = Math.random() * 2 - 1;
roid.move(roid.scale * 3); // give them a little push
Game.sprites.push(roid);
}
||||||| merged common ancestors
// break into fragments
for (var i = 0; i < 3; i++) {
var roid = $.extend(true, {}, this);
roid.vel.x = Math.random() * 6 - 3;
roid.vel.y = Math.random() * 6 - 3;
if (Math.random() > 0.5) {
roid.points.reverse();
}
roid.vel.rot = Math.random() * 2 - 1;
roid.move(roid.scale * 3); // give them a little push
Game.sprites.push(roid);
}
=======
this.breakIntoFragments();
>>>>>>> master
}
};
};
solution
In this case I want to keep Sarah's change of moving this code to the function.
So I'll need to make my change to the function.
So, I'll scroll back up to the function and change this 3 to a 2 (line 4).
Now I'll delete my version since I already have the change from it and the original version since I don't need that version any more and I'll delete git's special lines.
I'll also clean up a little bit by removing these blank lines. Now I'm left with the call to the breakIntoFragments function, which breaks each asteroid into two fragments, rather than three.
changed file:
Asteroid = function() {
this.breakIntoFragments = function () {
for (var i = 0; i < 2; i++) { //change 3 to 2
var roid = $.extend(true, {}, this);
roid.vel.x = Math.random() * 6 - 3;
roid.vel.y = Math.random() * 6 - 3;
if (Math.random() > 0.5) {
roid.points.reverse();
}
roid.vel.rot = Math.random() * 2 - 1;
roid.move(roid.scale * 3); // give them a little push
Game.sprites.push(roid);
}
};
this.collision = function (other) {
SFX.explosion();
if (other.name == "bullet") Game.score += 120 / this.scale;
this.scale /= 3;
if (this.scale > 0.5) {
// break into fragments
for (var i = 0; i < 2; i++) {
var roid = $.extend(true, {}, this);
roid.vel.x = Math.random() * 6 - 3;
roid.vel.y = Math.random() * 6 - 3;
if (Math.random() > 0.5) {
roid.points.reverse();
}
roid.vel.rot = Math.random() * 2 - 1;
roid.move(roid.scale * 3); // give them a little push
Game.sprites.push(roid);
}
this.breakIntoFragments();
}
};
};
2.8.13 Committing the Conflict Resolution
我想保留branch easy-mode, 所以merge master into easy-mode. 先checkout easy-mode, 再merge. 发现有conflict,我们按照上一节的教程,修改好game.js
。
更改完成后,git status
, 显示 fix conflict. git add game.js
后,再git status
, 显示 you are still merging.
git commit -m "fix merge confict, merge master into easy-mode"
, 不知道怎么设置能像教程里那样自动弹出写message的editor界面,所以只好自己写message,要尽量写的详细些。
git log --graph --oneline
:
quiz
git log -n 1
只输出一个commit,更改数字1
可以得到不同数量的commit
output:
2.9 Concept Map: branch, merge
You know that merging and branching both have to do with commits, but what kind of relationships does each of these ideas have with the commit.
Branches are really just labels that refer to commits, so we have this new kind of relationship, through first year relationship. So this has to be branch. Let's make sure this really makes sense.
Does diff really have an operates on relationship with branch?
Well, yes.You can diff two commits or you can diff two branches, which is basically just diff into two commits. But they're sort of different ideas, similarly you can run log on a branch or on a commit.
So let's check this one, does merge operate on commits?
Well, yeah, merge takes two commits and sort of smushes them together into a new commit.