git使用
前言
本篇文章内容根据[廖雪峰的官方网站](Git教程 - 廖雪峰的官方网站 (liaoxuefeng.com))编写,若有侵权,请联系我。
Git
1. 简介
Git是目前世界上最先进的分布式版本控制系统(没有之一)。
Git 作用:版本控制(将项目的各个版本,即同一个项目修改和没修改过的版本保存在git的版本库中,便于用户管理各个版本,即历史记录)
分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
2. Git使用
2.1 git安装后设置
因为Git是分布式版本控制系统,所以,首先设置的便是你的名字和Email地址。
1 | git config --global user.name "Your Name" |
==注意==:git config
命令的--global
参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
2.2 创建版本库
什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
==创建一个版本库==:首先,选择一个合适的文件夹,然后,进入文件夹右键选择git bash here
打开git面板输入git init
初始化命令把这个目录变成Git可以管理的仓库。也可以在cmd
中切换到该文件夹输入git init
(前提是git加入环境变量)
1 | git init |
(这里我将==Git Repositories==文件目录变成变成Git可以管理的仓库)
==注意==:所有的版本控制系统,其实只能跟踪文本文件的改动,比如``TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从
100KB改成了
120KB`,但到底改了啥,版本控制系统不知道,也没法知道。
不幸的是,Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,前面我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件。
因为文本是有编码的,比如中文有常用的GBK
编码,日文有Shift_JIS
编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8
编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。
2.3 利用git进行基本操作
想要利用git的文件必须放在git版本库的父目录。例如:
在==Git Repositories==文件目录创建一个``test.txt`文件,文件内容为
1 | Git is a version control system. |
==注意==:一定要放到==Git Repositories==目录下(子目录也行),因为这是一个Git仓库,放到其他地方Git再厉害也找不到这个文件。
把一个文件放到Git版本仓库只需要两步。
第一步,用命令git add
告诉Git,把文件添加到暂存库:
1 | git add test.txt |
执行上面的命令之后是没有任何显示的。
第二步,用命令git commit
告诉Git,把文件提交到版本仓库:
1 | git commit -m "wrote a test file" |
简单解释一下git commit
命令,-m
后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
git commit
命令执行成功后会告诉你,1 file changed
:1个文件被改动(我们新添加的readme.txt
文件);2 insertions
:插入了两行内容(readme.txt
有两行内容)。
为什么Git添加文件需要add
,commit
一共两步呢?因为commit
可以一次提交很多文件,所以你可以多次add
不同的文件,比如:
1 | git add file1.txt |
3. git版本管理
3.1 初识版本控制
现在我们已经成功地添加并提交了一个test.txt
文件,接着,我们继续修改text.txt
文件,改成如下内容:
1 | Git is a distributed version control system. |
现在,运行git status
命令看看结果:
1 | git status |
git status
命令可以让我们时刻掌握仓库当前的状态,上面的命令输出告诉我们,readme.txt
被修改过了,但还没有准备提交的修改。
虽然Git告诉我们readme.txt
被修改了,但如果能看看具体修改了什么内容,自然是很好的。所以,需要用git diff
这个命令看看:
1 | git diff test.txt |
git diff
顾名思义就是查看difference,显示的格式正是Unix通用的diff
格式,可以从上面的命令输出看到,我们在第一行添加了一个distributed
单词。
知道了对readme.txt
作了什么修改后,再把它提交到仓库就放心多了,提交修改和提交新文件是一样的两步,第一步是git add
:
1 | git add test.txt |
同样没有任何输出。在执行第二步git commit
之前,我们再运行git status
看看当前仓库的状态:
1 | git status |
git status
告诉我们,将要被提交的修改包括readme.txt
,下一步,就可以放心地提交了:
1 | git commit -m "add distributed" |
提交后,我们再用git status
命令看看仓库的当前状态:
1 | git status |
3.2 版本回退
现在,我们回顾一下test.txt
文件一共有几个版本被提交到Git仓库里了:
版本1:wrote a test file
1 | Git is a version control system. |
版本2:add distributed
1 | Git is a distributed version control system. |
在Git中,我们用git log
命令查看历史记录:
1 | git log |
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline
参数:
1 | git log --pretty=oneline |
==注意==:你看到的一大串类似1094adb...
的是commit id
(版本号)
(HEAD -> master): 解释请点链接:innocent:
现在我们准备把test.txt
回退到上一个版本,也就是wrote a test file
的那个版本
首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD
表示当前版本,上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。
现在,我们要把当前版本add distributed
回退到上一个版本wrote a test file
,就可以使用git reset
命令:
1 | git reset --hard HEAD^ |
1 | cat test.txt |
果然被还原了。
让我们用git log
再看看现在版本库的状态:
1 | Author: Afei-Yolo <15536180253@163.com> |
最新的那个版本add distributed
已经看不到了!
但如果我们想要回到最新的版本,只要找到那个add distributed
的commit id
是4522e3...
,于是就可以指定回到未来的某个版本:
1 | git reset --hard 4522e3 |
再小心翼翼地看看test.txt
的内容:
1 | cat test.txt |
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD
指针,当你回退版本的时候,Git仅仅是把HEAD从指向add distributed
:
1 | ┌────┐ |
改为指向wrote a test file
:
1 | ┌────┐ |
然后顺便把工作区的文件更新了。所以你让HEAD
指向哪个版本号,你就把当前版本定位在哪。
现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,但找不到新版本的commit id
怎么办?
在Git中,总是有后悔药可以吃的。当你用$ git reset --hard HEAD^
回退到wrote a test file
版本时,再想恢复到add distributed
,就必须找到add distributed
的commit id
。Git提供了一个命令git reflog
用来记录你的每一次命令:
1 | git reflog |
终于舒了口气,你又可以乘坐时光机回到未来了。
3.2 管理修改
每次修改,如果不用git add
到暂存区,那就不会加入到commit
中
3.3 撤销修改
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout --<file>
。(当然,如果你打开该文件的软件还有撤销功能的话,点它!!)
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,即:你改了文件并用了add
命令。想丢弃修改,分两步,第一步用命令git reset HEAD <file>
,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
3.4 删除文件
命令rm用于删除文件,但工作区和版本库就不一致了,git status
命令会立刻告诉你哪些文件被删除了,之后有两种情况:
确实要从版本库中删除该文件,那就用命令
git rm
删掉,并且git commit
,现在,文件就从版本库中被删除了。删错了,可以用下列命令很轻松地把误删的文件恢复到最新版本:
1
git checkout -- test.txt
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
4. 远程仓库
4.1 将本地git版本库推送到远程仓库(本例采用GitHub
仓库)
在GitHub新建一个仓库
链接本地git版本库与GitHub仓库
1
git remote add origin git@github.com:michaelliao/learngit.git
@github.com:michaelliao/learngit.git
:你的仓库地址,添加后,远程库的名字就是origin
把本地库推送到GitHub仓库:
1
git push -u origin master
由于远程库是空的,我们第一次推送
master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
4.2 从远程仓库克隆
1 | git clone git@github.com:michaelliao/gitskills.git |
@github.com:michaelliao/gitskills.git
:仓库地址
5. 分支管理
5.1 分支介绍
现在,我们假设有一个文件1,其修改为2,3,4,5,6;其add
到暂存区的文件为对应的1(对于文件1),2,3,4,5,6;提交到版本区的文件为1,2,3,4,5,6.(==注意==:提交到版本区之后,暂存区相应的文件会消失,🤔,也就是没了。这里标出来只是为了形象);在提交了3之后,我们新建了ber
分支,(注意现在main
和ber
都指向3,而HADE
指向main
),之后,我们在工作区将3改为了4,并add
到暂存区为4,提交到版本区为4,此时HADE
指向main
,main
指向4,而ber
仍指向3,接着我们修改4为5,并add
到暂存区为5,修改分支为ber
,提交到版本区为5,此时HADE
指向ber
,ber
指向5,最后我们修改5为6,并add
到暂存区为6,提交到版本区为6,此时HADE
指向ber
,ber
指向6。
5.2 分支操作
用
git checkout -b ber
新建分支1
2git checkout -b ber
Switched to a new branch 'ber'git checkout
命令加上-b
参数表示创建并切换,相当于以下两条命令:1
2
3git branch ber
git checkout ber
Switched to branch 'ber'最新版本的Git提供了新的
git switch
命令来切换分支:创建并切换到新的
ber
分支,可以使用:1
$ git switch -c ber
用
git branch
命令查看当前分支:1
2
3git branch
* ber
mastergit branch
命令会列出所有分支,当前分支前面会标一个*
号。切换分支
1
git switch master
删除分支:
1
2git branch -d dev
Deleted branch dev (was b17d20e).
5.3 合并分支
某个分支的内容比另一个分支的内容新时:(此时
dev
内容较新)git merge
命令用于合并指定分支到当前分支:1
2
3
4
5git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)上面的
Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。两个分支指向内容不一样,即:
1
2
3
4git merge feature1
Auto-merging 1.txt
CONFLICT (content): Merge conflict in 1.txt
Automatic merge failed; fix conflicts and then commit the result.有冲突。用
git status
查看一下:1
2
3
4
5
6
7
8
9
10
11git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: 1.txt
no changes added to commit (use "git add" and/or "git commit -a")用
cat
查看文件:1
2
3
4
5
6cat 1.txt
<<<<<<< HEAD
abcdefghijklmnopqr123456789!@#¥%……&*
=======
abcdefghijklmnopqr123456789+++++++++++
>>>>>> feature1我们修改内容提交到任意一个分支,情况就与1一样了。
5.4 分支管理策略
```bash
A—B—C topic
/ \
D—E———–H master
```
从E分叉出topic分支,topic分支上提交了A、B、C,但master上从E开始没有进行提交。
当我们处于master分支,想要将topic分支合并到master中时,就涉及到三个不同的选项–ff、–no-ff、–ff-only。也就是说这三个选项只在这种情况有效,这种情况为:master上从E开始都没有提交,并想要将topic分支合并到master中。这种情况称为“当合并的历史是当前历史的后代”。
节点的tag不存储在refs/tags/中时,默认使用–no-ff。其他情况默认使用–ff
–no-ff:创建新的提交H
```bash
A—B—C topic
/ \
D—E———–H master
```
–ff:不创建新的提交H,将指针mater指向C,得到如下结果:
```bash
D—E—A—B—C master
```
–squash:将待合并分支的最后一个提交放到当前分支的工作区。如A、B、C对readme.txt进行了修改,那么–squash的作用就是将经过A、B、C的readme.txt文件复制到工作区中。然后我们就可以进行add和commit来生成一个提交。生成的这个提交,就相当于对A、B、C工作的总结。
如果复制到工作区的readme.txt文件与工作区中的readme.txt文件有冲突??答:出现这种冲突时的处理办法就是将冲突内容全部展示在readme.txt中。
–squash只是为让提交的日志看起来简洁一些。
6. 标签管理
6.1 标签介绍
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
Git有commit,为什么还要引入tag?
“请把上周一的那个版本打包发布,commit号是6a5819e…”
“一串乱七八糟的数字不好找!”
如果换一个办法:
“请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
6.2 标签操作
创建标签
找到打标签的分支以及版本,输入:
1
git tag v1.0 f52c633
f52c633
:版本号创建带有说明的标签,用
-a
指定标签名,-m
指定说明文字:1
git tag -a v0.1 -m "version 0.1 released" 1094adb
查看标签
1
2git tag
v1.0查看标签详细信息:
1
git show v0.9
==注意==:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
删除标签
1
2git tag -d v0.1
Deleted tag 'v0.1' (was f15b0dd)创建的标签都只存储在本地,不会自动推送到远程,打错的标签可以在本地安全删除。
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
1
2git tag -d v0.9
Deleted tag 'v0.9' (was f52c633)然后,从远程删除。删除命令也是push,但是格式如下:
1
2
3git push origin :refs/tags/v0.9
To github.com:michaelliao/learngit.git
- [deleted] v0.9推送标签
推送某个标签到远程,使用命令
git push origin <tagname>
:1
2
3
4git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
* [new tag] v1.0 -> v1.0一次性推送全部尚未推送到远程的本地标签:
1
2
3
4git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
* [new tag] v0.9 -> v0.9
7. 忽略特殊文件
在Git工作区的根目录下创建一个特殊的.gitignore
文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
把指定文件排除在.gitignore
规则外的写法就是!
+文件名,所以,只需把例外文件添加进去即可。
忽略文件的原则是:
- 忽略操作系统自动生成的文件,比如缩略图等;
- 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的
.class
文件; - 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。