GitGuide

GitGuide

Git 仓库中的提交记录保存的是你的目录下所有文件的快照,就像是把整个目录复制,然后再粘贴一样,但比复制粘贴优雅许多!

Git 希望提交记录尽可能地轻量,因此在你每次进行提交时,它并不会盲目地复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。

Git 保存了提交的历史记录。

git commit

通过git commit命令可以将修改保存成了一个新的提交记录 C2(我们暂时这么叫,实际上是一串很长的hash值)。C2 的父节点是 C1,父节点是当前提交中变更的基础。Git 还可以修改最后一次提交的描述信息,当然,如果push之后是没法修改的:

# 进行一次提交
git commit <filename> -m"提交说明"
# 修改最近一次提交信息
git commit --amend

相对引用

HEAD 是一个对当前检出记录的符号引用,也就是指向你正在其基础上进行工作的提交记录。 HEAD 总是指向当前分支上最近一次提交记录。 HEAD 通常情况下是指向分支名的。 分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。 可以利用checkout命令并指定提交记录的hash码让HEAD指向具体的提交:

git checkout [提交记录hash码]

git的hash值基于 SHA-1,共 40 位。例如: fed2da64c0efc5293610bdd892f82a58e8cbc5d8 哈希值指定提交记录很不方便,所以 Git 引入了相对引用。可以从一个分支使用相对引用切换到具体的提交记录

  • 使用 ^ 表示之前 1 个提交记录

  • 使用 ~<num> 之前多个提交记录,如 ~3

git branch

Git 的分支也非常轻量。它们只是简单地指向某个提交纪录。即使创建再多分的支也不会造成储存或内存上的开销,并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。

相对引用

对于分支也可以使用相对引用:

  • 使用@{-<n>} e.g. @{-1} 来表示之前切换的第n个分支

常用示例

查找某提交存在于哪些分支:

查询已经被合并到当前分支的其他所有分支:

git merge

没事别瞎merge!!确定需要之后再merge!!

将指定分支合并到当前分支之后。merge命令只会将选择的分支合并到当前分支,当前分支中的修改不会保存到merge到分支中。

git rebase

rebase会将指定的提交,直接放在指定分支之后rebase很容易出现冲突!!不要在一个共享的分支上进行Git rebase操作。

参考示例

git cherry-pick

实现“我想要把这个提交放到这里, 那个提交放到刚才那个提交的后面”

相较于rebasegit cherry-pick可以指定提交的记录,

例如将需要使用的代码提交到了存在不确定使用的代码分支中

参考示例

git revert

主要有两种方法用来撤销变更git resetgit revertgit revert 会将指定的提交记录的上一个提交记录作为一个新的提交,如果与当前分支有冲突,则需要解决冲突。

git reset

git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

放弃本地未提及commit,强制拉取远程仓库分支

git tag

tag可以用于永远指向某个提交记录的标识呢,比如软件发布新的大版本,或者是修正一些重要的 Bug 或是增加了某些新特性。

git describe

git describe用来描述离指定ref最近的锚点(也就是标签tag)

<ref> 可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会以你目前所检出的位置(HEAD)。

它输出的结果是这样的:

__g

tag 表示的是离 ref 最近的标签, numCommits 是表示这个 reftag 相差有多少个提交记录, hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。当 ref 提交记录上有某个标签时,则只输出标签名称。

git stash

git stash可以将对当前分支的缓存区和工作区的修改保存到list,此时git status查看当前工作区是干净的。

git stash默认会将所有的文件变动暂存起来。如果只想要部分文件可以使用:

git stash list可以查看保存的所有进度

需要恢复时可以通过popapply恢复修改内容。

进度删除可以使用drop或者clear

stashdropclear之后的找回:

git diff

比较本地 ahead 远程的提交

git clean

git remote

远程仓库只是你的仓库在另个一台计算机上的拷贝。你可以通过因特网与这台计算机通信 —— 也就是增加或是获取提交记录。

  • 远程仓库是一个强大的备份。本地仓库也有恢复文件到指定版本的能力, 但所有的信息都是保存在本地的。有了远程仓库以后,即使丢失了本地所有数据, 你仍可以通过远程仓库拿回你丢失的数据。

  • 远程让代码社交化了! 既然你的项目被托管到别的地方了, 你的朋友可以更容易地为你的项目做贡献(或者拉取最新的变更)

Git 远程仓库相当的操作实际可以归纳为两点:

  • 向远程仓库传输数据

  • 从远程仓库获取数据。

git clone 命令在真实的环境下的作用是在本地创建一个远程仓库的拷贝(比如从 github.com),创建之后默认会讲ssh地址作为远程仓库。

远程仓库的分支反映了远程仓库(在你上次和它同步时)的状态。这会有助于你理解本地的工作与公共工作的差别。 在你检出时自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, (更新了远程分支之后)再用远程分享你的工作成果。

远程跟踪

mastero/master 的关联关系就是由分支的“remote tracking”属性决定的。master 被设定为跟踪 o/master

当你克隆时,Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/master)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 master

可以让任意分支跟踪 o/master, 然后该分支会像 master 分支一样得到隐含的 push 目的地以及 merge 的目标。 例如:以下命令就可以创建一个名为 totallyNotMaster 的分支,它跟踪远程分支 o/master

设置远程仓库

可以用remote命令查看远程是否可以fetch并用set-url选项来修改远程仓库,或者add来添加远程仓库

设置远程分支

另一种设置远程追踪分支的方法就是使用:git branch -u 命令,

远程有分支更新时,本地可能没有获取新的分支,可以使用:

git fetch

从远程仓库获取数据, 使远程分支更新以反映最新的远程仓库。

git fetch 完成了仅有的但是很重要的两步:

  • 从远程仓库下载本地仓库中缺失的提交记录(但不会直接合并到对应分支)

  • 更新远程分支指针(如 o/master

git fetch 实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。 git fetch 通常通过互联网(使用 http://git:// 协议) 与远程仓库通信。 git fetch 并不会改变你本地仓库的状态。它不会更新你的分支,也不会修改你磁盘上的文件。 git fetch 的参数和 git push 极其相似。他们的概念是相同的,只是方向相反罢了(因为现在你是下载,而非上传)

如果不指定 的话即将本地的分支删除

git pull

当远程分支中有新的提交时,你可以像合并本地分支那样来合并远程分支。也就是说就是你可以执行以下命令:

  • git cherry-pick o/master

  • git rebase o/master

  • git merge o/master

Git 提供了一个专门的命令 git pull来完成这两个操作。

git pull 就是 git fetchgit merge 的缩写

所以以下命令是等价的:

git pull还可以和rebase一起使用,即

该命令会从远程仓库获取到最新的提交并将当前分支合并到该提交上。

git push

负责将变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。

push之前必须要保证当前分支获取了最新的original/master

否则需要先通过pull命令获取到最新的远程提交,然后在push

同时可以为 push 指定参数,语法是:

同时为源和目的地指定 <place> 的话,只需要用冒号 : 将二者连起来就可以了:

**注:**这个参数实际的值是个 refspec,“refspec” 是一个自造的词,意思是 Git 能识别的位置(比如分支 foo 或者 HEAD~1

如果不指定`的话会将远程仓库的分支删除

删除远程分支还可以使用:

git config

查看配置信息

此外还可以在~/.gitconfig文件中直接修改

Hooks

我们可以根据配置,在通过git提交或者推送代码的时候,进行一些默认的操作。在每个git管理的仓库的根目录下,都会有一个.git/hooks的文件夹,其中又一些默认的文件。我们以pre-commit.sample为例子。在该文件中添加内容,并将文件名改为pre-commit

在下次提交的时候就可以看到Hello Work!的输出。

通过一下命令可以设置(全局)的hooks,其命令的执行路径是相对于项目的.git所在路径:

pre-push

pre-commit

参考:Git禁止大文件提交到仓库中

gitignore

.gitigonre

在一个项目中,可能有些文件的修改我们并不想将其推送到远程仓库。于是我们可以在.gitignore文件配置相应的规则

.gitignore是一个没有后缀的文本文件,需要更.git文件夹放在同一级目录。具体语法如下,更多例子可参考这里

**注:**需要注意的是,.gitignore默认会搜索所有路径下的文件。比如项目根目录与一级目录下存在同名文件夹:

如果只想忽略更目录下的Lib文件夹中的内容,需要指定为/Lib/

core.excludesFile

.gitignore只能对应一个仓库,如果想全局忽略所有仓库中的某些文件,可以通过配置core.excludesFile来设置:

需要注意的是,这里的配置文件中每条配置跟.gitignore一样,是相对于每个仓库而言的,而不是绝对路径。

git log

参考: https://git-scm.com/docs/git-log#_pretty_formats https://www.cnblogs.com/bellkosmos/p/5923439.html

git submodule

submodule允许将一个git仓库作为另外一个git仓库的子目录,且两个仓库具有独立的提交。可以通过如下命令根据一个已有仓库在当前仓库中创建同名的子模块:

主仓库和子模块的关联会体现在执行命令后新生成的.gitsubmodule文件、当前仓库的.git/config配置信息、.git/modules/submodule_name文件夹中。

由于子模块独立于主仓库,因此子模块中的提交、远程同步都是独立的。在子模块中提交更改后,才能在主仓库中体现出来有更改。并且在主仓库中是依照子模块提交的hash码进行关联的,所以如果子模块的提交没有推送到远程仓库,而主仓库已经推送到了远程仓库,则可能导致其他开发人员无法获取到对应的子模块提交。

彻底移除子模块

本地取消子模块关联

重新初始化并更新子模块

根据主仓库拉取子模块代码

Git规范

Commit 规范

git commit 时应当使用-a 进入交互界面编辑提交信息。基本格式:

示例:

Head

Type
Description

feat/feature

新增功能

fix

bug修复

perf

提高代码性能

style

代码格式类变更

refactor

其他代码类变更,如简化代码、删除溶于重命名变量等

test

修改测试用例

ci

持续集成和部署相关变更

docs

文档类更新

chore

对构建过程或辅助工具和库(如文档生成)的更改

Body

动词开头、使用现在时。必须包括修改的动机、跟上一版本相比的改动点。 如果当前 commit 还原了先前的 commit,则应以 revert: 开头,后跟还原的 commit 的 Header。 而且,在 Body 中必须写成 This reverts commit <hash> ,其中 hash 是要还原的 commit 的 SHA 标识。

Footer

如当前代码跟上一个版本不兼容,需要在 footer 部分以 BREAKING CHANG: 开头,跟上不兼容改动的摘要。其他部分需要说明变动的描述、变动的理由和迁移方法:

分支管理

通常在非开源项目中一般会根据不同的环境来设置分支,比如:

分支名
描述

master

该分支上的最新代码永远是发布状态,不能直接在该分支上开发 master分支每合并一个hotfix/release分支,都会打一个版本标签

develop

该分支上的代码是开发中的最新代码,该分支只做合并操作,不能直接在该分支上开发

feature

在研发阶段来做功能开发的分支 新功能的feature分支基于该分支创建,分支名通常为feature/xxxx-xxx 开发完成之后,先pull一下develop分支,解决冲突后再合并到develop分支并删除。

release

基于develop分支创建的发布阶段作版本发布的预发布分支,分支名建议命名为:release/xxxx-xxxø 例如,v1.0.0版本的功能全部开发测试完成后,提交到develop分支 然后基于develop分支创建release/1.0.0分支,并提交测试,测试中遇到的问题在release分支修改 最终通过测试后,将release分支合并到master和develop,并在master分支打上v1.0.0标签

hotfix

基于master分支上创建的维护阶段作紧急bug修复分支 修复完成后合并到master。分支名通常为hotfix/xxxx-xxxo 例如:当线上某个版本出现Bug后,从master检出对应版本的代码,创建hotfix分支并修复问题 问题修复后,将hotfix分支合并到master和develop分支,并在master分支打上修复后的版本标签

基本流程:

语义化版本

参考:semantic versioning

常用示例

Github基于别人分支修改

撤销

如果要撤销或者重新排序提交记录可以参考[撤销提交记录](#git reset)和[git cherry-pick](#git cherry-pick)。

删除历史提交大文件

git rev-list --objects —all显示所有commit及其所关联的所有对象 verify-pack -v *.idx:查看压缩包内容

filter-branch :命令通过一个filter来重写历史提交,这个filter针对指定的所有分支运行 --index-filter:过滤命令作用于git rm -rf --cached --ignore-unmatch [filename] git rm -rf --cached --ignore-unmatch [filename]: 删除index中的文件,并且忽略没有匹配的index --prune-empty:指示git filter-branch 完全删除所有的空commit -–tag-name-filter:将每个tag指向重写后的commit cat命令会在收到tag时返回tag名称 –-选项用来分割 rev-list 和 filter-branch 选项 --all参数告诉Git我们需要重写所有分支(或引用)

此时就已经完成了对文件的删除,但是提交到远程仓库时一定要先备份原来的仓库,一旦提交后就再也没有办法恢复了!!一旦提交后就再也没有办法恢复了!!一旦提交后就再也没有办法恢复了!!

参考:Git清理删除历史提交文件

rebase实践

合并提交

添加参数 --interactive或者-i可以进入交互式的 rebase。 增加了这个选项后, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。假设我们有如下提交:

HEAD~2到最新提交开始修改:

修改内容为一下内容并保存:

进入到修改提交注释的界面,直接保存:

最后两个分支被合并了

修改之前 的提交信息

首先通过git log找到需要更改的上一个提交记录的hash,比如6ea8799

然后通过git rebase -i 6ea8799进入到交互页面,将对应的提交从pick修改为edit

通过git commit --amend重新修改提交的信息

最后使用git rebase --continue 结束

一次cherry-pick实践

背景:在共享的开发中,二进制文件通常不应该被提交到远程仓库。一旦提交之后就会被git保存在历史记录中,这将占用大量的空间。并且即使在后续的提交中进行了删除,之前的二进制文件记录也会被保存在历史记录中。

在一次开发中,不小心吧二进制文件提交到了远程仓库,好在还没有合并到主分支。坏在提交二进制文件的记录之后已经有了十几次提交。 重新修改的思路就是,找到提交二进制文件的位置,然后切出一个新的分支。修改之后,将原来分支之后每一个提交记录cherry-pick到新切出的分支。

获取到这这些提交记录之后,我们可以回到刚刚的fix_cherry_pick直接使用cherry-pick整理提交:

这时候遇到了一个错误,这是因为<commit:c28e6e0>这个提交是一个merge的操作,而merge有可能会出现冲突,git并不知道如何去处理这些冲突,所以需要我们手动去处理:

处理完冲突之后提交,并继续cherry-pick,直到完成所有的提交即可:

统计用户提交

统计某用户提交:

统计某用户时间范围内的提交:

统计所有用户提交:

最后更新于

这有帮助吗?