Git 学习笔记——git 分支管理、git 工具和子模块

Git分支管理

Git最重要的运用场景是多人协同开发,但是如何能保证每个人之间的开发不影响其他人的开发进程,Git 分支的出现就是解决了这个问题,使得每个人之间的开发是独立的,互不影响的。
与许多其它版本控制系统不同,Git 鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。

分支操作

使用 git branch 来查看现有的分支或创建新的分支。当不带任何命令参数时,输入 git branch 可以帮助我们查看当前项目所拥有的全部分支。并且 Git 会使用 * 来标明我们当前所处的分支上。

当我们想要新增加新的分支时,只需要在git branch命令后面加上我们想要新建的分支的名称即可。

创建issue102的分支
git branch issue102

查看现有的所有分支
git branch

虽然创建了issue102的分支,但是当前分支还是在master上。我们可以通过 git checkout 命令来进行切换分支。

git checkout issue102
	Switched to branch 'issue102'

git branch
	* issue102
	master

切换分支后,我们就可以进行自己的开发。分支上的文件状态是不同的。

touch issue102.md
git add issue102.md
git commit -m "update issue102.md"

touch issue102.html
git add issue102.html
git commit -m "update issue102.html"

在完成上述命令后,

git log --oneline
cd836b0 (HEAD -> issue102) update issue102.html
7575f02 update issue102.md
242c407 (master) update hello.md

issue102分支上的记录与master的记录间隔开了。除此之外,当我们切换回主分支后,我们还会发现master分支下没有新建的issue102.md和issue102.html两个文件。

当我们在分支上完成来开发工作后,我们需要将我们在当前分支进行的工作合并到主分支上。首先我们需要切回需要合并到的分支上,此处以issue102合并到master上为例子进行演示。

# 切换回主分支
git checkout master
# 使用git merge 进行合并
git merge issue102

# 查看所有未合并工作的分支
git branch --no-merged

有时候分支的合并不会一番顺利,当我们在两个分支中对同一个文件的同一个部分进行了不同的修改,Git就没有办法顺利的合并他们,会在合并的时候产生合并冲突。比如我们在issue102分支和master分支下对issue102.md文件进行了修改,当我们将issue102分支融合到主分支上时就会发生冲突。]

可以通过 git status 查看命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件。当出现矛盾后,合并的文件内容将会出现 "<<<<<<","=======",">>>>>>" 等分割线来进行标记。

当出现了矛盾时,我们需要进行手动解决或者放弃合并。

  1. 手动合并的方法很简单,就是我们选择我们要保留的代码,然后再把>>>>>, ======, <<<<<<这些提示行给去掉。最后重新进行add commit的操作即可。
  2. 当我们发现冲突所导致的改动量很大时,我们可以选择放弃该次的合并。我们可以使用 git merge --abort 放弃此次的融合。如果我们在运行了git merge之后又进行了一些人为的改动,那么在abort之后,所进行的改动也会被回滚掉。

除了手动合并以及放弃合并之外,我们还有一些其他的合并工具。git官方开发了一个专门用来合并的工具,叫做git mergetool(下图所示),它会将找到一份两个分支的祖先代码作为base(基准),然后再将两个分支的改动都列举出来作为对比,让我们在git编辑器当中决定要留下什么。

  1. use vimdiff as git mergetool
git remote -v查看远程库的详细信息。会显示我们可以抓取或推送的origin地址。

$ git remote -v
origin  git@github.com:ProjectOwner/ProjectName.git (fetch)
origin  git@github.com:ProjectOwner/ProjectName.git (push)

当我们需要推送本地分支到远程时,需要指定具体的本地分支。

# 推送本地的master分支到远程
git push origin master
# 推送本地的issue102分支到远程
git push origin issue102

在Git中没有什么分支是不可以删除的(除了当前所在的分支不能删除),包括master分支也是可以进行删除。 Git的分支删除可以分为删除本地分支和远程分支。

# 删除本地分支
# branchName 是需要删除的本地分支名字
git branch -d branchName

当我们想强行删除分支时,只需要将参数d改为D即可。

删除远程分支
# origin 是远程的主机名
# branch 需要删除的远程分支
git push origin --delete branch

当我们需要重命名分支的名称时,也使用git branch命令来进行,具体方式如下:

# oldBranchName: 旧分支名
# newBranchName :新分支名
git branch -m oldBranchName newBranchName

当我们想要将改名后的分支推送到远程时,我们需要进行如下操作:

git branch -m oldBranchName newBranchName   # 将本地的分支进行重命名
git push origin newBranchName               # 将新的分支推送到远程        
git push --delete origin oldBranchName      # 删除远程的旧的分支 

分支开发工作流

在整个项目开发周期的不同阶段,我们可以同时拥有多个分支;然后我们可以定期地把某些主题分支合并入其他分支中。许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为 develop 或者 next 的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master 分支了。这样,在确保这些已完成的主题分支(短期分支,比如之前的 issue102 分支)能够通过所有测试,并且不会引入更多 bug 之后,就可以合并入主干分支中,等待下一次的发布。

短期分支也可以叫做主题分支,它的作用是用来实现某一种特性或者相关工作(修复bug,开发产品新特性)。比如当我们的产品出现了bug时,我们应该新建一个分支并起名为bug分支,并在该分支上进行bug的修复,等我们的代码确定不会引起其他bug时,我们就可以合并到主分支上进行修复。当我们看见issue时,我们也可以使用同样的方式来解决issue的问题。常见的短期分支还有上面提到的develop,topic分支。在实际开发中,我们应该按照以下几个基本原则进行分支开发工作流程

master分支应该是最稳定的,也就是仅用来发布新版本,平时不能直接在上面进行操作,应该保存在远程。
短期分支是我们干活的分支,短期分支可以不用上传到远程,当我们完成了bug的修复,新功能的开发时才需要合并到主分支上。

Git 工具

引用修订版本和分支

Git 支持多种方式来引用单个提交、一组提交或一定范围内的提交。

切换至项目工作目录,执行 git log 能看到类似提交日志的输出。
从日志能明显的看到多次提交的记录,每次包括 commit + 一串字符、作者、提交时间 和详细信息等。
想查看某次提交信息,可以通过 git show 来查看。
通过在 git log后增加 --pretty=oneline简化输出内容。

如果你要查看一个分支的最后一次对象,可以通过分支名查看。查看本地分支列表通过git branch查看。

查看远程分支通过后加参数 -r

通过 git show stable 查看指定分支最后一次提交信息

暂存文件

git stash 会处理工作目录的的状态,跟踪文件的修改和暂存的改动,然后将未完成的修改保存至一个栈上,后续再切换回来.
通过 git stash list 查看所有 stash 的列表。
切换至最后 stash 变更,直接执行 git stash apply 即可,当然如果有多个,可以通过 git stash apply stash@{n} 中的 n 来获取指定的的变更。
可以通过 git stash drop 或者 git stash pop 来删除 stash 的内容。
想同时获取变更,并删除 stash 的内容的话, git stash pop 是比较好的用的方式,也是个人最喜欢的方式。

清理工作目录

使用 git clean -f -d 命令来移除工作目录中所有未追踪的文件以及空的子目录。 -f 意味着强制移除,使用它需要 Git 配置变量 clean.requireForce 没有显式设置为 false。

如果你只是想看下或者删除前小心翼翼的确认: 它到底会删除那些东西. 可以通过–dry-run或者-n选项来执行命令,这只是告诉你会删除什么,而不会真的删除.

默认情况下,git clean 命令只会移除没有忽略的未跟踪文件。 任何与 .gitignore 或其他忽略文件中的模式匹配的文件都不会被移除。如果你也想移除,可以通过增加选项-x

同样增加选项-d可以删除目录

如果你想交互删除,可以通过选项 -i来操作

通过What now后输入命令或序号交互操作,不知道可以输入 help 查看具体的描述

搜索

Git 提供了一个grep命令,可以很方便的从提交历史,工作目录,甚至索引中查找一个字符串或者正则表达式。

默认情况下git grep会查找你的工作目录文件。

$ git grep a.percentileBoundary

src/trace/histogram.go:func (h *histogram) percentileBoundary(percentile float64) int64 {
src/trace/histogram.go: return h.percentileBoundary(0.5)
src/trace/histogram_test.go:            percentile := a.percentileBoundary(test.fraction)

通过-n或者–line-number显示匹配的行号

$ git grep -n percentileBoundary

src/trace/histogram.go:120:func (h *histogram) percentileBoundary(percentile float64) int64 {
src/trace/histogram.go:166:     return h.percentileBoundary(0.5)
src/trace/histogram_test.go:181:                percentile := a.percentileBoundary(test.fraction)

通过-c或者–count输出统计信息

git grep -c percentileBoundary

src/trace/histogram.go:2
src/trace/histogram_test.go:1

通过-p 或者 --show-function 显示每个匹配字符串所在的方法或函数

 git grep -p percentileBoundary

src/trace/histogram.go=func (h *histogram) standardDeviation() float64 {
src/trace/histogram.go:func (h *histogram) percentileBoundary(percentile float64) int64 {
src/trace/histogram.go=func (h *histogram) median() int64 {
src/trace/histogram.go: return h.percentileBoundary(0.5)
src/trace/histogram_test.go=func TestPercentileBoundary(t *testing.T) {
src/trace/histogram_test.go:            percentile := a.percentileBoundary(test.fraction)

子模块

基础操作

项目中经常会遇到包含另外一个项目,如:第三方库,或者你将自己的项目切分成多个子项目,然后在其他项目中引用,如,将项目中的 model 独立处理,独立维护;其他项目组引用这个项目,并不维护 model。这里我们可以将 model 做子项目添加到当前项目中。

通过 git submodule add 添加子模块,使用 https://github.com/datawhalechina-git-samples/model 进行测试,如

$ git submodule add https://github.com/datawhalechina-git-samples/model

Cloning into '/Users/martin/project/datawhalechina/app/model'...

remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (5/5), done.

参数同 clone,默认是 repo 的名称,如果你想改名,可以在后续增加新的名称或路径。

通过 git status 能看到新的 model 库。

$ git status

On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   .gitmodules
	new file:   model

能看到有个新增的 model 和 .gitmodules文件,该配置文件保存了项目 URL 和本地目录的 mapping 关系。

$ cat .gitmodules

[submodule "model"]
	path = model
	url = https://github.com/datawhalechina-git-samples/model

如果有多个子模块,这里会列出多条。

通过git diff能看到更详细的信息

$ git diff --cached model

diff --git a/model b/model
new file mode 160000
index 0000000..a8328fd
--- /dev/null
+++ b/model
@@ -0,0 +1 @@
+Subproject commit a8328fd6ee683ef8f6a2d7c4edfefed2923b0795

虽然 model 是工作目录中的一个子目录,但 Git 还是会将它视作一个子模块。当你不在那个目录中时,Git 并不会跟踪它的内容, 而是将它看作子模块仓库中的某个具体的提交。

如果你想看到更漂亮的差异输出,可以给 git diff 传递 --submodule 选项。

$  git diff --cached --submodule

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..f9d131a
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "model"]
+       path = model
+       url = https://github.com/datawhalechina-git-samples/model
Submodule model 0000000...a8328fd (new submodule)

当你提交时,会看到类似下面的信息:

$ git commit -am 'add model module'

[main 4432854] add model module
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 model

注意 app 记录的 160000 模式。这是 Git 中的一种特殊模式,它本质上意味着你是将一次提交记作一项目录记录的,而非将它记录成一个子目录或者一个文件。

然后推送至服务端

git push origin main

Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 457 bytes | 457.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/datawhalechina-git-samples/app
   a55ea12..4432854  main -> main

我们在 clone 一个含子模块的项目时,默认是不会包含子模块内容的,只有目录,如重新 clone 上述的 app 项目

$ git clone  https://github.com/datawhalechina-git-samples/app.git new_app

Cloning into 'new_app'...
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 8 (delta 1), reused 3 (delta 0), pack-reused 0
Receiving objects: 100% (8/8), done.
Resolving deltas: 100% (1/1), done.

$ cd new_app/model
$ ls -alh

total 0
drwxr-xr-x  2 martin  staff    64B May  4 13:46 .
drwxr-xr-x  8 martin  staff   256B May  4 13:46 ..

会发现什么也没有,需要通过如下两个命令来获取内容

git submodule init 初始化本地配置文件
git submodule update 则从该项目中抓取所有数据并检出父项目中列出的合适的提交。

$ git submodule init

Submodule 'model' (https://github.com/datawhalechina-git-samples/model) registered for path './'

$ git submodule update

Cloning into '/Users/martin/project/datawhalechina/new_app/model'...Cloning into '/Users/martin/project/datawhalechina/new_app/model'...
Submodule path './': checked out 'a8328fd6ee683ef8f6a2d7c4edfefed2923b0795'

不过还有更简单一点的方式。 如果给 git clone 命令传递 --recurse-submodules 选项,它就会自动初始化并更新仓库中的每一个子模块, 包括可能存在的嵌套子模块。

$ git clone --recurse-submodules  https://github.com/datawhalechina-git-samples/app.git new_app2

Cloning into 'new_app2'...
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 8 (delta 1), reused 3 (delta 0), pack-reused 0
Receiving objects: 100% (8/8), done.
Resolving deltas: 100% (1/1), done.
Submodule 'model' (https://github.com/datawhalechina-git-samples/model) registered for path 'model'
Cloning into '/Users/martin/project/datawhalechina/new_app/model/new_app2/model'...
...

如果你已经克隆了项目但忘记了 --recurse-submodules,那么可以运行 git submodule update --init 将 git submodule init 和 git submodule update 合并成一步。如果还要初始化、抓取并检出任何嵌套的子模块。

当子模块有更新的时候,执行 git submodule update --remote

$ git submodule update --remote

该命令默认会更新 main 分支,如果你想设置为其他分支,可以在 .gitmodules 文件中设置 (这样其他人也可以跟踪它),也可以只在本地的 .git/config 文件中设置,我们在.gitmodules中配置它

$ git config -f .gitmodules submodule.model.branch stable

$ cat .gitmodules
[submodule "model"]
	path = model
	url = https://github.com/datawhalechina-git-samples/model
	branch = stable

很明显很看到 branch 已经变化。当运行 git submodule update --remote 时,Git 默认会尝试更新 所有 子模块, 所以如果有很多子模块的话,你可以传递想要更新的子模块的名字。如 git submodule update --remote model

$ git submodule update --remote model

remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), 266 bytes | 133.00 KiB/s, done.
From https://github.com/datawhalechina-git-samples/model
   a8328fd..ca79fae  main       -> origin/main
 * [new branch]      stable     -> origin/stable
Submodule path 'model': checked out 'ca79fae869c9b4ddd7999f06ffd48ac25971b9dd'

打包

Git 提供了多种网络传输的方法,如 SSH、HTTP 等,但是还有种不太常用的功能又什么有效。

Git 可以就将它的数据"打包"到一个文件中,通过 git bundle来实现。bundle 命令会将git push命令所传输的所有内容打包成一个二进制文件,你可以将这个文件转发给别人,然后解包到仓库中。

$ git bundle create repo.bundle HEAD main

Enumerating objects: 90, done.
Counting objects: 100% (90/90), done.
Compressing objects: 100% (83/83), done.
Total 90 (delta 12), reused 24 (delta 3), pack-reused 0

$ ls -alh repo.bundle

-rw-r--r--  1 martin  staff   6.2M May  4 12:05 repo.bundle

这个 repo.bundle 就是打包之后的文件,改文件包含了所有重建仓库 main 分支所需要的数据。在使用 bundle 命令时,你需要列出所有你希望打包的引用或者提交的区间。 如果你希望这个仓库可以在别处被克隆,你应该像例子中那样增加一个 HEAD 引用。

别人就可以从这个二级制文件 clone 一个目录,就像从git clone https/ssh 一样的功能

$ git clone repo.bundle repo

Cloning into 'repo'...
Receiving objects: 100% (90/90), 6.20 MiB | 88.21 MiB/s, done.
Resolving deltas: 100% (12/12), done.

$ git log --oneline
...

如果你在打包时没有包含 HEAD 引用,你还需要在命令后指定一个 -b main 或者其他被引入的分支, 否则 Git 不知道应该检出哪一个分支。

如果只是要提交最新提交的修改,这需要我们手工计算,可以通过如下的指令计算差别

$ git log --oneline origin/main..main

或者

$ git log --oneline main ^origin/main

这里将获得到我们希望被打包的提交列表,将这些提交打包,通过 git bundle create操作

$ git bundle create commits.bundle main ^5de18d5

可以将 commits.bundle 文件分享给合作者,他可以将这个文件导入到原始仓库中。在导入前可通过bundle verify 命令检查这个文件是否是一个合法的 Git 包,是否拥有共同的祖先。

git bundle verify commits.bundle

如果打包工具打包的并不是全部的变更,而是最后几个变更,原始仓库则无法导入这个包,因为这个包缺失必要的提交信息。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值