写给程序员的 Git 使用指南

初学者强烈推荐廖雪峰老师的 Git 系列教程,通俗易懂,点击此处即可开始学习

前言

我们为什么要从命令行学起?

想象一个场景,假设你在一台 CentOS7 的服务器上要使用 Git 拉取代码你怎么操作?显而易见,你是需要通过 Git 命令来拉取代码的,一方面使用 git 命令执行的效率很高,另外一方面通过 git 命令我们可以更深刻理解 git 的原理。

当我们学会了命令之后,我们再去学习各种 GUI 界面化的 Git 工具就很 easy 了,只要稍微花点时间熟悉下就可以了,反过来就没那么容易了。

1.安装Git

2.配置全局用户Name和E-mail

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

如果想为某个仓库单独配置用户名和 email 也是可以的,去掉 –global 参数即可:

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

一般我们工作中,配置的是工作的用户名和企业邮箱,这样也显得专业一些,方便同事们看提交记录。

3.初始化仓库

1
git init

4.添加文件到Git仓库

1
git add <file>

提示:可反复多次使用,添加多个文件;

5.提交添加的文件到Git仓库

1
git commit

然后会弹出一个Vim编辑器输入本次提交的内容;

或者

1
git commit -m "提交说明"

6.查看仓库当前的状态

1
git status

7.比较当前文件的修改

1
$ git diff <file>

8.查看历史提交记录

1
git log

如果加上参数查看就比较清晰了,git log 的常用选项:

选项 说明
-p 按补丁格式显示每个更新之间的差异。
–stat 显示每次更新的文件修改统计信息。
–shortstat 只显示 –stat 中最后的行数修改添加移除统计。
–name-only 仅在提交信息后显示已修改的文件清单。
–name-status 显示新增、修改、删除的文件清单。
–abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
–relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。
–graph 显示 ASCII 图形表示的分支合并历史。
–pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。
选项 说明
-(n) 仅显示最近的 n 条提交
–since, –after 仅显示指定时间之后的提交。
–until, –before 仅显示指定时间之前的提交。
–author 仅显示指定作者相关的提交。
–committer 仅显示指定提交者相关的提交。
–grep 仅显示含指定关键字的提交
-S 仅显示添加或移除了某个关键字的提交
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ git log --pretty=oneline

# git命令都可以通过git manual查看
git log --help

# 可以看到该文件相关的commit记录
git log -- filename

# 可以显示该文件每次提交的 diff
git log -p filename

# 查看某次提交中的某个文件变化
git show commit-id filename

# 根据 commit-id 查看某个提交
git show commit-id

# 以图形化的界面显示文件修改列表
gitk --follow filename
  

9.回退版本

1
$ git reset --hard HEAD^

说明:在Git中,用HEAD表示当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^,以此类推,如果需要回退几十个版本,写几十个^容易数不过来,所以可以写,例如回退30个版本为:HEAD~30。

如果你回退完版本又后悔了,想回来,一般情况下是回不来的,但是如果你可以找到你之前的commit id的话,也是可以的,使用如下即可:

1
$ git reset --hard + commit id 

提示:commit id不需要写全,Git会自动查找;

补充说明:Git中,commit id是一个使用SHA1计算出来的一个非常大的数字,用十六进制表示,你提交时看到的一大串类似3628164…882e1e0的就是commit id(版本号);

在Git中,版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向回退的版本,然后顺便刷新工作区文件;

Git revert 使用:

1
git revert e7c8599d29b61579ef31789309b4e691d6d3a83f

现在查看log,发现多了一次commit,其内容就是回到了原来的那个阶段

10.查看操作的历史命令记录

1
$ git reflog

结果会将你之前的操作的commit id和具体的操作类型及相关的信息打印出来,这个命令还有一个作用就是,当你过了几天,你想回退之前的某次提交,但是你不知道commit id了,通过这个你可查找出commit id,就可以轻松回退了,用一句话总结:穿越未来,回到过去,so easy!

11.diff文件

1
git diff HEAD -- <file>

说明:查看工作区和版本库里面最新版本文件的区别,也可以不加HEAD参数;

12.丢弃工作区的修改

1
$ git checkout -- <file>

说明:适用于工作区修改没有add的文件

13.丢弃暂存区的文件

1
$ git reset HEAD <file>

说明:适用于暂存区已经add的文件,注意执行完此命令,他会将暂存区的修改放回到工作区中,如果要想工作区的修改也丢弃,就执行第12条命令即可;

14.删除文件

1
$ rm <file>

然后提交即可;

如果不小心删错了,如果还没有提交的话使用下面命令即可恢复删除,注意的是它只能恢复最近版本提交的修改,你工作区的修改是不能被恢复的!

1
$ git checkout -- <file>
1
2
3
4
5
6
7
8
9
10
11
12
13
# 删除 untracked files
git clean -f

# 连 untracked 的目录也一起删掉
git clean -fd

# 连 gitignore 的 untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o .apk 之类的文件用的)
git clean -xfd

# 在用上述 git clean 前,墙裂建议加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删
git clean -nxfd
git clean -nf
git clean -nfd

丢弃本地修改的所有文件(新增、删除、修改),比如,本地修改了许多文件,其中有些是新增的,因为开发需要这些都不要了,想要丢弃掉,可以使用如下命令来删除:

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
# 本地所有修改且没有的提交的,都返回到原来的状态
git checkout .

# 返回到某个节点,不保留修改,已有的改动会丢失
git reset --hard HASH

# 返回到某个节点, 保留修改,已有的改动会保留,在未提交中,git status 或 git diff 可看
git reset --soft HASH

# 返回到某个节点,(未跟踪文件的删除)
git clean -df

git clean 参数
-n 不实际删除,只是进行演练,展示将要进行的操作,有哪些文件将要被删除。(可先使用该命令参数,然后再决定是否执行)
-f 删除文件
-i 显示将要删除的文件
-d 递归删除目录及文件(未跟踪的)
-q 仅显示错误,成功删除的文件不显示

注:
git reset 删除的是已跟踪的文件,将已 commit 的回退
git clean 删除的是未跟踪的文件

git clean -nxdf(查看要删除的文件及目录,确认无误后再使用下面的命令进行删除)
git checkout . && git clean -xdf

删除符合条件的所有文件:

1
2
3
4
5
# 删除 log/ 目录下扩展名为 .log 的所有文件
git rm log/\*.log

# 删除以 ~ 结尾的所有文件
git rm \*~

15.创建SSH key

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

一般本地Git仓库和远程Git仓库之间的传输是通过SSH加密的,所以我们可以将其生成的公钥添加到Git服务端的设置中即可,这样Git就可以知道是你提交的了;

16.与远程仓库协作

1
$ git remote add origin git@github.com:xinpengfei520/IM.git

删除本地库与远程库的关联:

1
$ git remote rm origin

作用:有时候我们需要关联其他远程库,需要先删除旧的关联,再添加新的关联,因为如果你已经关联过了就不能在关联了,不过想关联多个远程库也是可以的,前提是你的本地库没有关联任何远程库,操作如下:

先关联 Github 远程库:

1
$ git remote add github git@github.com:xinpengfei520/IM.git

接着关联码云远程库:

1
$ git remote add gitee git@gitee.com:xinpengfei521/IM.git

现在,我们用 git remote -v 查看远程库的关联信息,如果看到两组关联信息就说明关联成功了;

ok,现在我们的本地库可以和多个远程库协作了

如果要推送到 GitHub,使用命令:

1
$ git push github master

如果要推送到码云,使用命令:

1
$ git push gitee master

一次提交到所有远程仓库:

1
git push --all
注意:
1
2
git pull 是 git pull (from) origin (to) master
git push 是 git push (to) origin (from) master

17.推送到远程仓库

1
$ git push -u origin master

注意:第一次提交需要加一个参数-u,以后不需要

推送master到远程库

1
$ git push origin master

推送branch1到远程库

1
$ git push origin branch1

推送分支时需要注意,你在那个分支时就推那个分支!例如,不可以在 master 分支推到其他分支上去!!!

18.克隆一个远程库

1
$ git clone git@github.com:xinpengfei520/IM.git

19.Git分支管理

创建一个分支 branch1

1
$ git branch branch1

切换到branch1分支:

1
$ git checkout branch1

创建并切换到 branch1 分支:

1
$ git checkout -b branch1

查看分支:

1
$ git branch

提示:显示的结果中,其中有一个分支前有个 * 号,表示的是当前所在的分支;

合并 branch1 分支到 master:

1
$ git merge branch1

删除分支:

1
2
3
4
5
6
7
8
# 删除本地分支
git branch -d branch1

# 删除远程分支
git push origin --delete <branchName>

# 删除掉没有与远程分支对应的本地分支
git fetch -p

拉取一个远程的分支的同时并创建一个本地分支:

1
$ git fetch origin branch:branch

20.查看提交的历史记录

1
$ git log

命令可以看到分支合并图

1
git log --graph

21.合并分支

禁用 Fast forward 模式合并分支

1
$ git merge --no-ff -m "merge" branch1

说明:默认 Git 合并分支时使用的是Fast forward模式,这种模式合并,删除分支后,会丢掉分支信息,所以我们需要强制禁用此模式来合并;

补充内容:实际开发中分支管理的策略

  • master 分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面提交;
  • 我们可以新开一个 dev 分支,也就是说 dev 分支是不稳定的,到版本发布时,再把 dev 分支合并到 master 上,在 master 分支发布新版本;
  • 你和你的协作者平时都在 dev 分支上提交,每个人都有自己的分支,时不时地往 dev 分支上合并就可以了;

22.保存工作现场

1
$ git stash

作用:当你需要去修改其他内容时,这时候你的工作还没有做完,先临时保存起来,等干完其他事之后,再回来回复现场,再继续干活;为什么?因为暂存区是公用的,如果不通过 stash 命令隐藏,会带到其它分支去;

查看已经保存的工作现场列表:

1
$ git stash list

恢复工作现场(恢复并从stash list删除):

1
$ git stash pop

或者:

1
git stash apply

恢复工作现场,但 stash 内容并不删除,如果你需要删除执行如下命令:

1
$ git stash drop

恢复指定的 stash:

1
$ git stash apply stash@{0}

说明:其中 stash@{0}git stash list 中的一种编号

23.丢弃一个没有被合并过的分支

强行删除即可:

1
$ git branch -D <name>

作用:实际开发中,添加一个新 feature,最好新建一个分支,如果要丢弃这个没有被合并过的分支,可以通过上面的命令强行删除;

要把两个不同的项目合并,git 需要添加一句代码,在 git pull,这句代码是在 git 2.9.2 版本发生的,最新的版本需要添加 –allow-unrelated-histories

24.查看远程库的信息

1
$ git remote

显示更详细的信息:

1
$ git remote -v

25.拉取分支

拉取 master 到本地

1
$ git pull origin master

拉取 branch1 分支的内容到本地

1
$ git pull origin branch1

拉取时要注意本地已经有和远程分支对应了,如果没有的话,参考 19 中,拉取并创建本地分支,拉取时需要注意,你在那个分支时就拉取个分支!不可以在 master 分支拉取其他分支下来,这样会把远程的其他分支拉取下来和本地 master 分支合并!!!引起不必要的麻烦!

如果不小心拉取下来了,就可以使用下面的命令,恢复到上一次提交:

1
$ git reset HEAD^

26.创建本地分支

1
$ git checkout -b branch1 origin/branch1

说明:如果远程库中有分支,clone 之后默认只有 master 分支的,所以需要执行如上命令来创建本地分支才能与远程的分支关联起来;

重命名远程分支:在git中重命名远程分支,其实就是先删除远程分支,然后重命名本地分支,再重新提交一个远程分支。

1
2
3
4
5
6
7
8
# 1.删除远程分支:
git push --delete origin devel

#2.重命名本地分支:
git branch -m devel develop

# 3.推送本地分支:
git push origin develop

git拉取远程分支并创建本地分支:

1
2
# 方式一:
git checkout -b 本地分支名x origin/远程分支名x

使用该方式会在本地新建分支x,并自动切换到该本地分支x。采用此种方法建立的本地分支会和远程分支建立映射关系。

1
2
# 方式二:
git fetch origin 远程分支名x:本地分支名x

使用该方式会在本地新建分支x,但是不会自动切换到该本地分支x,需要手动checkout。采用此种方法建立的本地分支不会和远程分支建立映射关系。

27.指定本地branch1分支与远程origin/branch1分支的链接

1
$ git branch --set-upstream branch1 origin/branch1

作用:如果你本地新建的 branch1 分支,远程库中也有一个 branch1 分支(别人创建的),而刚好你也没有提交过到这个分支,即没有关联过,会报一个 no tracking information 信息,通过上面命令关联即可;

28.创建标签

1
$ git tag <name>

例如:git tag v1.0

查看所有标签:

1
$ git tag

对历史提交打 tag

先使用 $ git log --pretty=oneline --abbrev-commit 命令找到历史提交的commit id

例如对 commit id 为123456 的提交打一个 tag:

1
$ git tag v0.9 123456

查看标签信息:

1
$ git show <tagname>

eg: git show v1.0

创建带有说明的标签,用 -a 指定标签名,-m 指定说明文字,123456 为 commit id:

1
$ git tag -a v1.0 -m "V1.0 released" 123456

用私钥签名一个标签:

1
$ git tag -s v2.0 -m "signed V2.0 released" 345678

说明:签名采用 PGP 签名,因此,必须先要安装 gpg(GnuPG),如果没有找到 gpg,或者没有 gpg 密钥对,就会报错,具体请参考GnuPG 帮助文档配置 Key;

作用:用 PGP 签名的标签是不可伪造的,因为可以验证 PGP 签名;

删除标签:

1
2
3
4
$ git tag -d <tagname>

# 删除远程库中的标签
git push origin --delete tag <tagname>

删除远程库中的标签:

比如要删除远程库中的 V1.0 标签,分两步:

[1] 先删除本地标签: $ git tag -d V1.0

[2] 再推送删除即可: $ git push origin :refs/tags/V1.0

推送标签到远程库:

1
$ git push origin <tagname>

推送所有标签到远程库:

1
$ git push origin --tags

获取远程 tag:

1
git fetch origin tag <tagname>

29.自定义Git设置

Git显示颜色,会让命令输出看起来更清晰、醒目:

1
$ git config --global color.ui true

设置命令别名:

1
$ git config --global alias.st status

说明:–global表示全局,即设置完之后全局生效,st表示别名,status表示原始名

好了,现在敲 git st 就相当于是 git status 命令了,是不是方便?

当然还有其他命令可以简写,这里举几个:很多人都用 co 表示 checkout,ci 表示 commit,br 表示 branch …
根据自己的喜好可以设置即可,个人觉得不是很推荐使用别名的方式;

推荐一个比较丧心病狂的别名设置:

1
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

效果自己去体会…

其他说明:配置的时候加上–global是针对当前用户起作用的,如果不加只对当前的仓库起作用;每个仓库的Git配置文件都放在 .git/config 文件中,我们可以打开对其中的配置作修改,可以删除设置的别名;而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中,我们也可以对其进行配置和修改。

30.忽略文件规则

原则:

  • 忽略系统自动生成的文件等;
  • 忽略编译生成的中间文件、可执行文件等,比如Java编译产生的.class文件,自动生成的文件就没必要提交;
  • 忽略你自己的带有敏感信息的配置文件,个人相关配置文件;
  • 忽略与自己相关开发环境相关的配置文件;

使用:在Git工作区的根目录下创建一个特殊的 .gitignore 文件,然后把要忽略的文件名或者相关规则填进去,Git就会自动忽略这些文件,不知道怎么写的可参考:https://github.com/github/gitignore,这里提供了一些忽略的规则,可供参考;

如果你想添加一个被 .gitignore 忽略的文件到Git中,但发现是添加不了的,所以我们可以使用强制添加 $ git add -f <file>

或者我们可以检查及修改 .gitignore 文件的忽略规则:

1
$ git check-ignore -v <file>

Git会告诉我们具体的 .gitignore 文件中的第几行规则忽略了该文件,这样我们就知道应该修改哪个规则了;

如何忽略已经提交到远程库中的文件?
如果你已经将一些文件提交到远程库中了,然后你想忽略掉此文件,然后在 .gitignore 文件中添加忽略,然而你会发现并没有生效,因为Git添加忽略时只有对没有跟踪的文件才生效,也就是说你没有add过和提交过的文件才生效,按如下命令:

比如说:我们要忽略 .idea 目录,先删除已经提交到本地库的文件目录

1
git rm --cached .idea

格式:git rm --cached + 路径

如果提示:fatal: not removing ‘.idea’ recursively without -r

加个参数 -r 即可强制删除

1
$ git rm -r --cached .idea

然后,执行 git status 会提示你已经删除.idea目录了,然后执行 commit 再 push 就可以了,此时的.idea目录是没有被跟踪的,将.idea目录添加到 .gitignore 文件中就可以忽略了。

31.git rebase

Rebase 顾名思义,就是重新校准基础版本的意思。

我们想象一个场景:在工作中,有好几个同事一起开发同一个项目,假设每个人都在 develop 分支上进行开发和提交代码,你开开心心的开发完成了自己功能并且提交了代码。

然后你将自己的代码 push 到远程的分支,推送的时候报错了,糟糕了,它提示你,其它人开发完自己的功能并且比你先提交和推送,所以,你在推送之前必须要先将最新的代码拉下来与本地的代码合并才能推送到远端。

如果你的同事跟你修改的代码不是同一个文件,那么这时候是比较顺利的,默认会使用递归策略合并本地代码,不会产生任何的代码冲突,这时候你再推送到远端就行了。

如果你们修改的是同一个文件,那么你要先解决冲突,然后再提交,最后再推送就可以了。

但是,这种合并代码的方式有一个弊端就是,每次合并后都会多一个分支,这样一来,后面这样的分支会越来越多,显的特别乱,那怎么解决这个问题呢?当然,使用 git rebase 就可以完美解决这个问题了。

跟上面的场景一样,你需要先拉代码才能提交,这时候拉的方式我们指定为使用 rebase 的方式去拉,具体命令如下:

1
git pull origin develop —rebase

这个命令执行完后,会把远程 develop 分支上的最新代码更新的本地,并且会重新校准本地分支的基准版本,什么意思呢?

你可以理解成,我们重新校准一下本地代码的版本,我们以此为基准版本来进行我们自己的开发,为什么要校准呢?因为我们开发的时候没有拉代码或者已经是最新代码拉,结果后来别人又提交了一些代码,但是我们不知道,所以我们需要校准。

我们自己的开发的代码的提交记录就是排在在校准之后了,这样代码就是一条流水线了,在分支上也会顺序显示,并不会多出一个分支,符合我们的预期。

如果没有冲突,后面就可以正常提交和推送操作了。但是如果代码有冲突(别人和你修改了同一个文件或者同一行行代码,导致 git 不能自动合并),命令会提示哪些文件没有 rebase 成功,你需要手动将这些冲突合并。

所有冲突文件修改完成,暂存所有文件,然后继续执行命令:

1
git rebase --continue

另外,我们还可以给当前仓库设置 pull 的方式,这样就可以使用设置的策略来 pull 代码了:

1
2
3
4
5
6
7
8
# merge (the default strategy)
Git config pull.rebase false

# rebase
Git config pull.rebase true

# fast-forward only
Git config pull.ff only

32.你需要知道的一些 git 命令

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
27
28
29
30
# 创建一个源码包,我运行:
git archive --format=tar --prefix=proj-1.2.3/ HEAD

$ git add -u

Git 将查找当前目录的文件并自己算出具体的情况。除了用第二个add命令,如果你也打
算这时提交,可以运行`git commit -a`。关于如何指定应被忽略的文件,参见 *git
help ignore* 。

你也可以用一行命令完成以上任务:
git ls-files -d -m -o -z | xargs -0 git update-index --add --remove

这里 *-z* 和 *-0* 选项可以消除包含特殊字符的文件名引起的不良副作用。注意这个
命令也添加应被忽略的文件,这时你可能需要加上 `-x` 或 `-X` 选项。

$ git add -p
为你做的每次修改,Git 将展示给你变动的代码,并询问该变动是否应是下一次提交的一
部分。回答“y”或者“n”。也有其他选项,比如延迟决定;键入“?”来学习更多。

如果你修改了许多地方的许多文件怎么办?一个一个地查看变更令人沮丧,心态麻木。
这种情况下,使用 *git add -i* , 它的界面不是很直观,但更灵活。敲几个键,你可
以一次决定阶段或非阶段性提交几个文件,或查看并只选择特定文件的变更。

作为另一种选择,你还可以运行 *git commit --interactive* ,这个命令会在你操作完后自动
进行提交。

$ git reflog

检出后五次访问过的提交:
git checkout "@{5}"

Git 的自动删除:

1
2
3
4
5
6
7
$ git config gc.pruneexpire "30 days"

意思是一个被删除的提交会在删除30天后,且运行 *git gc* 以后,被永久丢弃。
你或许还想关掉 *git gc* 的自动运行:

$ git config gc.auto 0
在这种情况下提交将只在你手工运行 *git gc* 的情况下才永久删除。

附图:

好了,基本差不多了,其实常用的命令也就那么几个,如果使用多了,就熟练了,相信git给我们工作效率及工作上的提升…

声明:附图来自 CSDN 知识库,仅作为学习交流用;