快速跳转目录
文章所有操作均使用Git Bash
版本库
版本库又名仓库(Repository),你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
创建版本库
使用git init 命令创建版本库,当看到了.git文件,说明仓库已经建立了
$ pwd
/c/Users/Lenovo/Desktop/learnGit
$ git init
Initialized empty Git repository in C:/Users/Lenovo/Desktop/learnGit/.git/
$ ls -a
./ ../ .git/
文件添加与提交
使用git add命令添加文件,可以多次添加,也可以git add . 添加全部有改动的文件 。
$ ls
readme.txt
$ git add readme.txt
warning: LF will be replaced by CRLF in readme.txt.
The file will have its original line endings in your working directory
使用git commit 提交文件,提交完文件之后,本地你的这个版本库就视为更新了一版了。-m参数后面带着的是本次提交的说明。很多DevOps平台就是通过解析这个来进行需求完成的管理的。
$ git commit -m "提交文件"
[master (root-commit) 7697fe6] 提交文件
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
管理版本(重要)
仓库当前的状态
使用git status查看仓库当前的状态
$ git status
On branch master
nothing to commit, working tree clean
修改文件后再使用git status,可见有一个文件显示modified,被修改。
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
git status命令显示文件被修改,可以使用git diff查看修改内容。+是增加 的行,-是减少的行。
$ git diff
warning: LF will be replaced by CRLF in readme.txt.
The file will have its original line endings in your working directory
diff --git a/readme.txt b/readme.txt
index 46d49bf..9373e1d 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,4 @@
Git is a version control system.
Git is free software.
+Edit
+
版本控制
在Git中,我们用git log命令查看系统的版本迭代历史。
这个命令的输出显示的是最近到最远的版本提交历史,其中第一行是Commit ID。Head->master指的是master分支的当前版本在这个提交。
$ git log
commit 6f081103c2b4ce066ccc15cdb0c039e6744a6c3b (HEAD -> master)
Author: ZeroDegreeLightYear <1481557812@qq.com>
Date: Sun Sep 22 18:10:02 2024 +0800
second
commit 7697fe60402bab75ec6e732c09fd051d3bc274b7
Author: ZeroDegreeLightYear <1481557812@qq.com>
Date: Sun Sep 22 17:41:18 2024 +0800
提交文件
这样显得有些乱,我们可以使用这样一个参数
十分简洁地表现提交历史
$ git log --pretty=oneline
6f081103c2b4ce066ccc15cdb0c039e6744a6c3b (HEAD -> master) second
7697fe60402bab75ec6e732c09fd051d3bc274b7 提交文件
既然看到了版本历史,那么肯定要进行版本回滚。
我们使用git reset来进行版本回滚。
$ git reset --hard HEAD^
HEAD is now at 7697fe6 提交文件
可以见到,这个命令使我们的系统回滚了一个版本。实际上,这个命令有很多参数的变化。
- 在Git中,用HEAD表示当前版本,上一个版本就是HEAD^
,上上一个版本就是HEAD^^ ,当然往上100个版本写100个 ^ 比较容易数不过来,所以写成HEAD~100。 - –hard会回退到上个版本的已提交状态(已经commit完了),而–soft会回退到上个版本的未提交状态(没有add也没有commit),–mixed会回退到上个版本已添加但未提交的状态(add了但是还没有commit)。
当然,除了使用HEAD指针(Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD指向不同的版本)之外,我们可以直接写上commit id:
版本号写前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
$ git reset --hard 6f08
HEAD is now at 6f08110 second
$ git log
commit 6f081103c2b4ce066ccc15cdb0c039e6744a6c3b (HEAD -> master)
Author: ZeroDegreeLightYear <1481557812@qq.com>
Date: Sun Sep 22 18:10:02 2024 +0800
second
commit 7697fe60402bab75ec6e732c09fd051d3bc274b7
Author: ZeroDegreeLightYear <1481557812@qq.com>
Date: Sun Sep 22 17:41:18 2024 +0800
提交文件
灵活使用这两个参数,可以在不同版本之间往来自如。
但是版本回去了,新的版本去哪了?
使用git log查看,可以发现新版的记录已经没了。
$ git log
commit 7697fe60402bab75ec6e732c09fd051d3bc274b7 (HEAD -> master)
Author: ZeroDegreeLightYear <1481557812@qq.com>
Date: Sun Sep 22 17:41:18 2024 +0800
提交文件
这种情况下我们有两种办法。
- 在Shell关闭之前,翻到上面去,复制commit id
- 正确且好用的办法,使用git reflog,这个命令可以记录每次操作的命令和HEAD指针的位置
可见每行的最前面就是HEAD指针指向哪一次提交。
$ git reflog
6f08110 (HEAD -> master) HEAD@{0}: reset: moving to 6f08
7697fe6 HEAD@{1}: reset: moving to HEAD^
6f08110 (HEAD -> master) HEAD@{2}: commit: second
7697fe6 HEAD@{3}: commit (initial): 提交文件
- 有了git reflog之后,我们才能说,所有的提交,可以来往自如了。
add和commit如何运行——工作区与暂存区&管理修改
图像来自廖雪峰官方网站
暂存区就是add之后文件在的位置,可以直接add全部修改,也可以一个个add修改,但是commit是全部一起commit(实际上这有点问题,因为没法保证我们这次的工作都是需要提交的,可以看看后面搁置当前工作时用的git stash命令)。文件在暂存区和工作区时,使用git status的颜色是不一样的。
每次add或者是commit的,都是对文件的修改,+or-,即增加一行或者是减少一行。当我们使用git diff或者是git status时,常常能看见+or-这种符号。
撤销修改
常常遇到我们写的东西不想要了或是有些错误,想改掉。这个改动的方法在不同的阶段,有不同的做法。
- 在工作区(未add)
使用git checkout – (git checkout – file命令中的–很重要,没有–,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。)
或者是 **git resore **
这两者没有本质区别,只是现在的git新版本的提示里已经推荐使用git restore了。
$ cat readme.txt
Git is a version control system.
Git is free software.
Edit
gogogo
$ git checkout -- readme.txt
$ cat readme.txt
Git is a version control system.
Git is free software.
Edit
可见git restore起到的是一样的效果。
$ cat readme.txt
Git is a version control system.
Git is free software.
Edit
gogogo
$ git restore readme.txt
$ cat readme.txt
Git is a version control system.
Git is free software.
Edit
- 在暂存区(已经add了但未commit)
类似地,使用git reset HEAD 或者是git restore --staged
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
$ git restore --staged readme.txt
操作前后的效果,可以看到整个文件从绿色变为红色,表明从暂存区回到了工作区,可以参照前面的方法解决。
可见git restore --staged也能起到类似的作用。
-
在本地仓库了(已经commit了但未push)
这种情况使用之前的版本回退即可。 -
在远程仓库(已经push了)
对于这种情况,只能说,历史已经永远地记录了,你可以修改,但是记录永远在。
删除与找回
删除
当我们想要删除某个文件时,只需做两件事情
- 手动删除文件,rm命令或者是直接使用GUI删掉。
- git rm命令删除
- 使用git commit提交
$ rm dele.txt
$ git rm dele.txt
rm 'dele.txt'
$ git commit -m "commit dele"
[master 0d21216] commit dele
2 files changed, 1 insertion(+), 2 deletions(-)
delete mode 100644 dele.txt
找回&git checkout的本质
在未进行git rm之前,我们可以使用git checkout – 来找回被误删的文件。
$ ls
deleback.txt readme.txt
$ rm deleback.txt
$ git checkout -- deleback.txt
$ ls
deleback.txt readme.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
远程仓库
添加远程库(本地->远程)
以gitee为例
- 首先创建远程的仓库
把这些都设置好了
- 创建好后,会出现提示
可见,里面有三个提示,提示很全面。
第一是关于git的全局配置,好奇可以看结尾的第三个参考文档
第二个是如果本地没有仓库的全程办法
第三个就是本地有仓库的办法
按照提示执行即可
从远程库克隆(远程->本地)
一般来说,这种情况都是别人已经有一个仓库,拉取别人的仓库用。这种情况就更简单了。
自己准备一个文件夹,找到仓库里的下载按钮,复制url,然后在本地准备好的文件夹执行即可。
分支(重要)
创建分支与合并
此处要更正前面一个错误的点,前面为了好理解,说HEAD指向提交。实际上严格来说,指向提交的是分支,HEAD指向某个分支。
我们使用git branch 查看当前有哪些分支。
$ git branch
* master
同时,使用git branch < name >创建分支
$ git branch dev
$ git branch
dev
* master
可以看到,当前的HEAD指向的是master分支。我们想要切换分支,可以使用git checkout 或者是 git switch。
$ git switch dev
Switched to branch 'dev'
$ git branch
* dev
master
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git branch
dev
* master
为了防止和git checkout – 混淆,建议我们常用git switch。
既然创建和切换都有了,可以一步到位,创建并切换分支。
使用git checkout -b和git switch -c
$ git checkout -b new1
Switched to a new branch 'new1'
$ git branch
dev
master
* new1
$ git switch -c new2
Switched to a new branch 'new2'
$ git branch
dev
master
new1
* new2
分支创建与切换齐全了,现在可以说如何合并了。
使用git merge合并分支。
在dev分支上做点改动,进行提交,然后切回maser,把dev的改动合并到maser上。
$ git switch master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git merge dev
Updating fc04a8f..7d8acd7
Fast-forward
readme.txt | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
可以看到,这次的提交是Fast-forward快进模式,merge一共有三种模式,在后面的分支管理策略中会给出这些的区别。
当然,有时候在开发完成merge之后,就不需要分支了,可以使用git branch -d删掉分支。
$ git branch -d new1
Deleted branch new1 (was fc04a8f).
$ git branch
dev
* master
new2
解决冲突
有合并就会有冲突,当两个合并的分支的某个修改针对的相同的行,这时候合并就会失败。
dev分支和master分支都修改的相同的文件,此时往master分支合并dev。
$ git merge dev
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
可以看到出现了冲突。
通过git status可以看到对冲突解决方法的提示。
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
- 使用git merge --abort会到解决冲突之前的状态。但是注意,这种方法也有弊端(原文在此)。
该命令仅仅在合并后导致冲突时才使用。git merge --abort将会抛弃合并过程并且尝试重建合并前的状态。但是,当合并开始时如果存在未commit的文件,git merge --abort在某些情况下将无法重现合并前的状态。(特别是这些未commit的文件在合并的过程中将会被修改时)
警告:运行git-merge时含有大量的未commit文件很容易让你陷入困境,这将使你在冲突中难以回退。因此非常不鼓励在使用git-merge时存在未commit的文件,建议使用git-stash命令将这些未commit文件暂存起来,并在解决冲突以后使用git stash pop把这些未commit文件还原出来。
- 手动解决冲突之后重新提交。
刚刚的提示说两个分支都修改了readme.txt,那就可以去看看readme。
$ vim readme.txt
Git is a version control system.
Git is free software.
Edit
<<<<<<< HEAD
new1 edit
=======
dev edit
>>>>>>> dev
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改到我们满意的结果后保存:
Git is a version control system.
Git is free software.
Edit
new1 edit
dev edit
保存后要add并commit才能起作用。
最后,使用终极版git log命令
git log --graph --pretty=oneline --abbrev-commit
git log:这是 Git 中用来查看提交历史的命令。默认情况下,它会列出所有分支的提交历史,但通常会按照你当前所在的分支进行排序。
–graph:这个选项会在每个提交旁边显示一个图形化的表示,展示分支和合并的历史。这使得查看复杂的分支和合并情况变得更容易。图形通常通过 ASCII 字符来表示,比如 * 表示一个提交,| 表示一个分支,/ 和 \ 表示分支的合并。
–pretty=oneline:这个选项会以一种更紧凑的格式显示提交历史,每个提交只显示一行。默认情况下,git log 会显示提交的哈希值、作者的姓名和邮箱地址、提交日期以及提交信息。使用 --pretty=oneline 后,它只会显示提交的哈希值(通常是前7个字符,但可以通过 --abbrev-commit 进一步缩短)和提交信息的第一行。
–abbrev-commit:这个选项会让 Git 在显示提交哈希值时,使用尽可能短的缩写形式,默认是前7个字符,但如果这些字符不足以确保唯一性,Git 会增加字符数直到能够唯一标识提交为止。然而,当与 --pretty=oneline 一起使用时,–abbrev-commit 通常会使得每个提交的哈希值缩短到更少的字符(通常是4个字符),这取决于你的 Git 配置和仓库的历史。
这个命令可以看到带合并信息的提交情况
$ git log --graph --pretty=oneline --abbrev-commit
* 528388a (HEAD -> master) fix confilct
|\
| * b670784 (dev) dev edit
* | 500696a new1 edit
|/
* 7d8acd7 test merge
* fc04a8f (origin/master, new2) deleback
* 0d21216 commit dele
* be823a1 add dele
* 6f08110 second
* 7697fe6 提交文件
可见解决冲突后的提交形成了一个新结点。
合并模式
之前看到的Fast=forward模式有一给问题,就是合并的地方看不出来有合并的痕迹,只是简单的将指针移动了。 Fast forward这个提交就只有一条记录,没有合并的痕迹。
$ git merge dev
Updating 528388a..b732750
Fast-forward
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --graph --pretty=oneline --abbrev-commit
* b732750 (HEAD -> master, dev) Fast forward
* 528388a fix confilct
|\
| * b670784 dev edit
* | 500696a new1 edit
|/
* 7d8acd7 test merge
* fc04a8f (origin/master, new2) deleback
* 0d21216 commit dele
* be823a1 add dele
* 6f08110 second
* 7697fe6 提交文件
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
可以使用方式的git merge --no-ff:
主要产生了两点区别:
- 首先可以看出,合并形成了新节点,可以看到明显的合并痕迹
- 其次,可以看到dev分支的指针并没有到这个新节点来,还在原来的位置。
$ git merge --no-ff -m " Merge branch 'dev'" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
* a157bb1 (HEAD -> master) Merge branch 'dev'
|\
| * e958062 (dev) noff
|/
* b732750 Fast forward
* 528388a fix confilct
|\
| * b670784 dev edit
* | 500696a new1 edit
|/
* 7d8acd7 test merge
* fc04a8f (origin/master, new2) deleback
* 0d21216 commit dele
* be823a1 add dele
* 6f08110 second
* 7697fe6 提交文件
实际上还有一种squash的模式,使用方法如下
1、 切换至 master分支
2、 git merge origin/develop --squash
3、 git commit -m "问题修改--测试ok"
– 将多个commit 记录合并成一个,可用于
本地提交测试后合并至远程分支,只留下一个commit 记录
develop分支merge至主线分支时,只有一个 commit 记录,代码整洁
例如这种情况,dev上进行了三次commit
$ git log --graph --pretty=oneline --abbrev-commit
* 2bfc78f (HEAD -> dev) squash3
* 9abb160 squash2
* bd0f9dd squash1
* a157bb1 (master) Merge branch 'dev'
|\
| * e958062 noff
|/
* b732750 Fast forward
* 528388a fix confilct
|\
| * b670784 dev edit
* | 500696a new1 edit
|/
* 7d8acd7 test merge
* fc04a8f (origin/master, new2) deleback
* 0d21216 commit dele
* be823a1 add dele
* 6f08110 second
* 7697fe6 提交文件
切换回master,进行合并
$ git merge dev --squash
Updating a157bb1..2bfc78f
Fast-forward
Squash commit -- not updating HEAD
readme.txt | 3 +++
1 file changed, 3 insertions(+)
$ git commit -m "all squash"
[master 1b89e20] all squash
1 file changed, 3 insertions(+)
可见dev的三次commit被合并了
$ git log --graph --pretty=oneline --abbrev-commit
* 1b89e20 (HEAD -> master) all squash
* a157bb1 Merge branch 'dev'
|\
| * e958062 noff
|/
* b732750 Fast forward
* 528388a fix confilct
|\
| * b670784 dev edit
* | 500696a new1 edit
|/
* 7d8acd7 test merge
* fc04a8f (origin/master, new2) deleback
* 0d21216 commit dele
* be823a1 add dele
* 6f08110 second
* 7697fe6 提交文件
bug修复
前置知识
所有未提交(commit)的修改,对于所有分支都是可见的,一旦某个改动被commit,那么这个改动才会被某个分支所独占。
即,工作区和暂存区对于仓库来说实际上是相对独立的,是可以面向任何分支的。这种情形下,面临一个新需求的来临,暂存工作区和暂存区实际上是必要的。
可见,在master、分支更改了文件,在dev分支依然可以看到,在dev分支add之后,在master分支依然可以看到,印证了上面的观点。
搁置当前的工作
我们都遇到过这种情况,当前的工作还没有完成,但是一个很急的需求或者是bug来了,怎么办?
使用git stash
暂存之后,原来的工作就保存好了,同时也不会影响新的工作。
$ git stash
Saved working directory and index state WIP on master: 1b89e20 all squash
$ git status
On branch master
Your branch is ahead of 'origin/master' by 8 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
可以使用git stash list查看保存的工作现场。
$ git stash list
stash@{0}: WIP on master: 1b89e20 all squash
好了,当我们新的工作处理完之后,想要取出这些工作。有两种方法。
这两种方法等价
这三个命令不带参数,默认是最新的stash的。也可以带上参数stash@{0},即stash list最前的标号,指定某次stash。
- git stash apply + git stash drop
$ git stash apply stash@{0}
On branch master
Your branch is ahead of 'origin/master' by 8 commits.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git stash drop
Dropped refs/stash@{0} (a5b1d92784f2afb60984300bd377d3dfe19d8d65)
- git stash pop
$ git stash pop stash@{0}
On branch master
Your branch is ahead of 'origin/master' by 8 commits.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{0} (c045df69108303a873cfaff44cd9e4e472acc6d5)
转移一个特定的提交
当bug修好之后,很自然地想到,要把修好bug的代码应用到不同的分支去。
此时,使用git cherry-pick命令,将单独的某个提交应用到另一个分支。
先在dev上提交一个改动
$ git log --graph --pretty=oneline --abbrev-commit
* 4ca9c73 (HEAD -> dev) try cherrypick
* 0f31566 (master) Merge branch 'master' into dev
|\
| * 1b89e20 all squash
* | 2bfc78f squash3
* | 9abb160 squash2
* | bd0f9dd squash1
|/
* a157bb1 Merge branch 'dev'
|\
| * e958062 noff
|/
* b732750 Fast forward
* 528388a fix confilct
|\
| * b670784 dev edit
* | 500696a new1 edit
|/
* 7d8acd7 test merge
* fc04a8f (origin/master, new2) deleback
* 0d21216 commit dele
* be823a1 add dele
* 6f08110 second
* 7697fe6 提交文件
在master上应用这个改动
$ git cherry-pick 4ca9c73
[master 2ef1843] try cherrypick
Date: Tue Sep 24 17:21:19 2024 +0800
2 files changed, 2 insertions(+)
create mode 100644 cherry.txt
可见,这次try-cherry的改动被应用到了master。值得注意的是,两者的commit id不同。
$ git log --graph --pretty=oneline --abbrev-commit
* 2ef1843 (HEAD -> master) try cherrypick
* 0f31566 Merge branch 'master' into dev
|\
| * 1b89e20 all squash
* | 2bfc78f squash3
* | 9abb160 squash2
* | bd0f9dd squash1
|/
* a157bb1 Merge branch 'dev'
|\
| * e958062 noff
|/
* b732750 Fast forward
* 528388a fix confilct
|\
| * b670784 dev edit
* | 500696a new1 edit
|/
* 7d8acd7 test merge
* fc04a8f (origin/master, new2) deleback
* 0d21216 commit dele
* be823a1 add dele
* 6f08110 second
* 7697fe6 提交文件
新需求的开发
开发新功能的一般流程
每个新功能最好建立一个新分支,完成之后再删掉新分支。
建立新分支
$ git switch -c feature
Switched to a new branch 'feature'
完成更改
$ vim feature.txt
添加和提交
$ git add .
warning: LF will be replaced by CRLF in feature.txt.
The file will have its original line endings in your working directory
$ git commit "-m" feature
[feature 4ac0e67] feature
2 files changed, 2 insertions(+)
create mode 100644 feature.txt
合并
(实际上这个过程大概率存在合并冲突,得手动解决)
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 13 commits.
(use "git push" to publish your local commits)
$ git merge feature
Updating 2ef1843..4ac0e67
Fast-forward
feature.txt | 1 +
readme.txt | 1 +
2 files changed, 2 insertions(+)
create mode 100644 feature.txt
删除分支
- 分支已经被合并了,使用git branch -d删除
$ git branch -d feature
Deleted branch feature (was 4ac0e67).
- 有提交的分支没有被合并就删除了,使用git branch -D强行删除
$ git branch -d test
error: The branch 'test' is not fully merged.
If you are sure you want to delete it, run 'git branch -D test'.
$ git branch -D test
Deleted branch test (was 53d564b).
推送&拉取&协作
一般来说,本地有分支,远程也有分支。git会自动把本地和远程同名分支对应起来。 (事实上并不会,详见分支对应)
使用git remote查看远程分支信息
$ git remote
origin
用git remote -v显示更详细的信息
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
完成后,我们使用git push origin master向远程的origin推送master分支
git push origin master
类似的,也可以推送dev分支。
并不是所有分支都必须推送到远程。
在版本控制系统中,特别是使用Git这样的分布式版本控制系统时,推送(push)操作是将本地仓库的更改同步到远程仓库的过程。关于哪些分支需要推送,这主要取决于你的项目工作流程、团队协作方式以及个人偏好。以下是一些常见的场景和推荐做法:
主分支(如master或main):
当你完成了一个功能开发,并且这个功能已经通过了所有的测试,准备发布时,你应该将包含这些更改的分支合并到主分支,并将主分支推送到远程仓库。这是为了确保远程仓库中的主分支始终包含最新的、稳定的代码。
特性分支(feature branches):
在一些工作流中,如Gitflow或Feature Branch Workflow,开发者会在特性分支上工作,完成一个特定的功能或修复。当这个功能或修复完成时,它会被合并到主分支(或开发分支,在Gitflow中),然后这个特性分支可能就不需要再推送了,因为它已经完成了它的使命。然而,在合并之前,你可能需要将这个特性分支推送到远程仓库,以便进行代码审查或让其他团队成员了解你的进度。
发布分支(release branches):
在Gitflow工作流中,发布分支用于准备下一个版本的发布。这个分支是从开发分支中分离出来的,用于最后的测试、修复bug以及准备发布说明等。当这个分支准备好发布时,它会被合并到主分支和/或开发分支,并且可能需要被推送到远程仓库以供其他团队成员或CI/CD系统使用。
修复分支(hotfix branches):
修复分支用于快速修复生产环境中的严重问题。这些分支通常从主分支中分离出来,进行必要的修复,然后合并回主分支和/或开发分支。在合并之前,修复分支可能需要被推送到远程仓库,以便其他团队成员可以了解这个紧急修复。
长期支持分支(long-term support branches, LTS):
对于一些项目来说,可能需要维护旧版本的代码库,以便为那些仍然使用旧版本的用户提供支持。这些长期支持分支可能需要定期接收来自主分支的更新(如安全修复),并且这些更新可能需要被推送到远程仓库。
总的来说,哪些分支需要推送取决于你的项目需求和工作流程。然而,一个通用的原则是,任何包含重要更改或需要与其他团队成员共享的分支都应该被推送到远程仓库。
有这样一种情况值得注意,当你想要使用别人推送的分支,那么就可以使用git checkout -b dev origin/dev当然使用 git switch -c dev origin/dev创建远程分到本地。名字最好一致。
$ git switch -c dev2 origin/dev
Switched to a new branch 'dev2'
Branch 'dev2' set up to track remote branch 'dev' from 'origin'.
分支对应
本地分支和远程分支的关系究竟是怎么样的?
Git会自动对应本地和远程的同名分支,但这种自动对应是在一定条件下发生的。以下是关于Git自动对应本地和远程同名分支的详细解释:
1.创建与自动对应
当在本地创建一个新的分支,并且这个分支的名字与远程仓库中已经存在的分支名字相同时,Git并不会自动将这个本地分支与远程的同名分支对应起来。但是,如果在之后的操作中(如使用git push推送本地分支到远程时),Git会根据推送的目标和分支名来判断是否需要自动创建远程分支或更新远程分支,从而间接实现对应。
如果想要显式地将本地分支与远程的同名分支对应起来,可以使用git push -u origin <branch_name>命令,这里的-u或–set-upstream参数会将远程分支设置为本地分支的上游(upstream),从而建立起对应关系。
2.拉取与自动关联
当使用git pull命令从远程仓库拉取代码时,如果本地已经存在一个与远程仓库中分支同名的本地分支,并且该本地分支没有显式地设置上游(upstream)分支,Git会尝试根据分支名来自动匹配远程分支,并拉取对应的代码。但是,这种自动关联并不是绝对的,它取决于Git的配置和当前的工作状态。
为了确保拉取操作的准确性,建议显式地设置本地分支的上游分支,使用git branch --set-upstream-to=origin/<branch_name> <branch_name>命令来实现。
3.区分与管理
在Git中,可以通过前缀来区分本地分支和远程分支。本地分支通常以分支名称开头,而远程分支则以origin/为前缀,后跟分支名称。例如,main是本地分支,而origin/main是远程分支。
当本地分支和远程分支同名时,需要特别注意操作的上下文和目标,以避免将代码推送到错误的分支上。通过显式地设置上游分支和使用git pull、git push等命令的指定选项,可以更加精确地控制代码的同步和推送操作。
综上所述,Git并不会无条件地自动对应本地和远程的同名分支。但是,在特定的操作(如推送和拉取)中,Git会根据分支名和配置来尝试建立对应关系。为了确保代码管理的准确性和高效性,建议开发者显式地设置和管理本地分支与远程分支的对应关系。
简而言之,就是,当拉取和推送时有同名未指定链接关系的分支时,git会自动分配。
当然,我们完全可以使用git branch --set-upstream branch-name origin/branch-name
现在高版本的git已经不支持这个命令了,显然提示建议我们使用–track 和–set-upstream-to来指定链接。
$ git branch --set-upstream dev origin/dev
fatal: the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.
正确的使用方式是
git branch --set-upstream-to=origin/dev dev
如果你当前在dev分支,只需要
git branch --set-upstream-to=origin/dev
即可。
还有一种方式,使用git branch --track创建分支的同时即可设置链接。
$ git branch --track dev origin/dev
当然,直接使用git switch -c 后带两个参数也能起到一样的效果。
协作时解决冲突&抓取分支(重要)
尽管当前推送没有发生问题。但是,一旦进入多人合作,推送时产生冲突几乎是必然。
当推送时发生冲突的时候
- 使用git pull抓取分支。
抓取分支后会提示冲突。 - 本地解决冲突
- commit提交
- 进行推送
实践中的技巧是,在开始搞自己的需求的时候先pull一下,能够减少本地处理冲突的次数。
rebase(变基)
刚刚我们讨论了推送时冲突的问题,解决方法是先本地解决冲突再次push。
提交的历史会变成这样很乱
$ git log --graph --pretty=oneline --abbrev-commit
* d1be385 (HEAD -> master, origin/master) init hello
* e5e69f1 Merge branch 'dev'
|\
| * 57c53ab (origin/dev, dev) fix env conflict
| |\
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
| |/
* | 12a631b merged bug fix 101
|\ \
| * | 4c805e2 fix bug 101
|/ /
* | e1e9c68 merge with no-ff
|\ \
| |/
| * f52c633 add merge
|/
* cf810e4 conflict fixed
这时候我们想到了rebase
图来自参考文章里的rebase理解
这个图生动形象,变基的本质就是基底变了。
对于这个图,使用rebase命令的流程如下
- 当前在feature分支
$ git rebase master
- 会提示冲突,冲突其实也简单,因为我们要生成新的C’和D’嘛,那C’的内容如何得到呢?照搬C的?当然不是,C’的内容就是C和M两个节点的内容合并的结果,D’的内容就是D和M两个节点的内容合并的结果。我们手动处理冲突后,执行如下命令即可:
# 先处理完C,会继续报D的冲突,所以下面命令一共会执行两次
git add file
git rebase --continue
- 最后rebase完成,提交历史变成了图上的样子
最后提交还要什么git push force之类的。
说来说去,rebase使用实际上我个人觉得实在一般。直接merge就好了。所以rebase不再详述了。
标签
很好,一个我从来没有用过的东西。tag,类似于给某次commit起的一个别名,和某个commit高度绑定。实际上此时我在想,绑定tag的commit经过rebase的变化之后,会变成什么样?
首先要跳到相应分支
创建标签
- git tag 默认在最新的提交上打标签
$ git tag v1.0
- git tag name commitid指定某个提交打标签
$ git tag v2.0 a157bb1
注意,这些标签都可以带参数,当想带说明时,用 -a 指定标签名,-m指定说明
$ git tag -a v3.0 -m "try" b670784
查看标签
- 使用git tag查看有哪些标签
$ git tag
v1.0
v2.0
v3.0
注意,标签不是按时间顺序列出,而是按字母排序的。可以用**git show **查看标签信息:
$ git show v3.0
tag v3.0
Tagger: xxxxxxxxxxxxxxx
Date: Tue Sep 24 20:17:30 2024 +0800
try
commit b67078xxxxxxxxxxxxxxxxxxx180592ad (tag: v3.0)
Author: xxxxxxxxxxxxxxxx
Date: Mon Sep 23 16:05:11 2024 +0800
dev edit
diff --git a/readme.txt b/readme.txt
index 564231d..63ce1fe 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,3 +1,5 @@
Git is a version control system.
Git is free software.
-Edit
+Edit
+dev edit
+
推送标签
推送单个标签 git push origin < tagname >
$ git push origin v1.0
一次性推送全部尚未推送到远程的本地标签git push origin --tags
git push origin --tags
删除标签
删除本地标签git tag -d
$ git tag -d v4.0
Deleted tag 'v4.0' (was af5e5cc)
删除远程标签git push origin :refs/tags/< tagname >
$ git push origin :refs/tags/v3.0
注意,这两个本质上没有关系。
但是经过我的实验,本地标签推送代码时不会自动推送,但是远程标签拉取代码时会自动拉取。
换言之,删除标签不删远程那相当于没删。
git 的 ssh公钥和私钥
配置git时,我们这样生成ssh-key
# 你的Github绑定的邮箱
ssh-keygen -t rsa -C "***@gmail.com"
根据命令行提示,进入文件夹,获取以ssh-rsa的字符串(包括ssh-rsa)
这个文件夹下,一般id_rsa为私钥,id_rsa.pub为公钥。
我们得把公钥粘到某个git我们账户下面,将来推送的时候,远程主机才会认识我们。
ssh的原理可以这样简单理解:
当本地主机需要登录远程主机时,本地主机向远程主机发送一个登录请求,远程收到消息后,返回一个随机生成的字符串,本地拿到该字符串,用存放在本地的私钥进行加密,再次发送到远程,远程用之前存放在远程的公钥对本地发送过来加密过的字符串进行解密,如果解密后与源字符串等同,则认证成功。
Fork&Pull request
一般的开源项目协作的流程
- 在github或者是gitee或者其他工具上Fork某个感兴趣的仓库,之后你就拥有一个一样的了。这个过程类似于创建了一个原项目的分支,但这个分支是完全独立的,并且位于你的个人或组织名下。
- 将fork的仓库克隆到自己的机器上开发。开发好了可以在自己的仓库进行推送。
- 创建一个Pull request(PR),这个功能允许你将你在Fork仓库中所做的更改提交给原始项目,并请求项目维护者审查和合并你的更改。PR通常包括标题、描述和代码差异,以便清晰地说明你的更改内容。