Git Learning Notes

Git 简介

Git is a version control system for tracking changes in computer files and coordinating work on those files among multiple people.

It is primarily used for source code management in software development, but it can be used to keep track of changes in any set of files.

As a distributed revision control system it is aimed at speed, data integrity, and support for distributed, non-linear workflows.

From Wikipedia

简单来讲,Git 是一个分布式版本控制系统,它允许软件开发者可以共同参与一个软件开发专案,但是不必在相同的网络系统下工作。

Git 安装

下载安装

Windows 上安装 Git,从 Git 官网下载安装程序,按默认选项安装即可。

安装完成后,在开始菜单里找到并打开 “Git” -> “Git Bash” ,若出现如下图所示的类似命令行窗口的东西,说明Git安装成功!

Git Bash

设置

安装完成后,还需要进行设置,在命令行输入:

1
2
git config --global user.name "Your Name"
git config --global user.email "Your Email"

创建版本库

版本库(repository),可以简单理解成一个目录,这个目录里面的所有文件都可以被 Git 管理起来,每个文件的修改、删除,Git 都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。

首先,创建一个文件夹,作为目录;然后再当前目录下执行 git init 命令将这个目录变成 Git 可以管理的仓库:

1
git init

此时,当前目录下多了一个 .git 目录,这个目录是 Git 来跟踪管理版本库的,不要随便修改这个目录里面的文件。

将文件添加到版本库

在当前目录中,创建一个文件,例如 readme.txt ,内容如下:

1
2
Git is a version control system.
Git is free software.
  • 运行 git add 命令,将文件添加到仓库:
1
git add readme.txt
  • 运行 git commit 命令,将文件提交到仓库:
1
git commit -m "wrote a readme file"

此时,已经成功地添加并提交了一个 readme.txt 文件。其中,wrote a readme file 是对这个版本的 readme.txt 的描述。

版本控制

查看仓库状态的两个命令

修改 readme.txt 文件,内容如下:

1
2
Git is a distributed version control system.
Git is free software.
  • 运行 git status 可查看仓库状态:
1
2
3
4
5
6
7
8
9
$ git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
  • 运行 git diff 命令,可查看具体修改的内容:
1
2
3
4
5
6
7
8
9
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.

可以从上面的命令输出看到,我们在第一行添加了一个 distributed 单词。

接下来将 readme.txt 文件添加并提交到仓库:

  • 运行 git add 命令,将文件添加到仓库:
1
git add readme.txt
  • 运行 git status 查看仓库当前状态:
1
2
3
4
5
6
7
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: readme.txt
#

git status 告诉我们,将要被提交的修改包括 readme.txt

  • 运行 git commit 命令,将文件提交到仓库:
1
2
3
$ git commit -m "add distributed"
[master ea34578] add distributed
1 file changed, 1 insertion(+), 1 deletion(-)
  • 再次运行 git status 命令,查看仓库当前状态:
1
2
3
$ git status
# On branch master
nothing to commit (working directory clean)

此时,Git 告诉我们当前没有需要提交的修改,而且工作目录是干净(working directory clean)的。

工作区和暂存区

  • 工作区(Working Directory) 就是电脑里的目录,例如之前创建的文件夹

  • 工作区的 .git 目录实际上叫作 版本库(Repository)。Git的版本库里存了很多东西,其中最重要的就是暂存区(stage/index),还有 Git 为我们自动创建的第一个分支 master,以及指向 master 的一个指针叫 HEAD

git add 命令实际上是把工作区的文件修改添加到暂存区,git commit 命令实际上是把暂存区的所有内容提交到当前分支,如下图所示。

working_directory$repository

版本回退

先修改 readme.txt 文件如下:

1
2
Git is a distributed version control system.
Git is free software distributed under the GPL.

添加并提交:

1
2
3
4
$ git add readme.txt
$ git commit -m "append GPL"
[master 3628164] append GPL
1 file changed, 1 insertion(+), 1 deletion(-)

此时, readme.txt 文件共有三个版本被提交到 Git 仓库里了:

  • 版本1:wrote a readme file

Git is a version control system.
Git is free software.

  • 版本2:add distributed

Git is a distributed version control system.
Git is free software.

  • 版本3:append GPL

Git is a distributed version control system.
Git is free software distributed under the GPL.

运行 git log 命令可查看文件修改的历史记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git log
commit 3628164fb26d48395383f8f31179f24e0882e1e0
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 15:11:49 2013 +0800

append GPL

commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 14:53:12 2013 +0800

add distributed

commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao <askxuefeng@gmail.com>
Date: Mon Aug 19 17:51:55 2013 +0800

wrote a readme file

git log 命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是 append GPL,上一次是 add distributed,最早的一次是 wrote a readme file

如果嫌输出信息太多,看得眼花缭乱的,可以试试加上 --pretty=oneline 参数:

1
2
3
4
$ git log --pretty=oneline
3628164fb26d48395383f8f31179f24e0882e1e0 append GPL
ea34578d5496d7dd233c827ed32a8cd576c5ee85 add distributed
cb926e7ea50ad11b8f9e909c05226233bf755030 wrote a readme file

注意的是,最前面的一串数字和字母是 commit id (版本号)。

现在如果想把 readme.txt 文件退回到上一个版本,即从版本3退回到版本2,需要运行 git reset 命令:

1
2
$ git reset --hard HEAD^
HEAD is now at ea34578 add distributed

在 Git 中, HEAD 表示当前版本,上一个版本就是 HEAD^,上上一个版本就是 HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成 HEAD~100

此时,再用 git log 命令查看现在版本库的状态:

1
2
3
4
5
6
7
8
9
10
11
12
$ git log
commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 14:53:12 2013 +0800

add distributed

commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao <askxuefeng@gmail.com>
Date: Mon Aug 19 17:51:55 2013 +0800

wrote a readme file

发现版本3已经不见了。

如果再想找回版本3,可以找到版本3的 commit id ,运行:

1
2
$ git reset --hard 3628164
HEAD is now at 3628164 append GPL

版本号没必要写全,前几位就可以了,Git 会自动去找。当然也不能只写前一两位,因为 Git 可能会找到多个版本号,就无法确定是哪一个了。

运行 git reflog 命令可以查看命令记录:

1
2
3
4
5
$ git reflog
ea34578 HEAD@{0}: reset: moving to HEAD^
3628164 HEAD@{1}: commit: append GPL
ea34578 HEAD@{2}: commit: add distributed
cb926e7 HEAD@{3}: commit (initial): wrote a readme file

总结:

  • HEAD 指向的版本就是当前版本,因此,Git 允许我们在版本的历史之间穿梭,使用命令 git reset --hard commit_id

  • 穿梭前,用 git log 可以查看提交历史,以便确定要回退到哪个版本

  • 要重返未来,用 git reflog 查看命令历史,以便确定要回到未来的哪个版本。

管理修改

Git 跟踪并管理的是修改,而非文件。

我们已经知道 git add 命令是将修改的文件放入暂存区;但要注意的是, git commit 命令只负责把暂存区的修改提交到仓库。如果某次修改后没有运行 git add 命令将文件添加到暂存区,则运行 git commit 不会将此修改提交。

可以在每次修改后,运行 git add 命令,最后再运行 git commit 命令,这样可以一次性把全部修改统一提交。

撤销修改

撤销工作区的修改

运行 git checkout 命令可以撤销文件在工作区的全部修改:

1
git checkout -- readme.txt

这里也分两种情况:

  • readme.txt 自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;

  • readme.txt 已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。

总之,是让文件回到最近一次 git commitgit add 时的状态。

git checkout -- file 命令中的 -- 很重要,没有 – ,就变成了 “切换到另一个分支” 的命令。

撤销暂存区的修改

倘若修改后的文件已添加到暂存区,用 git reset HEAD 命令可以把暂存区的修改撤销,重新放回 工作区

1
git reset HEAD readme.txt

git reset 命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用 HEAD 时,表示最新的版本。

注意,此命令是把修改的文件从暂存区撤回到工作区,要想将此次修改全部撤销,再运行 git checkout 命令。

撤销已提交的修改

若修改后的文件已提交到版本库,但还没推送到远程库,想要撤销本次提交,相当于 版本回退,相关操作参考之前的章节。

删除文件

在 Git 中,删除也是一个修改操作。

可以把工作区的文件直接手动删除,也可以运行 git rm 命令进行删除:

1
git rm readme.txt

此时,用 git status 命令查看状态,Git 会告诉我们哪些文件被删除了。

注意:此时只是将文件从工作区删除,但版本库中还存留着文件。

  • 如果确实要从版本库中删除文件,那就在运行 git rm 命令后,再运行 git commit 命令提交;

  • 如果是删错了,可以运行 git checkout 命令,将误删的文件恢复到最新版本:

1
git checkout -- readme.txt

git checkout 实际上是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

如果一个文件已经被提交到版本库,那么不用担心误删,但要注意的是,只能恢复文件到最新版本,你会丢失最近一次提交后修改的内容

远程仓库

连接 Git 和 Github

Git 是分布式版本控制系统,同一个 Git 仓库,可以分布到不同的机器上。

Github 就是提供 Git 远程仓库托管服务的网站。

本地 Git 仓库和 Github 远程仓库之间的传输是通过 SSH 加密的,要实现它们之间的连接,有下面几个步骤:

  • 创建 SSH Key

    用命令行运行:

    1
    ssh-keygen -t rsa -C "youremail@example.com"

    把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,无需设置密码。

    此时可以发现,在用户主目录(C:\Users\yourusername)下生成了一个 .ssh 的目录,里面有 id_rsaid_rsa.pub 两个文件,这两个就是 SSH Key 的秘钥对,其中 id_rsa 是私钥,不能泄露出去,id_rsa.pub 是公钥。

  • 添加 SSH Key

登陆 GitHub,打开 settings -> SSH and PG Keys 页面;然后点 “New SSH Key”,填上任意Title,在 Key 文本框里粘贴 id_rsa.pub 文件的全部内容;点 “Add SSH Key”,就已成功添加。

为什么 GitHub 需要 SSH Key 呢?因为 GitHub 需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而 Git 支持 SSH 协议,所以,GitHub 只要知道了你的公钥,就可以确认只有你自己才能推送。

当然,GitHub 允许添加多个 Key。假定有若干电脑,一会儿在公司提交,一会儿在家里提交,只要把每台电脑的 Key 都添加到 GitHub,就可以在每台电脑上往 GitHub 推送了。

友情提示:在 GitHub 上免费托管的 Git 仓库,任何人都可以看到。

添加远程库并关联本地库

Github 远程仓库既可以作为备份,又可以共享。那么如何添加远程库呢?

如何添加远程库

  • 首先,在 Github 上点 “New repository” 新建一个新的仓库;

此时 Github 上的这个仓库还是空的,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后把本地仓库的内容推送到 GitHub 仓库。

  • 在本地的 Git 仓库下运行:
1
git remote add origin git@github.com:username/repo-name.git

来关联远程库。其中,username 为自己的 github 账户名,repo-name 为自己创建的仓库名。

  • git push 命令将本地库的所有内容推送到远程库上:
1
git push -u origin master

这实际上是把当前分支master推送到远程。由于远程库是空的,我们第一次推送 master 分支时,加上了 -u 参数,Git 不但会把本地的 master 分支内容推送到远程新的 master 分支,还会把本地的 master 分支和远程的 master 分支关联起来,在以后的推送或者拉取时就可以简化命令。

之后,只要本地作了提交,就可以通过命令:

1
git push origin master

将本地 master 分支的最新修改推送至 GitHub 。

SSH警告

第一次使用 Gi t的 clone 或者 push 命令连接 GitHub 时,会得到一个警告:

1
2
3
The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?

这是因为 Git 使用 SSH 连接,而 SSH 连接在第一次验证 GitHub 服务器的 Key 时,需要你确认 GitHub 的 Key 的指纹信息是否真的来自 GitHub 的服务器,输入 yes 回车即可。

Git 会输出一个警告,告诉你已经把 GitHub 的 Key 添加到本机的一个信任列表里了:

1
Warning: Permanently added 'github.com' (RSA) to the list of known hosts.

这个警告只会出现一次,后面的操作就不会有任何警告了。

从远程库克隆

在工作区目录下,用 git clone 命令可以从远程库克隆一个本地库:

1
git clone git@github.com:username/repo-name.git

git@github.com:username/repo-name.git 为 Github 远程库的地址,也可以用 https://github.com/username/repo-name.git 作为地址。实际上,Git 支持多种协议,默认的 git://使用 ssh,但也可以使用 https 等其他协议。

使用 https 除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放 http 端口的公司内部就无法使用 ssh 协议而只能用 https。

分支管理

什么是分支

在版本回退里,每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。

Git 的默认分支叫主分支,即 master 分支。 HEAD 并不是直接指向提交,而是指向当前分支,例如 master 分支,master 指向提交。

每次提交,master 分支都会向前移动一步,如此随着不断提交,master 分支的线也越来越长:

master

创建与合并分支

  • 运行 git branch <name> 命令可创建分支;
  • 运行 git checkout <name> 命令可切换分支;
  • 运行 git checkout -b <name> 命令可创建并切换分支;
  • 运行 git merge <name> 命令可合并某分支到当前分支;
  • 运行 git branch 命令可查看分支;
  • 运行 git branch -d <name> 命令可删除分支;

实战:

  • 创建并切换到 dev 分支:
1
git checkout -b dev

new branch

  • 查看当前分支:
1
2
3
git branch
* dev
master

git branch 命令会列出所有分支,当前分支前面会标一个 * 号。

  • 修改 readme.txt 文件,并提交到 dev 分支:

readme.txt 文件加上一行:

Creating a new branch is quick.

添加并提交:

1
2
3
4
git add readme.txt 
git commit -m "branch test"
[dev fec145a] branch test
1 file changed, 1 insertion(+)
  • 切换回 master 分支:
1
2
git checkout master
Switched to branch 'master'

切换回 master 分支后,再查看一个 eadme.txt 文件,刚才添加的内容不见了!因为之前的提交是在 dev 分支上,而 master 分支此刻的提交点并没有变。

dev

  • dev 分支的工作成果合并到 master 分支上:
1
2
3
4
5
git merge dev
Updating d17efd8..fec145a
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)

合并后,再查看 readme.txt 的内容,就可以看到,和 dev 分支的最新提交是完全一样的。

merge_branch

  • 合并完成后,就可以放心地删除 dev 分支了:
1
2
git branch -d dev
Deleted branch dev (was fec145a).
  • 再次查看分支:
1
2
$ git branch
* master

此时只剩下 master 分支了。

因为创建、合并和删除分支非常快,所以 Git 鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在 master 分支上工作效果是一样的,但过程更安全。

参考阅读:

廖雪峰 - Git 教程