本文引用:Git学习
例子
例1:张三和李四需要对图书馆中同一本书,进行修改。
图书馆会将这本书最新修订版本分发给张三和李四。
张三,李四修改完成后送给图书馆。
问题:倘若不存在图书馆,或图书馆无意间被毁了,怎么办?张三和李四无法得知互相修改内容?
例2:一本正在编写的书需要张三和李四编写。
张三在自己的书上写了最新内容,李四也在自己的书上写了最新内容。
两个人只需把各自修改的地方告诉对方即可。之后两个人手中的书都是最新内容。
减少了图书馆,也减少很多不确定因素。
背景
Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。
Linus虽然创建了Linux,但Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码是如何管理的呢?
事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!
你也许会想,为什么Linus不把Linux代码放到版本控制系统里呢?不是有CVS、SVN这些免费的版本控制系统吗?因为Linus坚定地反对CVS和SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比CVS、SVN好用,但那是付费的,和Linux的开源精神不符。
不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。
安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。
Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的:
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由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 <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
使用
workspace:工作区
staging area:暂存区/缓存区
local repository:版本库或本地仓库
remote repository:远程仓库
基本使用
命令 | 说明 |
---|---|
git init | 初始化仓库 |
git clone | 拷贝一份远程仓库,也就是下载一个项目。。 |
git add | 添加文件到暂存区 |
git status | 查看仓库当前的状态,显示有变更的文件。 |
git diff | 比较文件的不同,即暂存区和工作区的差异。 |
git commit | 提交暂存区到本地仓库。 |
git rebase | 可用于commit压缩(合并多个commit为单个commit) |
git reset | 回退版本。 |
git rm | 删除工作区文件。 |
git mv | 移动或重命名工作区文件。 |
git log | 查看历史提交记录 |
git blame <file> | 以列表形式查看指定文件的历史修改记录 |
git remote | 远程仓库操作 |
git fetch | 从远程获取代码库 |
git pull | 下载远程代码并合并 |
git push | 上传远程代码并合并 |
git branch [new branch name] | 没有参数时,git branch 会列出你在本地的分支。<br />指定 [new branch name]则会在本地创建新分支 |
git checkout [-b] (branch name) | 执行:git checkout (branch name)时会切换到指定分支。<br />执行:git checkout (new branch name)时会创建新分支并切换到该分支上。 |
git branch -d (branch name) | 删除本地指定分支 |
git push origin --delete (remote branch name) | 删除远程分支,同时也会删除本地分支 |
git merge (other branch name) | 合并分支:将其他分支内容合并当前分支中 |
使用
git --help
查看更多命令
- git初始化:
git init
# 在一个目录执行,会生成.git目录文件 - 克隆远程仓库代码到本地:
-
git clone http://xxx
# 执行完成后,会在所在目录下生成一个名称为项目名称的目录/文件夹
-
- 拉取远程仓库最新代码:
-
git pull
# 此步骤更多的是在执行git push
前执行,否则可能会提示merge或push失败。
-
- 提交文件到远程仓库:假定当前所在分支:
main
- 将文件加到暂存区:
git add test.txt
- 将文件提交到本地仓库:
git commit -m "add test.txt file"
- 推送到远程仓库:
git push -u origin main
- 将文件加到暂存区:
- 查看仓库地址
git remote -v
- 查看分支
- 查看本地分支:
git branch
- 查看所有分支(包含远程分支):
git branch -a
- 查看本地分支:
- 删除分支
- 本地分支:
git branch -d (branch name)
- 远程分支:
git push origin -d (branch name)
- 本地分支:
高级使用
分支合并
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test) ## 当前分支:main分支
$ vi test.txt ## 编辑内容
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test)
$ echo "test merge branch" >> merge.txt ## 新增一个merge.txt文件
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test)
$ ls
README.md merge.txt test.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test)
$ git add . ## add所有修改到暂存区
warning: LF will be replaced by CRLF in merge.txt.
The file will have its original line endings in your working directory
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test)
$ git commit -m "test merge" ## 提交到本地仓库
[test 2af25aa] test merge
2 files changed, 3 insertions(+)
create mode 100644 merge.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test)
$ git checkout main ## 切换到main分支
Switched to branch 'main'
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main) ## 当前分支:main分支
$ ls ## 此时main分支下只有2个文件
README.md test.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ cat test.txt ## 查看test.txt也没有新内容
test in main branch
test cherry-pick
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git merge test ## 将test分支合并到当前的main分支
CONFLICT (add/add): Merge conflict in test.txt
Auto-merging test.txt
Automatic merge failed; fix conflicts and then commit the result.
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ ls ## 再次查看,发现多了个文件
README.md merge.txt test.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ cat test.txt ## 再次查看文件内容,发现新增了其他内容。并产生合并冲突
## 1. 之前执行git merge test时已经提示有冲突,关键字:CONFLICT)。
## 2. 从当前分支名称(main|MERGING)也能区分产生冲突
test in main branch
test cherry-pick
<<<<<<< HEAD
=======
============
test branch merge
>>>>>>> test
合并出现分支冲突
合并过程中,出现文件内容冲突问题。
合并时出现CONFLICT
字样。
- 执行合并命令
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git merge test
CONFLICT (add/add): Merge conflict in test.txt
Auto-merging test.txt
Automatic merge failed; fix conflicts and then commit the result.
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ cat test.txt
test in main branch
test cherry-pick
<<<<<<< HEAD
=======
============
test branch merge
>>>>>>> test
- 手动修改冲突内容
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ git diff ## 查看不同
diff --cc test.txt
index 2c9058d,ef3349c..0000000
--- a/test.txt
+++ b/test.txt
@@@ -1,3 -1,5 +1,8 @@@
test in main branch
test cherry-pick
++<<<<<<< HEAD
++=======
+ ============
+ test branch merge
++>>>>>>> test
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ vi test.txt ## 编辑冲突文件,解决冲突
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ git diff ## 查看不同
diff --cc test.txt
index 2c9058d,ef3349c..0000000
--- a/test.txt
+++ b/test.txt
@@@ -1,3 -1,5 +1,5 @@@
test in main branch
test cherry-pick
-============
++==================
+ test branch merge
## 以上内容,可以看出删除(“-”减号表示)和新增(“+”减号表示)
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ git status -s ## 检查状态
A merge.txt
AA test.txt
## 此时AA是红色表示
- 标记冲突已解决:用 git add 要告诉 Git 文件冲突已经解决
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ git add test.txt ## add到暂存区,此操作会告知git表示已经解决冲突文件
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ git status -s ## 再次检查状态
A merge.txt
M test.txt
## 此时M是绿色表示
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main|MERGING)
$ git commit ## 执行commit,此时不需要加-m参数。会自动进入另一个窗口。提示输入commit信息
[main 6b431d6] Merge branch 'test' solve conflict
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git push -u origin main ## 推送到远程仓库
Enumerating objects: 12, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 4 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (8/8), 790 bytes | 263.00 KiB/s, done.
Total 8 (delta 2), reused 0 (delta 0), pack-reused 0
To http://gitlab.adorado.top/gaoqihua/test-parent.git
5f90207..6b431d6 main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.
合并单个文件到指定分支
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main) ## 当前分支:main分支
$ ls
README.md test.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git checkout test
Switched to branch 'test'
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test) ## 当前分支:test分支
$ ls ## 查看文件,此时没有test.txt文件
README.md
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test)
$ git checkout main test.txt ## 使用checkout命令,将main分支中根目录下text.txt文件合并过来
Updated 1 path from fefc1a5
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test)
$ ls ## 再次查看文件,已经存在了test.txt文件
README.md test.txt
合并commit
合并单个commit到其他分支
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test) ## 当前分支:test分支
$ git log --oneline
bc82781 (HEAD -> test, origin/test) test cherry-pick ## 在test分支上新提交的内容。将此提交合并到main分支。log id:bc82781
744cf22 merge test
8fc010f Initial commit
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (test)
$ git checkout main ## 切换到main分支
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main) ## 当前分支:main分支
$ git cherry-pick bc82781 ## 执行cherry-pick,指定log id。将指定commit合并到当前分支
[main f4533ec] test cherry-pick
Date: Mon Feb 28 15:26:42 2022 +0800
1 file changed, 2 insertions(+)
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ cat test.txt
test in main branch
test cherry-pick
多个commit成一个commit
- 场景:有时合并分支时,由于分支commit太多,会造成合并后main分支log太多,需要合并分支前先合并commit
$ git log --oneline
4ee51d6 docs(user): update user/README.md
176ba5d docs(user): update user/README.md
5e829f8 docs(user): add README.md for user
f40929f feat(user): add delete user function
fc70a21 feat(user): add create user function
7157e9e docs(docs): append test line 'update3' to README.md
5a26aa2 docs(docs): append test line 'update2' to README.md
55892fa docs(docs): append test line 'update1' to README.md
89651d4 docs(doc): add README.md
可以看到我们提交了 5 个 commit。接下来,我们需要将 feature/user分支的改动合并到 master 分支,但是 5 个 commit 太多了,我们想将这些 commit 合并后再提交到 master 分支。
接着,我们合并所有 commit。在上一步中,我们知道 fc70a21是 feature/user分支的第一个 commit ID,其父 commit ID 是 7157e9e,我们需要将7157e9e之前的所有分支 进行合并,这时我们可以执行:
$ git rebase -i 7157e9e
执行命令后,我们会进入到一个交互界面,在该界面中,我们可以将需要合并的 4 个 commit,都执行 squash 操作,如下图所示:
修改完成后执行:wq 保存,会跳转到一个新的交互页面,在该页面,我们可以编辑 Commit Message,编辑后的内容如下图所示:
开头的行是 git 的注释,我们可以忽略掉,在 rebase 后,这些行将会消失掉。修改完成后执行:wq 保存,就完成了合并提交操作。
除此之外,这里有 2 个点需要我们注意:
- git rebase -i 这里的一定要是需要合并 commit 中最旧 commit 的父 commit ID。
- 我们希望将 feature/user 分支的 5 个 commit 合并到一个 commit,在 git rebase 时,需要保证其中最新的一个 commit 是 pick 状态,这样我们才可以将其他 4 个 commit 合并进去。
然后,我们用如下命令来检查 commits 是否成功合并。可以看到,我们成功将 5 个 commit 合并成为了一个 commit:d6b17e0
。
$ git log --oneline
d6b17e0 feat(user): add user module with all function implements
7157e9e docs(docs): append test line 'update3' to README.md
5a26aa2 docs(docs): append test line 'update2' to README.md
55892fa docs(docs): append test line 'update1' to README.md
89651d4 docs(doc): add README.md
此时,就可以将d6b17e0
合并到其他分支即可。
撤销add
撤销所有add
针对add时,不小心将很多文件都add了。需要撤销这些add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ vi test.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git diff ## 编辑完成后使用diff查看不同
diff --git a/test.txt b/test.txt
index 2c9058d..3a91585 100644
--- a/test.txt
+++ b/test.txt
@@ -1,3 +1,5 @@
test in main branch
test cherry-pick
+===================================
+test: cancel add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git add test.txt ## 执行add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git diff ## 执行add后查看不同,显示没有任何不同。说明没有新的修改需要add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git reset head ## 撤销,将整体修改撤销add操作
Unstaged changes after reset:
M test.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git diff ## 再次查看不同,发现已经恢复到跟之前修改后执行diff结果一样
diff --git a/test.txt b/test.txt
index 2c9058d..3a91585 100644
--- a/test.txt
+++ b/test.txt
@@ -1,3 +1,5 @@
test in main branch
test cherry-pick
+===================================
+test: cancel add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$
撤销单个文件
针对add时,不小心包含个别文件,只需要对个人文件撤销add。如果执行git reset head
很不划算。
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ vi test.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git diff ## 编辑完成后使用diff查看不同
diff --git a/test.txt b/test.txt
index 2c9058d..3a91585 100644
--- a/test.txt
+++ b/test.txt
@@ -1,3 +1,5 @@
test in main branch
test cherry-pick
+===================================
+test: cancel add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git add test.txt ## 执行add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git diff ## 执行add后查看不同,显示没有任何不同。说明没有新的修改需要add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git reset head test.txt ## 只需要指定撤销的文件即可。
Unstaged changes after reset:
M test.txt
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git diff
diff --git a/test.txt b/test.txt
index 2c9058d..3a91585 100644
--- a/test.txt
+++ b/test.txt
@@ -1,3 +1,5 @@
test in main branch
test cherry-pick
+===================================
+test: cancel add
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$
- 如果此时发现该文件不应该被修改,恢复修改前内容。可以执行以下内容:
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git checkout -- test.txt ## 将文件回滚到上一次操作
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ git diff
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$ cat test.txt
test in main branch
test cherry-pick
Administrator@DESKTOP-G03KTBR MINGW64 /e/ideaWorkSpace/sky/test-parent (main)
$
撤销commit
-
--mixed
撤销到add之前的状态
保留 本地仓库、暂存区、以及工作区的代码,即回到 git add . 操作之前
的状态
-
--soft
撤销到add之后,commit之前的状态
保留 暂存区、工作区的代码,即回到git commit -m "提交信息" 操作之前、 git add . 操作之后
的状态
-
--hard
撤销到修改之前的代码:add之前的修改都会丢失。
不会保留提交的代码,即会造成commit的代码以及工作区的代码丢失,一朝回到解放前,慎用!
- 假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本
git fetch origin
git reset --hard origin/main
忽略文件
推送时,防止临时文件或IDE产生的文件被提交到git远程仓库
- 使用
.gitignore
文件- 在项目根目录下创建
.gitignore
文件 - 将需要忽略的文件名称填写到该文件即可
- 在项目根目录下创建
- 示例:
## 忽略项目跟目录下target目录
target/
## 忽略扩展名为log的文件
**.log
## 忽略文件名包含关键字的文件
*mvn*
## 指定忽略文件
### 忽略根目录下files子目录下aa目录下的test.txt。此时如果aa目录还有其他文件,则会被提交到git远程仓库
files/aa/test.txt
标签
像其他版本控制系统(VCS)一样,Git 可以给仓库历史中的某一个提交打上标签,以示重要。 比较有代表性的是人们会使用这个功能来标记发布结点( v1.0 、 v2.0 等等)
可以用此功能标记项目版本问题。
- 查看所有tag:
git tag
- 创建标签:
git tag -a [tag name] -m [tag message]
- 例如:
git tag -a v1.0 -m "my version 1.0"
- 例如:
- 查看标签信息:
git show v1.0
# 就会显示标签信息,创建时间等
特别时,新功能发布,多个bug修复时最好创建标签。
分支管理
- main:主分支,在gitlab/github上创建新项目时自动生成分支
- dev:开发分支
- fat(Feature Acceptance Test Environment: 功能验收测试环境):测试环境分支
- uat(User Acceptance Test Environment: 用户验收测试环境):用于生产环境下软件测试使用(环境跟生产保持一致)
- hotfix: 紧急问题修复。
- 针对生产环境出现紧急问题进行修复。基于main分支拉取hotfix分支进行紧急问题修复;
- feature/devX: 新需求开发分支。
- 当有新需求时,基于dev分支创建feature/devX分支
- 该分支也有自己的日常新需求验证环境,新需求开发完成,会将该分支代码合并回主开发分支dev上,经fat测试完成后实现交付。
参考:
Git的诞生
Git 工作区、暂存区和版本库 [菜鸟教程]
Git基本操作 [菜鸟教程]
git cherry-pick 教程 [阮一峰的网络日志]
git 合并多个commit
Git撤销已经提交的 commit
Git工作流的分支管理的解决方案
Git 基础 - 打标签
Git 分支 - 分支管理
分布式 Git - 分布式工作流程