Git教程(持续更新)

GIT (分布式版本控制系统)

简介


Git(读音为/gɪt/。)是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。
Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

什么是Git,Git可以用来干嘛?

GIt是什么?

Git是目前世界上最先进的分布式版本控制系统

Git可以用来干嘛?

如果你用Microsoft Word写过长篇大论,那你一定有这样的经历:

想删除一个段落,又怕将来想恢复找不回来怎么办?有办法,先把当前文件“另存为……”一个新的Word文件,再接着改,改到一定程度,再“另存为……”一个新文件,这样一直改下去,最后你的Word文档就“魂飞魄散”了!

过了一周,你想找回被删除的文字,但是已经记不清删除前保存在哪个文件里了,只好一个一个文件去找,真麻烦。

看着一堆乱七八糟的文件,想保留最新的一个,然后把其他的删掉,又怕哪天会用上,还不敢删,真郁闷。

更要命的是,有些部分需要你的财务同事帮助填写,于是你把文件Copy到U盘里给她(也可能通过Email发送一份给她),然后,你继续修改Word文件。一天后,同事再把Word文件传给你,此时,你必须想想,发给她之后到你收到她的文件期间,你作了哪些改动,得把你的改动和她的部分合并,真困难。

于是你想,如果有一个软件,不但能自动帮我记录每次文件的改动,还可以让同事协作编辑,这样就不用自己管理一堆类似的文件了,也不需要把文件传来传去。如果想查看某次改动,只需要在软件里瞄一眼就可以,岂不是很方便?

这个软件用起来就应该像这个样子,能记录每次文件的改动:

版本用户说明日期
1张三删除了软件服务条款57/12 10:38
2张三增加了License人数限制7/12 18:09
3李四财务部门调整了合同金额7/13 9:51
4张三延长了免费升级周期7/14 15:17

Git与SVN的区别

GIT不仅仅是个版本控制系统,它也是个内容管理系统(CMS),工作管理系统等。
如果你是一个具有使用SVN背景的人,你需要做一定的思想转换,来适应GIT提供的一些概念和特征。
Git 与 SVN 区别点:

  • GIT是分布式的,SVN不是:这是GIT和其它非分布式的版本控制系统,例如SVN,CVS等,最核心的区别。
  • GIT把内容按元数据方式存储,而SVN是按文件:所有的资源控制系统都是把文件的元信息隐藏在一个类似.svn,.cvs等的文件夹里。
  • GIT分支和SVN的分支不同:分支在SVN中一点不特别,就是版本库中的另外的一个目录。
  • GIT没有一个全局的版本号,而SVN有:目前为止这是跟SVN相比GIT缺少的最大的一个特征。
  • GIT的内容完整性要优于SVN:GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

安装Git

最早Git是在Linux上开发的,很长一段时间内,Git也只能在Linux和Unix系统上跑。不过,慢慢地有人把它移植到了Windows上。现在,Git可以在Linux、Unix、Mac和Windows这几大平台上正常运行了。
Git 各平台安装包下载地址为:http://git-scm.com/downloads

在Linux平台安装Git

Git 的工作需要调用 curl,zlib,openssl,expat,libiconv 等库的代码,所以需要先安装这些依赖工具。
在有 yum 的系统上(比如 Fedora)或者有 apt-get 的系统上(比如 Debian 体系),可以用下面的命令安装:
各 Linux 系统可以使用其安装包管理工具(apt-get、yum 等)进行安装:

Debian/Ubuntu

Debian/Ubuntu Git 安装命令为:

1
2
3
4
5
6
7
$ apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \
libz-dev libssl-dev

$ apt-get install git

$ git --version
git version 1.8.1.2

Centos/RedHat

如果你使用的系统是 Centos/RedHat 安装命令为:

1
2
3
4
5
6
7
$ yum install curl-devel expat-devel gettext-devel \
openssl-devel zlib-devel

$ yum -y install git-core

$ git --version
git version 1.7.1

在Mac OS X上安装Git

如果你正在使用Mac做开发,有两种安装Git的方法。

一是安装homebrew,然后通过homebrew安装Git,具体方法请参考homebrew的文档:http://brew.sh/

第二种方法更简单,也是推荐的方法,就是直接从AppStore安装Xcode,Xcode集成了Git,不过默认没有安装,你需要运行Xcode,选择菜单“Xcode”->“Preferences”,在弹出窗口中找到“Downloads”,选择“Command Line Tools”,点“Install”就可以完成安装了。

在Windows上安装Git

在Windows上使用Git,可以从Git官网直接下载安装程序,(网速慢的同学请移步国内镜像),然后按默认选项安装即可。

安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功!

Git配置

Git 提供了一个叫做 git config 的工具,专门用来配置或读取相应的工作环境变量。
这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方:

  • /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 –system 选项,读写的就是这个文件。
  • ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 –global 选项,读写的就是这个文件。
  • 当前项目的 Git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。

在 Windows 系统上,Git 会找寻用户主目录下的 .gitconfig 文件。主目录即 $HOME 变量指定的目录,一般都是 C:\Documents and Settings\$USER。
此外,Git 还会尝试找寻 /etc/gitconfig 文件,只不过看当初 Git 装在什么目录,就以此作为根目录来定位。

用户信息

配置个人的用户名称和电子邮件地址:

1
2
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。

如果用了 –global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。
如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 –global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。

注意: 用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。

查看配置信息

要检查已有的配置信息,可以使用 git config –list 命令:

1
2
3
4
$ git config --list
credential.helper=manager
user.name=ProgramerHui
user.email=1712817197@qq.com

Git工作流程

一般工作流程如下:

  • 克隆 Git 资源作为工作目录。
  • 在克隆的资源上添加或修改文件。
  • 如果其他人修改了,你可以更新资源。
  • 在提交前查看修改。
  • 提交修改。
  • 在修改完成后,如果发现错误,可以撤回提交并再次修改并提交。

Git工作区、暂存区和版本库

基本概念

  • 工作区:就是你在电脑里能看到的目录。
  • 暂存区:英文叫stage, 或index。一般存放在 “.git目录下” 下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
  • 版本库:工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。

下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

图中左侧为工作区,右侧为版本库。在版本库中标记为 “index” 的区域是暂存区(stage, index),标记为 “master” 的是 master 分支所代表的目录树。
图中我们可以看出此时 “HEAD” 实际是指向 master 分支的一个”游标”。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
图中的 objects 标识的区域为 Git 的对象库,实际位于 “.git/objects” 目录下,里面包含了创建的各种对象及内容。
当对工作区修改(或新增)的文件执行 “git add” 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
当执行 “git reset HEAD” 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
当执行 “git rm –cached“ 命令时,会直接从暂存区删除文件,工作区则不做出改变。
当执行 “git checkout .” 或者 “git checkout –“ 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动。
当执行 “git checkout HEAD .” 或者 “git checkout HEAD“ 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。

Git创建版本库(仓库)

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

所以,创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录:

1
2
3
4
5
$ pwd
/c/Users/Administrator
$ cd E:
$ mkdir Git
$ cd Git

pwd命令用于显示当前目录,cd命令用于切换目录,mkdir用于创建文件夹,在我的Windows上,这个仓库位于E:/Git.

注意:文件夹应避免使用中文,防止乱码。

让文件夹成为真正的仓库

上面步骤只是创建了一个文件夹,严格意义上并不算是一个Git版本库,so,现在,我们让这个文件夹变成一个真正的仓库。

输入git init命令把这个目录变成Git可管理的仓库

1
2
$ git init
Initialized empty Git repository in E:/Git/.git/

瞬间Git就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),细心的读者可以发现当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。

如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看见。

把文件添加到版本库

首先这里再明确一下,所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。

不幸的是,Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,前面我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件。
建议:编辑文本最好使用Notepad++代替记事本,因为微软自带记事本会在每个文件开头添加0xefbbbf(十六进制)的字符。

现在,我们编写一个readme.txt文件,内容如下:

1
Hello,Git!

注意:文件应该放在Git文件夹里,因为这是一个Git仓库,放到其他地方Git会找不到这个文件。

Git add

通过git add告诉Git,把文件添加到仓库(暂存区):

1
$ git add readme.txt

执行上面的命令,没有任何显示,代表执行成功。

Git commit

通过git commit告诉Git,把文件提交到仓库(版本库):

1
2
3
4
$ git commit -m "wrote a readme file"
[master (root-commit) eacdf4e] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt

其中:git commit -m "wrote a readme file"中的-m "xxx"代表本次提交的说明。
例如:
$ git commit -m "本次提交解决了若干个BUG"

当然,提交说明也可以带表情,具体表情参考Github表情提交指南

Git 基本操作

获取和创建项目命令

git init

此命令在创建版本库的时候就讲过,git init命令就是将一个文件夹初始化为一个Git仓库,创建成功后在该文件夹下会生成一个.git的文件夹.
例如:

1
2
$ git init
Initialized empty Git repository in E:/Git/.git/

现在你能看见该仓库中的.git文件了。

1
2
$ ls -a
. .. .git

注意:这里的ls -a中的ls就是列表,清单的意思,在终端就是显示当前目录的所有文件,而-a的意思是显示隐藏文件。

git clone

使用 git clone 拷贝一个 Git 仓库到本地,让自己能够查看该项目,或者进行修改。

1
$ git clone [url]

url是你想克隆(复制)的网上仓库。
例如:我们克隆Github上的项目

1
2
3
4
5
6
7
$ git clone git@github.com:HuiProgramer/HTML5_Learning.git
Cloning into 'HTML5_Learning'...
remote: Counting objects: 26, done.
remote: Total 26 (delta 0), reused 0 (delta 0), pack-reused 26
Receiving objects: 100% (26/26), done.
Resolving deltas: 100% (5/5), done.
Checking connectivity... done.

克隆完成后,在当前目录下会生成一个 HTML5_Learning 目录:

1
2
3
4
$ cd HTML5_Learning
& ls
CSS/ index.html logo.png Screen1.png
img/ JavaScript/ README.MD Screen2.png

至此,一个Github上的项目就克隆完成了,你可以尝试修改或者查看。

常用命令

git add

此命令前面就讲过了,这里再举一个例子,这次我们往暂存区添加多个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ touch hello.py hello.c hello.java hello.html

$ ls
hello.py hello.c hello.java hello.html
$ git add hello.c hello.java hello.html hello.py

$ git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: hello.c
new file: hello.html
new file: hello.java
new file: hello.py

我们分别向暂存区提交了多个语言hello world程序源文件,touch代表我们新建这几个文件,git status的意思是查看当前暂存区的状态。
扩展: 通过git add .可以将该文件夹的东西都添加到暂存区。

git status

git status用于查看在你上次提交之后是否有修改,便于查看当前暂存区的状态,通过增加-s参数可以获得简短的输出结果,如果没有加该参数将会得到详细的输出。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: hello.c
new file: hello.html
new file: hello.java
new file: hello.py

$ git status -s
A hello.c
A hello.html
A hello.java
A hello.py

补充说明:A的意思是文件添加到了暂存区,而AM的意思是文件添加到暂存区后在工作区又有所改动。

git diff

执行 git diff 来查看执行 git status 的结果的详细信息。
git diff 命令显示已写入缓存与已修改但尚未写入缓存的改动的区别。git diff 有如下几个主要的应用场景。

  • 尚未缓存的改动(工作区):git diff
  • 查看已缓存的改动(暂存区): git diff –cached
  • 查看已缓存的与未缓存的所有改动(工作区与暂存区):git diff HEAD
  • 显示摘要而非整个 diff:git diff –stat

例如我们利用notepad++编辑器修改hello.c文件内容如下:

1
2
3
4
5
#include<stdio.h>
int main(void){
printf("hello,world");
return 0;
}

现在我们使用git diff file命令来看看结果:

1
2
3
4
5
6
7
8
9
10
11
12
$ git diff hello.c
diff --git a/hello.c b/hello.c
index e69de29..fb0c3bd 100644
--- a/hello.c
+++ b/hello.c
@@ -0,0 +1,5 @@
+#include<stdio.h>
+int main(void){
+ printf("hello,world");
+ return 0;
+}
\ No newline at end of file

果然,git将整个文件的变化都列了出来。
扩展:diff就是单词different的缩写,其原意是“区别,不同”。

git commit

此命令在讲述创建版本库的时候就介绍过,主要用于将暂存区的东西提交到版本库中。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git status -s
AM hello.c
A hello.html
A hello.java
A hello.py

$ git commit -m"第一次提交"
[master (root-commit) ac81931] 第一次提交
4 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 hello.c
create mode 100644 hello.html
create mode 100644 hello.java
create mode 100644 hello.py

我们将暂存区的内容提交到版本库后再次执行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: hello.c

no changes added to commit (use "git add" and/or "git commit -a")

此时,我们发现,暂存区的内容已经全部提交到版本库中,只有一条工作区内容被修改的记录。

git reset HEAD

git reset HEAD file 命令用于取消已缓存(暂存区)的内容。
可以理解为:撤销暂存区的修改。可单个撤销,也可以全部撤销。
这里,我们先提交hello.c源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ 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: hello.c

no changes added to commit (use "git add" and/or "git commit -a")

$ git add hello.c

$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: hello.c

好的,现在hello.c源文件已经进入了暂存区了,那么,如果我不想提交该内容,该怎么解决呢?其实,通过git reset HEAD命令是可以丢弃掉暂存区里的内容的。
现在,我们来试试吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git reset HEAD
Unstaged changes after reset:
M hello.c

$ 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: hello.c

no changes added to commit (use "git add" and/or "git commit -a")

果然,暂存区的内容被撤销掉了。
要点:git reset HEAD filename实现单个或多个文件在暂存区中撤销,git reset HEAD实现整个暂存区的撤销。
注意:如果你提交暂存区后,在工作区又进行了修改,git reset HEAD命令并不会让你在工作区的内容回退到上一次提交的数据,该只是单纯的丢弃掉暂存区里的内容。

git checkout – file

git checkout --file命令用于取消未缓存的内容(工作区)。
可以理解为:丢弃工作区的修改,回到上次修改的样子。
那么,我们现在来试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ 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: hello.c

no changes added to commit (use "git add" and/or "git commit -a")

$ cat hello.c
#include<stdio.h>
int main(void){
printf("hello,world");
return 0;
}

$ git checkout -- hello.c

$ cat hello.c

在这里,我们使用git status命令可以看到该文件只是在工作区进行了修改,接着我们使用了cat命令查看了文件内容,发现里面是C语言源代码,然后我们使用了git checkout -- hello.c命令丢弃了本次修改。等我们再次使用cat命令查看hello.c内容时发现里面已经没有内容了,这是为什么呢?
原来这里的git checkout -- <file>命令的结果分为了两种情况:

  • 当文件在工作区修改后还没有被添加到暂存区,此时使用该命令,会使工作区回到和版本库一模一样的状态。
  • 当文件在工作区修改后被添加到了暂存区,此时使用该命令,会使工作区回到把文件添加到暂存区后的状态

那么,这里就是第一种状态了,因为我们并没有提交到暂存区,而版本库的hello.c里面恰恰是没有内容的。(因为当时并没有写内容)

git rm

如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 Changes not staged for commit 的提示。
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除,然后提交。可以用以下命令完成此项工作

1
$ rm <file>

如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f

1
$ rm -f <file>

如果把文件从暂存区域移除,但仍然希望保留在当前工作目录中,换句话说,仅是从跟踪清单中删除,使用 –cached 选项即可

1
$ git rm --cached <file>

我们先在hello.py增加如下代码

1
print("hello")

然后,我们将文件添加到暂存区后,在暂存区中删除hello.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ git add hello.py
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: hello.py

$ git rm --cached hello.py
rm 'hello.py'

$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

deleted: hello.py

Untracked files:
(use "git add <file>..." to include in what will be committed)

hello.py

可以看到,我们在暂存区移除了该文件,现在,我们将工作区的文件也移除掉。

1
2
3
4
$ rm hello.py
rm 'hello.py'
$ ls
hello.c hello.html hello.java

至此,基本命令到此结束。

Git查看提交历史

git log

在使用 Git 提交了若干更新之后,又或者克隆了某个项目,想回顾下提交历史,我们可以使用 git log 命令查看。

1
2
3
4
5
6
$ git log
commit ac81931794f4e232398796f5d4e09be155fc5bc2 (master)
Author: ProgramerHui <1712817197@qq.com>
Date: Fri Dec 28 19:17:51 2018 +0800

第一次提交

git log –oneline

git log --oneline 命令用来查看历史记录的简洁的版本。

1
2
$ git log --oneline
ac81931 (master) 第一次提交

git log –graph

git log --graph 命令,用于查看历史中什么时候出现了分支、合并。以下为相同的命令,开启了拓扑图选项:

1
2
3
4
5
6
$ git log --graph
* commit ac81931794f4e232398796f5d4e09be155fc5bc2 (master)
Author: ProgramerHui <1712817197@qq.com>
Date: Fri Dec 28 19:17:51 2018 +0800

第一次提交

git log –author

git log --author命令用于查看指定用户的提交日志。
示例如下:

1
2
3
4
5
6
$ git log --author=ProgramerHui
commit ac81931794f4e232398796f5d4e09be155fc5bc2 (master)
Author: ProgramerHui <1712817197@qq.com>
Date: Fri Dec 28 19:17:51 2018 +0800

第一次提交

Git版本回退

早在前面就说过Git能干什么的特性.
你可以理解为Git就是一个时空穿梭机,能够回退版本和前进版本。

现在,我们来修改一下readme.txt的内容。

1
2
3
4
5
6
7
$ cat readme.txt
hello,Git

$ echo "HuiProgramer is handsome!" >> readme.txt

$ cat readme.txt
HuiProgramer is handsome!

扩展:echo " " >> file是一个批处理命令,用于清空文件内容后,将” “里的内容写入到文件中。
我们尝试提交一下

1
2
3
4
5
6
$ git add readme.txt

$ git commit -m"handsome"
[master dd18d87] handsome
1 file changed, 1 insertion(+)
create mode 100644 readme.txt

我们现在来回忆一下我们一共提交了哪几个版本的readme.txt文件。
版本一:

hello,Git

版本二:

HuiProgramer is handsome

记不住也没事,我们可以通过git log命令来查看:

1
2
3
4
5
6
7
8
9
10
11
12
$ git log
commit dd18d87073e5d9a593dbfe3a89072e9f785431ad (HEAD -> master)
Author: ProgramerHui <1712817197@qq.com>
Date: Sun Jan 6 12:07:01 2019 +0800

handsome

commit ac81931794f4e232398796f5d4e09be155fc5bc2
Author: ProgramerHui <1712817197@qq.com>
Date: Fri Dec 28 19:17:51 2018 +0800

wrote a readme file

可以看到第一次提交就是开始介绍GIt仓库时写的readme.txt,里面的内容是hello,Git.
而最近一次提交就是我们重新修改了readme.txt后提交的,里面的内容是HuiProgramer is handsome(表脸).

那么,如果我想要回到上一个版本该怎么办呢?
其实很简单只需要执行git reset --hard HEAD^就行了,其中^代表上一个版本,^^代表上上个版本,以此类推。

1
2
$ git reset --hard HEAD^
HEAD is now at ac81931 wrote a readme file

Look,我们已经回到了上一个版本了,现在我们打开readme.txt文件看看。

hello,Git

哇,果然是这样,但我又如何回去呢?就像我们现在坐着时空穿梭机回到了过去,应该怎么回到现在呢?
其实很简单,我们可以通过上次执行git log命令拿到的commit后面的版本号再次穿梭就可以回来了。

1
2
$ git reset --hard dd18
HEAD is now at dd18d87 handsome

注意:版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
再次查看readme.txt的内容

HuiProgramer is handsome

嗯,不错,内容全回来了,还是一如既往的表脸。

扩展:假如我关闭了git bash这个终端,然后又回退了版本,输入git log又不显示,该怎么办呢?其实很简单,你只要输入git reflog命令就能再次看到你的提交记录了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ git reflog
dd18d87 (HEAD -> master) HEAD@{0}: reset: moving to dd18
ac81931 HEAD@{1}: reset: moving to HEAD^
dd18d87 (HEAD -> master) HEAD@{2}: commit: handsome
ac81931 HEAD@{3}: checkout: moving from Python to master
af26cd8 (Python) HEAD@{4}: checkout: moving from master to Python
ac81931 HEAD@{5}: checkout: moving from Python to master
af26cd8 (Python) HEAD@{6}: commit: test branch
ac81931 HEAD@{7}: checkout: moving from master to Python
ac81931 HEAD@{8}: checkout: moving from Python to master
ac81931 HEAD@{9}: checkout: moving from master to Python
ac81931 HEAD@{10}: checkout: moving from Python to master
ac81931 HEAD@{11}: checkout: moving from master to Python
ac81931 HEAD@{12}: checkout: moving from Python to master
ac81931 HEAD@{13}: checkout: moving from master to Python
ac81931 HEAD@{14}: reset: moving to HEAD
ac81931 HEAD@{15}: reset: moving to HEAD
ac81931 HEAD@{16}: reset: moving to HEAD^
5a56f73 HEAD@{17}: commit: hello world
ac81931 HEAD@{18}: commit (initial): wrote a readme file

Git与Github

什么是Github

GitHub是一个面向开源及私有软件项目的托管平台,因为只支持git 作为唯一的版本库格式进行托管,故名gitHub。
简单来说就是一个网上的仓库,而Git就是本地的仓库。

拥有自己的Github

注册Github账号

点击这里进行注册。

注册完成后,登录…

创建自己的项目(仓库/版本库)

点击+号进行创建

填写项目名字,点击完成即可。

完成后,会得到项目的提交地址。

创建SSH Key

打开Git Bash终端,输入:

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

你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。

如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsaid_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

绑定SSH Key

进入Github,点击头像,点击Settings

点击SSH and GPG Keys

点击New SSH key

可以看到此页面

Title可以随便取一个,Key可以去C:\Users\Administrator\.ssh下找到一个id_rsa.pub的文件,双击打开后复制里面的全部内容粘贴到Key下的框框里,然后点击Add SSH Key.

创建完成后就能看到这样的页面…

提交项目到Github

git remote

通过git remote add origin git@github.com:YourName/RepositoryName.git即可建立一个与当前分支关联的提交名字。

1
$ git remote add Golang git@github.com:HuiProgramer/Golang_Learning.git

解析:YourName为你的Github账户名,RepositoryName为你创建的项目名。
注意:这里的origin可以任你更改,前提你得记住,后面提交要用到。

git push

通过git push -u origin master命令可将本地项目推送到Github的仓库中。

1
2
3
4
5
6
7
8
9
10
$ git push -u Golang master
Counting objects: 9, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), 916 bytes | 152.00 KiB/s, done.
Total 9 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To github.com:HuiProgramer/Golang_Learning.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'Golang'.

Look,我们已经成功提交本地版本库到网上仓库了。

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

现在,我们打开我们的远程仓库看看,是否提交成功。

嗯,不错,成功了,以后就这样提交。

git pull

通过git pull origin master可将网上仓库拉取到本地来完成同步。

1
2
3
4
$ git pull Golang master
From github.com:HuiProgramer/Golang_Learning
* branch master -> FETCH_HEAD
Already up to date.

现在就会看到,本地项目和网上仓库一模一样了。

Git分支管理

几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。
有人把 Git 的分支模型称为”必杀技特性”,而正是因为它,将 Git 从版本控制系统家族里区分出来。

分支的创建和切换

创建分支命令:

1
$ git branch (branchname)

切换分支命令:

1
git checkout (branchname)

例如:

1
2
3
4
5
$ git branch Python

$ git checkout Python
Switched to branch 'Python'
D hello.py

扩展:git checkout -b (branchname)命令可以创建并切换新分支,从而实现在该分支中的操作。

分支的合并

Git Merge

在 Git 中合并两个分支时会产生一个特殊的提交记录,它有两个父结点。翻译成自然语言相当于:“我要把这两个父结点本身及它们所有的祖先都包含进来。”
merge合并分支命令:

1
git merge (branchname)

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ git checkout -b fixbug  //创建并切换fixbug分支
Switched to a new branch 'fixbug'
D test.txt

$ git add .
warning: LF will be replaced by CRLF in hello.go.
The file will have its original line endings in your working directory.

$ git commit -m "merge branch bugfix" //在分支上提交
[fixbug dbed58a] merge branch bugfix
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 hello.go
delete mode 100644 test.txt

$ git checkout master //切换回主分支
Switched to branch 'master'
Your branch is up to date with 'Golang/master'.

$ git merge fixbug //合并分支
Updating 8d7e87d..dbed58a
Fast-forward
hello.go | 1 +
test.txt | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 hello.go
delete mode 100644 test.txt

注意:这里git merge fixbug 是把该分支fixbug合并到当前分支(master)
对于前两步也同样可以使用git checkout -b fixbug一步完成。

Git Rebase

第二种合并分支的方法是 git rebase。Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。

Rebase 的优势就是可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。

rebase合并分支命令:

1
git rebase (branchname)

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ git checkout -b fixbug  //创建并切换fixbug分支
Switched to a new branch 'fixbug'
D test.txt

$ git add .
warning: LF will be replaced by CRLF in hello.go.
The file will have its original line endings in your working directory.

$ git commit -m "rebase branch bugfix" //在分支上提交
[fixbug dbed58a] merge branch bugfix
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 hello.go
delete mode 100644 test.txt

$ git checkout master //切换回主分支
Switched to branch 'master'
Your branch is up to date with 'Golang/master'.

$ git rebase fixbug //合并分支
Updating 8d7e87d..dbed58a
Fast-forward
hello.go | 1 +
test.txt | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 hello.go
delete mode 100644 test.txt

merge和rebase的区别

两个使用场景是不一样的,merge只是合并另外一个分支的内容,rebase也合并另外一个分支的内容,但是会把本分支的commits顶到最顶端

假设我们现在有3个分支

  • master分支:线上环境使用的分支
  • testing分支:测试环境使用的分支
  • my_feature分支:开发新功能的分支,也就是当前分支

A. 假设我在my_feature上开发了一段时间,之后另外的同事开发的功能正式上线到master分支了,那么我可以在当前的分支下rebase一下master分支,这样我这个分支的几个commits相对于master还是处于最顶端的,也就是说rebase主要用来跟上游同步,同时把自己的修改顶到最上面

B. 我在my_feature上开发了一段时间了,想要放到testing分支上,那就切到testing,然后merge my_feature进来,因为是个测试分支,commits的顺序无所谓,也就没必要用rebase (当然你也可以用rebase)

另外,单独使用rebase,还有调整当前分支上commits的功能(合并,丢弃,修改commites msg)

建议
推荐大家开发的时候,尽量及时rebase上游分支(我习惯是每周merge一次),有冲突提前就fix掉,即使我们自己的分支开发了很久(哪怕是几个月),也不会积累太多的conflict,最后合并进主分支的时候特别轻松, 非常反对从master check出新分支,自己闷头开发几个月,结果最后merge进主分支的时候,一大堆冲突,自己还嗷嗷叫的行为 。

  • 本文作者: 在线分享网
  • 本文链接: https://me.obey.fun/Git教程.html
  • 版权声明: 本博客所有文章除特别声明外,均为原创。转载请注明出处!
-------------本文结束 感谢您的阅读-------------
文章对我有益,我要小额赞赏...