Git 基础教程(干货)
本文主要介绍Git的基本知识,包括但不限于部分原理和基本的命令,旨在帮助初次接触Git的小伙伴能够在短时间内借助Git工具实现自己的代码及文件的版本管理。虽然Git和SVN存在部分相似的概念,但是还是希望阅读本文的小伙伴能够完全忘记SVN的代码控制的流程,切忌在阅读时脑海中形成SVN的流程,作者将试图使用图解的方式帮助大家尽可能的形成Git相关的脑海画面,以便于能够更好的利用Git进行自己的版本管理。
三种状态
记住,如果想要顺利地使用Git完成你的文件版本管理,一定要记住:Git的文件可以处于以下三种状态之一:
- 已提交(committed)
- 已修改(modified)
- 已暂存(staged)
由此对应着Git项目中的三个主要区域:Git仓库、工作目录以及暂存区。
Git工作模型
当我们进行一项工作时,在打开的编辑器里面看到的代码文件即代表着Git中的工作目录;当我们完成某次Bug修复,新增或者修改文件为已修改状态,执行 git add
命令后,对应的文件状态为已暂存,此时这些文件的修改或者当前版本会被标识并被加入到下一次要提交的快照中。当执行 git commit
命令后,相关文件会进入已提交状态,此时修改的数据已经被完全的存入本地数据库中了。
.git 目录
Git项目中,一定会存在一个.git
目录,我们只要知道这个目录很重要,记录着你git操作的一切,不要随便删除就好了。本文不做简介,有兴趣的小伙伴可以自行研究。
Git的基本操作
接下来,作者将演示Git中的基本操作,会以问答的形式模拟工作中常用的操作场景。建议阅读的小伙伴这个时候应该本地电脑是已经安装了Git客户端的。
配置自己的Git
git config
命令可以实现相关的一些配置,现在我们用它来实现全局用户的配置:
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
怎么新建项目?
如果我们需要对一个现存项目目录my_project
实现跟踪管理,只需要进入项目执行git init
命令即可:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git
$ cd my_project/
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/my_project
$ git init
Initialized empty Git repository in C:/Users/Genttle · Han/Desktop/git/my_project/.git/
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/my_project (master)
$ ls -al
total 4
drwxr-xr-x 1 Genttle · Han 197121 0 10月 26 10:53 ./
drwxr-xr-x 1 Genttle · Han 197121 0 10月 26 10:50 ../
drwxr-xr-x 1 Genttle · Han 197121 0 10月 26 10:53 .git/
-rw-r--r-- 1 Genttle · Han 197121 0 10月 26 10:50 test.txt
我们可以看到,在执行了git init
命令后,my_project
目录中被初始化了一个.git
目录,这个子目录包含了构成Git仓库骨架的所有必须文件,但是此刻git并没有跟踪任何文件。如果你打算跟踪目录中的test.txt文件,那必须对其进行一次首次提交(后面会涉及,这里只介绍git init
)。
怎么克隆项目?
克隆一个项目,就需要拿到一个项目克隆地址,执行git clone
命令即可,该命令默认会从服务器上把整个项目历史中的每一个文件的所有历史版本都拉取下来。
$ git clone https://gitee.com/hanjintao/ccrm.git
Cloning into 'ccrm'...
remote: Enumerating objects: 165, done.
remote: Counting objects: 100% (165/165), done.
remote: Compressing objects: 100% (132/132), done.
remote: Total 165 (delta 31), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (165/165), 77.41 KiB | 234.00 KiB/s, done.
Resolving deltas: 100% (31/31), done.
该命令会创建一个名为ccrm的新目录,并在其中初始化.git目录,然后将远程仓库中的所有的数据都拉取到本地并检出最新版本的可用副本。进入到ccrm中可以看到所有文件都已准备好:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git
$ cd ccrm/
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ ls
mvnw* mvnw.cmd pom.xml README.md src/
此时目录中的所有项目文件均为已跟踪且未修改的,这个时候你就可以打开编辑器进行系列工作了。其实就对应着已提交状态。
如何完成一次提交?
假使你已经完成了一个需求,涉及文件的增删改,接下来你应该做如下步骤:
- 检查文件的状态(已修改)
- 使得自己的修改得到Git的跟踪(已暂存)
- 提交自己的修改(已提交)
- 分享自己的修改给其他协作的小伙伴
我们可以使用git status
命令检查文件状态:
$ git status
On branch master
Your branch is up-to-date with 'origin/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: src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
no changes added to commit (use "git add" and/or "git commit -a")
提示有一个文件CcrmApplicationTests.java修改,还未暂存,也就是文件当前处于已修改状态,并且提示我们可以执行git add
命令来添加暂存,也可以使用git checkout
命令来放弃更改,假使我我们不确定更改的内容,想要查看哪里做了修改,可以执行git diff
命令来查看具体修改了哪些地方:
$ git diff src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
diff --git a/src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java b/src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
index c2901fa..6700a10 100644
--- a/src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
+++ b/src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
@@ -11,6 +11,7 @@ public class CcrmApplicationTests {
@Test
public void contextLoads() {
+ printf()
}
}
好,OK,接下来我们就可以使用git add
将修改添加到暂存区了:
$ git add src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
我们也可以使用git add .
命令来提交当前目录所有修改,此处可以使用问通配符。我们再执行git status
命令可看到,提示有一个文件修改待提交,也就是该文件目前处于已暂存状态,假使现在我们已经完成了本次的一个bug的修复,这个时候就可以使用git commit
命令来提交本次bug的修复:
$ git commit -m "修复了一个bug"
[master f9289e8] 修复了一个bug
1 file changed, 1 insertion(+)
这个时候所有修改已经提交,这个时候文件状态又再一次的回到对应的已提交状态了,到此为止,我们已经完成了一次bug的修复,并且将自己的成果提交到了git。
在工作中,如果你对自己的文件修改,了如执掌,那们你一定会认为,暂存区难免会显得啰嗦,这个时候,你完全可以跳过暂存区,来提交你的修改,只需要执行命令的时候加上-a命令参数就可以了,如:git commit -a -m "修复了一个问题"
.gitignore文件
上述,当你执行git status
等这类文件的时候,有时候你会发现很多文件我们并不需要跟踪,比如:README、LICEMSE.txt等这类文件。这个时候我们就可以使用.gitignore文件来管理这类文件,让git忽略这些文件,使得我们只要专注我们的业务文件的跟踪就好。
该文件支持通配符来定义开发者自己的忽略规则,这里给出一个例子供大家参考,更详情的例子,GitHub上面有很多,开发者可以自行寻找参考:
.a(忽略.a类型的文件)
!lib.a(仍然跟踪lib.a文件,即使上一行指令要忽略)
/ToDo(只忽略当前文件的ToDo文件,而不忽略子目录下的该文件)
build/(忽略build目录下的所有文件)
doc/.txt(忽略doc一级目录下的txt文件,而不忽略子目录下的txt文件)
doc/*/.pdf(忽略doc目录下的所有pdf文件)
如何放弃一个文件的跟踪?
在工作中,我们经常遇到想放弃一个文件的跟踪,也即是每次git staus
不会再去检查这个文件,这个时候我们可以使用git rm
命令来将这个文件从暂存区移除,如果文件处于已暂存状态,则可以使用git rm -f
来强制移除。
如何重命名一个文件?
我们可以使用git mv
命令来重命名一个文件,我们来查看一个test.txt文件的修改:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: test.txt
接下来我们来修改一下该文件的名字为test_1.txt:
$ git mv test.txt test_1.txt
此时我们再查看一下,文件的跟踪状态,会发现,git已经很聪明的跟踪到了新命名的文件:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: test_1.txt
其实这相当于执行了以下三种命令的集合:
$ mv test.txt test_1.txt
$ git rm test.txt
$ git add test_1.txt
如何查看提交历史?
在日常工作中,我们最常见的工作之一,就是去查看历史提交记录,在Git中,查看历史提交特别方便,只要执行git log
命令即可:
$ git log
commit f9289e866d7419e2c1f85294e268de625944fed7 (HEAD -> master)
Author: hanjt <hjt52163@163.com>
Date: Mon Oct 26 13:55:02 2020 +0800
修复了一个bug
该命令会按照时间顺序列出仓库中的所有提交,其中最新的提交在最前面,如图,该命令会替我们列出提交的作者,日期,校验和等信息。
我们还可以使用参数列出自己想要的格式,比如:
$ git log --pretty=oneline
f9289e866d7419e2c1f85294e268de625944fed7 (HEAD -> master) 修复了一个bug
84d3692f957221c307afa04015fb5ac0a96b014c (origin/master, origin/HEAD) hanjt
4e21f2e38889f1be10df6c655a7007a68985e320 hanjt
ee519f32f0e543bf47c6b996d9fd91cd1078f253 hanjt
654cd93c466318d111c85658bedc4d692b665ba7 新增客户信息接口
814a236f0bb7140f2e00f61922404eee843423d3 项目初始化
18633338a8513b8cef33a896fe9c4c48719e62ae 删除文件 README.en.md
0380b1c635f482e8379bf655938c9e3a4ee769b5 Initial commit
使用-p参数来显示最近两次提交的差异,还可以用-2实现限制列出查询个数:
$ git log -p -1
commit f9289e866d7419e2c1f85294e268de625944fed7 (HEAD -> master)
Author: hanjt <hjt52163@163.com>
Date: Mon Oct 26 13:55:02 2020 +0800
修复了一个bug
diff --git a/src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java b/src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
index c2901fa..6700a10 100644
--- a/src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
+++ b/src/test/java/com/mydefined/ccrm/CcrmApplicationTests.java
@@ -11,6 +11,7 @@ public class CcrmApplicationTests {
@Test
public void contextLoads() {
+ printf()
}
}
如何撤销提交操作?
我们经常会遇到让人头疼的事情,需要撤销一些操作。
如果我们提交了之后才发现我们忘记了添加某些文件,或者写错了提交信息,这个时候你会重新尝试提交,可以使用--amend选项:
$ git commit --amend
该命令会提交暂存区的内容,如果你在上次提交之后,并没有做出任何改动,那么你的提交快照就不会发生变化,但是你可以改动提交信息。
下面演示,如何将已暂存的文件,移出暂存区:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ touch a.txt b.txt
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ ls
a.txt b.txt mvnw* mvnw.cmd pom.xml README.md src/
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git add *
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: a.txt
new file: b.txt
上述,我们新建了两个a和b文件,但是由于开发者一不小心,执行了全部添加暂存,这个时候,开发者才发现,b文件不需要提交,那么怎么办呢?
其实git已经给了我们提示,我们可以使用git reset HEAD <file>...
来将文件移出暂存区:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git reset HEAD b.txt
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: a.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
b.txt
这个时候我们发现git提示我们,b.txt还未暂存,这个时候,我们就可以达到撤销暂存区的操作了。
如何提交远程仓库?
为了能在任意 Git 项目上协作,你需要知道如何管理自己的远程仓库。 远程仓库是指托管在因特网或其他网络中的你的项目的版本库。 你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以读写。 与他人协作涉及管理远程仓库以及根据需要推送或拉取数据。 管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义它们是否被跟踪等等。
当我们完成了一个本地提交之后,需要将成果提交到远程仓库,这样其他的小伙伴才能从看到并使用你的成果。
当我们尝试向远程提交的时候,如果远程存在他人的最新修改,那么Git会提示我们要先更新他人的提交,这样后面才能完成自己的提交。
我们可以使用git fetch [remote-name]
命令来获取远程的数据。这条命令会从远程仓库获取本地仓库没有的数据。执行上述命令后,我们就可以在本地引用远程仓库的所有分支,并可以在任何时候哼或检查这些分支。该命令只会将这些数据拉取到本地,并不会自动合并。
我们还可以使用git pull
命令来自动获取远程数据,并自动完成合并本地当前分支。
当我们完成pull之后,我们就可以使用git push
命令来完成向远程提交成功:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git push origin master
Counting objects: 9, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (9/9), 608 bytes | 608.00 KiB/s, done.
Total 9 (delta 2), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-5.0]
To https://gitee.com/hanjintao/ccrm.git
84d3692..f9289e8 master -> master
git push [remote-name] [branch-name]
Tag标签(暂不做介绍)
如何创建分支?
当我们需要开始某个Bug修复工作的时候,我们往往需要为自己在本地新建一个分支,进而在新建的分支上完成自己的修改。记住:无论进行什么操作,开发者必须明确的知道自己当前在哪个分支上进行修改,如果你不想为自己制造麻烦的话,请务必时刻保持清醒。
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git branch bug-1
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git branch
bug-1
* master
我们可以看到,使用git branch [branch-name]
命令,来创建了一个bug-1的分支,但是当前Git的指针指向了master分支,所以我们在进行bug-1修复的时候,必须先将分支切换到bug-1分支:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git checkout bug-1
Switched to branch 'bug-1'
A a.txt
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (bug-1)
$ git branch
* bug-1
master
此时,Git已经将指针指向bug-1分支了,此时我们就可以在工作目录进行我们的修改了。我们也可以使用-b参数实现新建分支,并切换至新建的分支:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (bug-1)
$ git checkout -b bug-2
Switched to a new branch 'bug-2'
A a.txt
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (bug-2)
$ git branch
bug-1
* bug-2
master
执行完该命令后,我们发现,该命令新建了一个bug-2的分支,并且将分支切换到了该新建分支上。
如何合并分支?
当我们完成一个分支的需求开发的时候,我们需要将当前的分支修改合并到自己的主分支上面,并且应当及时删除自己的需求分支。分支的合并通常可以使用git merge
命令来实现,下面演示如何将bug-2分支合并到master分支:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (bug-2)
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git merge bug-2
Already up-to-date.
此时你已经完成了一次合并,这个时候bug-2分支,原则上来说对我们已经没有什么用处了,这个时候我们就可以删除这个分支了:
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git branch -d bug-2
Deleted branch bug-2 (was f9289e8).
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git branch
bug-1
* master
可以看到,这个时候bug-2分支已经被删除了,这个时候我们就已经完成了一次完整的需求开发了。
三方合并是如何工作的?
当我们完成bug-2的需求的时候,将其合并入master分支,这个时候你还在继续工作bug-1的需求的开发,假使这个问题花了你两天时间,当你完成bug-1的需求的时候,再使用以上的合并方式,你就会发现,现在的master分支已经不再是之前的bug-1分支的直接祖先了,这个时候Git会多做一些工作,与之前的做法不同,这个时候Git会基于三方合并,结果就是创建新的快照,然后再创建一个提交指向新建的快照,Git会自己判断最优的共同祖先并将其作为合并的基础。
基本的合并冲突如何处理?
当我们遇到两个分支都修改了同一个文件的同一部分的内容的时候,当我们执行git merge
命令合并的时候,会出现合并冲突,这个时候Git会暂停合并处理,等待我们去解决合并的冲突后再来合并,那么我们该怎么解决冲突呢?
这个时候我们执行完合并命令发现有冲突的时候,我们应该使用git status
命令来查看哪些文件还没有合并,任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。 出现冲突的文件会包含一些特殊区段,看起来像下面这个样子:
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
这表示 HEAD 所指示的版本(也就是你的 master 分支所在的位置,因为你在运行 merge 命令的时候已经检出到了这个分支)在这个区段的上半部分(======= 的上半部分),而 iss53 分支所指示的版本在 ======= 的下半部分。 为了解决冲突,你必须选择使用由 ======= 分割的两部分中的一个,或者你也可以自行合并这些内容。 例如,你可以通过把这段内容换成下面的样子来解决冲突:
<div id="footer">
please contact us at email.support@github.com
</div>
上述的冲突解决方案仅保留了其中一个分支的修改,并且 <<<<<<< , ======= , 和 >>>>>>> 这些行被完全删除了。 在你解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。
如何快速查看分支的最新的提交?
Genttle · Han@DESKTOP-CUFNBN0 MINGW64 ~/Desktop/git/ccrm (master)
$ git branch -v
bug-1 f9289e8 修复了一个bug
* master f9289e8 修复了一个bug
变基操作?
变基操作新手不建议使用,推荐使用git merge
命令来实现分支的合并