Git使用教程
1.简介
Git(读音为/gɪt/)是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。 也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。
分布式相比于集中式的最大区别在于开发者可以提交到本地,每个开发者通过克隆(git clone),在本地机器上拷贝一个完整的Git仓库。
2.安装与配置
安装
在Windows上使用Git,可以直接从Git镜像下载安装程序。
打开安装程序,按默认选项安装即可。安装完成后,可以在开始菜单中中找到Git Bash、 Git GUI 、 Git CMD这几个软件,能打开说明安装成功;或者在命令行工具(例如PowerShell、cmd),输入命令git,能打印出Git的使用帮助,说明安装成功。
配置
安装完成后,为区别不同用户的提交记录,需要配置一下用户名和用户邮箱。使用命令行工具,输入命令并按下回车即可:
git config --global user.name "Your Name"
git config --global user.email "email@example.com"
使用如下命令可以查看当前全局配置的用户名和邮箱:
git config --global user.name
git config --global user.email
如果去掉--global,只在当前仓库生效。
3.Git的区域概念
一个仓库拥有三个区,不同区拥有不同的功能。文件的提交流程是工作区->暂存区->版本库
工作区:就是工作目录。别名:工作树
暂存区:是 .git 目录下一个叫做 index 的文件,所以暂存区有时也被叫作索引(index)。暂存区是一个介于工作区和版本库的中间状态,通过 add 指令暂存的内容,都会被写进这个文件里,当执行提交时实际上是将暂存区的内容提交到版本库中。
版本库:就是本地仓库,它就是 .git这个文件夹。
4. 从远程创建一个新仓库
①创建一个远程仓库
-
访问使用gitea自搭建的远程仓库(也可以使用Github、Gitee等第三方Git托管服务,功能基本上都是一样的)
-
注册或者登录账号(注意:邮箱要和本地配置的邮箱相同)
-
点击右上角
+号,创建一个远程仓库 -
进入仓库信息填写页面:1为仓库的名称,这个仓库名会被 Git 设置为你的仓库的根目录的名称;2为仓库的描述,远程仓库会根据描述信息生成一个
README.md文件;3为选择一个.gitignore模板,用于设置不允许git跟踪的文件。然后点击最下方的创建仓库来完成创建远程仓库刚创建完的仓库大概的样子:
点击复制右边的仓库地址(也可以点击下载按钮,直接把仓库压缩包下载到本地)
②把远程仓库取到本地
接下来就可以把远程仓库取下来了。取的方式很简单:在 powershell 或 cmd 中切换到你希望放置项目的目录中,然后输入:
git clone 远程仓库地址
Git就会把远程仓库clone到本地。(第一次下载和推送之前,Git可能会要求输入远程仓库的账户名与密码。)下载完成之后,我们会看到,当前所在的目录下出现了一个子目录,它的名字与远程仓库的名字相同:
可以看到在.gitignore和README.md这两个文件之外,还有一个.git的隐藏文件夹。
这个 .git 文件夹,就是你的本地仓库(Local Repository),你的所有版本信息都会存在这里。而 .git 所在的这个根目录,称为 Git 的工作目录(Working Directory),它保存了你当前从仓库中签出(checkout)的内容。现在把命令行工具切换到git-manual目录下,输入:
git log
可以看到仓库的提交历史:
这时候只有一条提交历史,这个提交是远程仓库帮你做的,它的内容是创建你的初始 .gitignore 和 README.md 这两个文件。图中第一行中的 commit 右边的那一大串字符( ad30e8731......6175),是这个 commit 的 SHA-1 校验和(你可以暂时把它简单理解为这个 commit 的 ID,因为SHA-1是一种哈希算法,所以也叫它哈希值);后面括号里的内容(HEAD -> main ...)后续再说;第一行的下面,依次是这个 commit 的作者、提交日期和提交信息,其中提交信息记录了这个提交做了什么,是提交者填写的(当然,这条提交信息是 远程仓库 帮你写的)。
③尝试自己写个提交
把远程仓库取到本地之后,我们就可以开始写代码了。为了练手,我们可以随便创建一个文本文件,.c,.txt等格式都可以,这里新建一个text.txt文件,向里面添加一段文本。
Git的提交命令是commit,但是现在我们还不能直接提交,首先在命令行输入:
git status
status用来查看工作目录当前的状态
这段文字表达的信息:
1.你当前在main分支
2.你的分支没有落后于origin/master
3.你有未追踪的文件,文件名是text.txt,如果你需要提交它,使用git add开始追踪文件。
由第三条可以看出,当前Git仓库对text.txt没有进行任何记录,对于Git仓库而言,text.txt是不存在的。所以,要想提交这个文件,需要先使用git add 让Git开始跟踪它的变化:
git add text.txt
执行完命令之后,git不会反馈成功的信息,这时候可以再次执行git status
可以看到,信息已经变化了,text.txt字体变成了绿色,前面多了new file:的标记,这些都说明一点:shopping list.txt 这个文件的状态从 "untracked"(未跟踪)变成了 "staged"(已暂存),意思是这个文件中被改动的部分(第一次添加就是这一整个文件)被记录进了 staging area(暂存区)。
现在文件已经进入了暂存区,就可以提交了。提交的方式是用commit指令:
git commit -m "添加text.txt,并添加一些文字"
这样就完成了一次提交,-m "添加text.txt,并添加一些文字"是为这次提交附加的提交信息,一般用于说明本次提交所作的改动。
这时我们可以再次执行log指令,可以看到提交历史多了一条我们刚才提交的记录:
④再来个提交
我们这时再向text.txt添加一段文本,然后再次使用git status指令:
上面这段文本表达的信息:
1.当前在main分支
2.你的分支比远程的origin/main领先一个commit
3.text.txt被修改了,但修改还没有被暂存。使用git add <file>把工作区中的修改更新到暂存区;使用git restore <flie>放弃工作区中的修改。
Git的提示已经很清楚了,我们只需要再次使用git add和git commit,就完成了第二次提交。再次使用git log查看提交历史:
我们可以看到最上面的总是最新的提交,这是Git默认的设置。
如果窗口区域不足以输出全部信息,Git会等待用户的操作:
此时按下Enter,Git会输出下一行;按下Q,会退出Git。
⑤把提交推送到远程仓库
到目前为止,我们已经在本地写了两条提交,我们想把它们推送到远程仓库,只需要使用push命令。在此之前我们先查看一下status
git status
可以看到,当前分支已经领先远程分支origin/main两个提交了。
origin/main 的中的 origin 是远端仓库的名称,是你在用 clone 指令初始化本地仓库时 Git 自动帮你起的默认名称;main 是 origin 上的分支名称。
而这句话的下面也说明了,你可以使用 git push 来把你的本地提交发布(即推送到远程仓库)。所以很简单,照做吧:
git push
第一次提交,可能需要使用输入账户密码进行认证:
输入账户密码之后,我们观察命令行,这时候Git就把本地的提交到远程仓库了。
5.本地创建仓库并关联到远程仓库
把本地仓库管理到远程仓库,有两种情况:要么本地已有仓库,要么本地还没有仓库。如果本地还没有创建仓库,则从①开始,如果本地已有一个仓库,直接从②开始:
①在项目目录中新建一个仓库
-
只在需要创建仓库的项目目录,使用命令
git init即可创建一个新的仓库 -
git默认的分支名为
master -
然后在目录中添加一个
.gitignore文件,在前面介绍过,它相当于忽略名单,用于设置不允许git跟踪的文件,避免一些不需要的文件被加入仓库。示例如下: -
最好再添加一个
readme.md或readme.txt文件,用于介绍这个仓库。.md是一种轻量化的格式文件,全称markdown。 -
使用
git add和git commit提交代码到本地仓库。只有产生一次提交之后,才能进行后续的操作
②创建一个空白的远程仓库
-
这次使用Gitee做演示,访问远程仓库Gitee.com。(Github也可以,不过Github是国外网站,访问速度较慢)
-
注册或者登录账号
-
点击仓库列表右边
+号,创建一个远程仓库 -
进入仓库配置页面,只需要设置仓库的名称即可
-
创建完成之后,我们可以看到仓库是一个完全空白的仓库,里面只有一些Gitee的提示。
③关联远程仓库
上面创建的空白的远程仓库已经提示我们应该怎么操作了,我们照做即可:
git remote add origin https://gitee.com/sq800/new-repo.git
origin是远程仓库的别名,代表后面的链接指向远程仓库,也可以设置为其他字符串。
例如,示例使用的是Gitee,我们也可以把别名设置为gitee
git remote add gitee https://gitee.com/sq800/new-repo.git
④把本地提交推送到远程仓库
git push -u origin master
这里-u可以不加,加上-u,下次再推送到这个分支,可以简化为git push。
不出意外的话,我们本地的仓库就提交到远程仓库了。
6.HEAD、master、分支的本质
我们使用git log看一下commit(提交)历史:
我们以这两个提交举例说明。每一个commit后面的的一长串字符串是它的哈希值,前面4.②中我们已经介���过了,哈希值是指向这个提交唯一的方式。
后面的(HEAD -> master, main),是两个指向这个commit的引用。下面红色的(gitea/main),是远程仓库gitea(别名)的main分支指向的提交。
这说明当前本地仓库已经领先了远程仓库gitea一个提交。
HEAD:当前commit的引用
HEAD是引用中最特殊的一个,它指向当前commit的引用,所谓**当前 commit**这个概念很简单,它指的就是当前工作目录所对应的 commit。上图中当前的commit就是最新的那个commit,每次当有新的 commit 的时候,HEAD 会转而指向最新的 commit。
事实上,当使用 checkout、reset 等指令手动指定改变当前 commit 的时候,HEAD 也会一起跟过去。
总之,当前 commit 在哪里,HEAD 就在哪里,这是一个永远自动指向当前 commit 的引用,所以你永远可以用 HEAD 来操作当前 commit。
分支(branch)
分支也是一个引用,它也指向一个commit
HEAD除了可以指向某个提交,也可以指向一个分支,当它指向某个 branch 的时候,会通过这个 branch 来间接地指向某个 commit;另外,当 HEAD 在提交时自动向前移动的时候,它会带着它所指向的 branch 一起移动。
例如上面那张图,HEAD -> master中的master就是一个分支的名字,而它左边的箭头表示HEAD正在指向它,如果这个时候再提交一次,HEAD和master会一块向前移动。我们可以来验证一下:
这里我省略了修改文件、add和commit的步骤。可以看到,经过一次新的提交后,HEAD以及它指向的master分支,都转而指向了新的的提交,而main分支依然指向原来的commit上,gitea/main 分支也依然指向原来的commit上。
master:默认分支
上面的这个 master ,其实是一个特殊的 branch:它是 Git 的默认 branch(俗称主 branch / 主分支)。
所谓的「默认 branch」,主要有两个特点:
- 新创建的仓库是没有任何
commit的。但在它创建第一个commit时,会把master指向它,并把HEAD指向master。 - 当有人使用
git clone时,除了从远程仓库把.git这个仓库目录下载到工作目录中,还会checkout(签出)master(checkout的意思就是把某个commit作为当前commit,把HEAD移动过去,并把工作目录的文件内容替换成这个commit所对应的内容)。
注意:一些远程仓库由于某些原因,在上面创建仓库的时���,会把主分支设置为main。
branch 的通俗化理解
尽管在 Git 中,branch 只是一个指向 commit 的引用,但它有一个更通俗的理解:你还可以把一个 branch 理解为从初始 commit 到 branch 所指向的 commit 之间的所有 commit 的一个「串」。例如下面这张图:
c0是初始提交,main分支指向c4提交,dev分支指向c3提交
7.创建、切换、合并、删除分支
列出所有分支
git branch
*号代表当前所在的分支
创建分支
在当前分支基础之上创建分支,新分支拥有当前分支的提交历史
git branch [分支名字]
创建一个新分支并切换到新分支
git ckeckout -b [分支名字]
切换分支
git switch [分支名字]
或
git checkout [分支名字]
合并分支
合并指定分支到当前分支
git merge [分支名]
合并分支会产生一个新提交,HEAD会带着当前分支一起指向新的提交。而被合并的分支依然指向原来的提交。
合并冲突可以参考下一节的合并冲突来解决。
删除分支
删除本地的分支
git branch -d [分支名字]
删除远程仓库的分支
#删除远程仓库gitea的某个分支,注意,默认分支不可删除
git push gitea -d [分支名字]
8.仓库的同步
本地的仓库的提交和远程的仓库的提交要保持一致,需要手动同步,包含拉取和推送。
如果仅仅是一个远程仓库和一个本地仓库,并且只在本地仓库做修改并推送到远程仓库,那么并只需要使用push向远程仓库推送就可以了。
推送
使用push把本地仓库的提交推送到远程仓库,前面已经介绍过。代码如下:
#把本地main分支推送到origin的main分支
git push origin main
#把本地main分支推送到origin的dev分支
git push origin main:dev
如果有多个同事共同开发一个项目,他们都向一个远程仓库提交代码。也就是说,不止一个本地仓库关联了远程仓库并推送提交;
或者只有一个人开发项目,但是在远程仓库上直接修改并提交了代码(这里仅用文本做示意):
总之,此时远程仓库包含了本地没有的commit,���此直接push本地仓库上去会报错:
git push origin main
拉取
我们需要先使用pull拉取变化。pull会把远程仓库的commit拉取到本地,并与本地的仓库进行合并,它实际包含fetch和merge两条命令。
#拉取origin上的main分支合并到本地的main分支
git pull origin main
#拉取origin上的main分支合并到本地的dev分支
git pull origin main:dev
使用git log,可以看到,远程仓库的commit已经被合并到本地仓库了,本地仓库已经完成了与远程仓库的同步:
合并冲突
团队开发时,我们一定会遇到一种情况:当同事A向远程仓库push时,同事B已经向远程仓库push了提交。这时同事A再向远程仓库push时,Git会报错误,错误截图就不放了,和上面的push报错是一样的。
这时候我们需要先把远程仓库的提交pull到本地:
#gitea是远程仓库别名,如果名字配的是origin,使用origin即可
git pull gitea main
Git会尝试自动合并冲突,如果冲突无法自动合并,Git会把冲突标记出来,留给我们自行合并。在Vscode中合并冲突如下图所示:
可以看到,text.txt文件产生了冲突:同事A添加的文本“同事A第一次提交”与同事B添加的文本冲突了。绿色块是本地的提交,蓝色块是从远程仓库拉下来的提交,他们之间用多个连续的等号隔开。
色块是VScode对Git的图形化支持。如果用记事本打开这个文件,我们可以看到它原本的样子:
我们可以手动地删除不想要的代码,然后删除掉下图中红框框起来的部分,就完成了合并冲突的第一步。
合并冲突的第二步就是提交修改后的第一步的代码:
# 首先把修改好的冲突文件添加到暂存区
git add .
git commit -m "合并远程冲突:...."
这样就完成了冲突的合并,然后就可以push到远程仓库了
git push gitea main
这时候我们看远程仓库,已经成功推送上去了:
多人合作的基本工作模型
-
写完所有的
commit后,不用考虑远程仓库是否有新的提交,直接push就好 -
如果其他人已经有过提交,则会
push失败,就用git pull拉取远程仓库的提交与把本地仓库的提交进行合并,然后再push一次
git pull包含git merge命令,会自动合并提交,如果遇到合并冲突,则需要手动合并。
9.Git的部分常用操作
提交与修改
为便于说明,接下来在目录中新建一些文本文件和目录,分别是empty.txt、file.txt、readme.md和用与存放图片的目录img。
git add --添加文件到暂存区
文件添加到暂存区后,Git会自动对文件更改进行跟踪。文件每次修改之后,如果需要提交,都要使用git add命令暂存到暂存区
git add empty.txt
可使用命令git status查看当前仓库的状态,显示有变更的文件,可以看到empty.txt被添加到了暂存区:
添加多个文件到暂存区:
git add file.txt readme.md
添加整个img文件夹到暂存区:
git add img
添加当前目录全部文件到暂存区:
git add .
git commit --提交暂存区到本地仓库
如果一个文件或文件的修改没有使用git add添加到暂存区进行追踪,那么git commit命令不会把它提交到仓库。
提交暂存区到本地仓库,使用-m附带提交说明
git commit -m "提交说明"
提交暂存区指定文件到本地仓库
git commit readme.md file2.txt -m "提交说明"
对于已添加到暂存区的文件(已跟踪的文件),可以跳过暂存区,直接提交文件在工作区的修改
git commit -a -m "提交说明"
git status --查看当前仓库的状态
查看上次提交之后是否对文件有修改。 常见的状态有:
| 状态 | 说明 |
|---|---|
| Changes to be committed | 暂存区暂存的修改,等待被提交 |
| Changes not staged for commit | 文件在工作区已被修改,等待暂存 |
| Untracked files | 未添加到暂存区的文件 |
git diff --比较文件差异
比较文件在暂存区和工作区的差异:
git diff file2.txt
比较暂存区和上一次提交的差异
git diff --cached file2.txt
或
git diff --staged file2.txt
比较两次提交的差异,需要使用两次提交的哈希值,哈希值可以通过git log获取。
git diff [hash1] [hash2]
可以使用代码编辑器例如VScode来对比文件差异,可以清楚地查看文件的不同之处
git rm --从暂存区删除文件
从暂存区删除文件1.txt,不再跟踪它的变化,把它保留在工作区
git rm --cached 1.txt
从暂存区和工作区中删除文件1.txt
git rm 1.txt
git restore --恢复操作
放弃在工作区对a.txt的修改,把暂存区的版本恢复到工作区
git restore a.txt
或
git checkout -- a.txt
放弃添加到暂存区的修改,恢复暂存区到添加前的状态,工作区不变:
git restore --staged a.txt
或
git reset HEAD
查看提交历史与回退版本
git log --查看提交历史
默认查看当前分支的历史;加--all 查看全部分支的历史
查看完整提交历史
git log
提交记录的哈希值也叫提交ID。
查看最近两次的提交
git log -2
查看简短版的提交历史,哈希值被缩短了。为了方便,一般都使用简短的哈希值,它具有唯一性。
git log --oneline
查看当前分支的可视化的提交历史:
git log --oneline --graph
查看全部分支的可视化的提交历史。可以清楚地看到什么时候出现了分支、合并:
git log --oneline --graph --all
git blame --查看指定文件的修改记录
包括提交的哈希、作者名、提交日期
#git blame [文件地址] 例如:
git blame readme.md
git reset --回退版本
重置命令git reset是Git最常用的命令之一,也是最危险,最容易误用的命令。
使用git reset命令,需要使用提交记录的哈希值,可以使用git log --oneline 命令获取某次提交(版本)的哈希值(提交ID)。哈希值是提交记录的绝对引用;如果知道要回退的版本与现在版本的距离,可以使用相对引用HEAD。HEAD可以被理解为“当前分支”,它默认指向当前所在分支的最新的提交,我们的工作都是在HEAD指向的位置上进行的,可以使用cat .git/HEAD查看当前HEAD的指向。HEAD^代表上一次提交,HEAD^^代表前上上一次提交,如果要向前N个版本,也可以使用HEAD~N来简写。
git reset命令的用法:
- 带文件路径的用法:把暂存区的文件替换为指定
commit的版本。
下面的命令是用最近一次提交的1.txt替换当前暂存区的1.txt,相当于取消git add 1.txt的操作
git reset HEAD 1.txt
-
不带文件路径的用法:
git reset [--soft | --mixed | --hard] [HEAD]/[哈希值]
根据soft,mixed,hard的不同,git reset对HEAD(当前分支)、暂存区、工作区也有不同的影响:
git reset --hard 9d60796当前分支回滚到哈希值
9d60796指向的版本;暂存区被替换为9d60796的版本内容;工作区被替换为9d60796的版本内容。(工作区与暂存区未提交的代码会丢失)git reset --soft 9d60796当前分支回滚到
9d60796的版本;不改变暂存区与工作区。git reset --mixed 9d60796当前分支回滚到
9d60796的版本;暂存区被替换为9d60796的版本内容;不改变工作区。可以省略--mixed,不使用参数,默认为--mixed。