git 常用命令
一些认知
git 是一个复杂的软件,命令参数组合起来有上百种,掌握常用的就好了
查看帮助
#查看常用命令
$ git help
#转到帮助文档查用法
$ git help merge
# 查看命令的用法,没有上面的直观
$ git merge -help
#或
$ git merge -h
查看版本
$ git --version
配置用户信息
安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址,这一点很重要,
因为每一个 Git 提交都会使用这些信息,它们会写入到你的每一次提交中,不可更改.
$ git config --global user.name "your name"
$ git config --global user.email "your_email@xxx.com"
提交时转换为 unix LF,检出时转换为 windows CR LF
$ git config --global core.autocrlf true
提交包含混合换行符的文件时给出警告
$ git config --global core.safecrlf warn
git init 初始化仓库
# 当前目录
$ git init
# 指定目录
$ git init dir
git config 配置
# 配置当前仓库
$ git config -e
# 全家配置
$ git config -e --global
# 显示当前的配置
$ git config --list
#配置当前仓库提交代码的用户信息 [--global] 配置全局的
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"
#配置别名
$ git config --global alias.gl "log --oneline --all --graph"
当前仓库的配置的信息保存在用户的家目录下的.gitconfig 文件里
$ cd ~
$ cat .gitconfig
也可以直接在这个文件进行配置
[alias]
gl = log --all --graph --oneline
git mv 重命名文件,目录或移动文件,目录位置
重命名只影响工作区和暂存区,本地仓库还需要提交 git mv 执行了 shell 的 mv 指令将旧文件重命名为新文件,接着使用 git rm 命令删除旧文件,并使用 git add 添加新文件
$ git mv oldFileName newFileName
git add 把代码加入暂存区
# 通过通配符 '.' 把当前目录内的文件全部加入暂存区
$ git add .
# 加入指定的文件
$ git add a.txt b.txt
# 加入指定后缀名的文件
$ git add *.txt
# 加入目录及目录内的所有文件
$ git add newDir
# 加入目录及指定的文件
$ git add newDir/a.txt
git commit 提交 commit 是 原子操作
#把暂存区的变更一次提交,原子操作
$ git commit -m '提交的注释'
# 工作区的文件更改后,不经过 git add 直接提交到本地仓库
$ git commit -a -m '提交的注释'
# 或
$ git commit -am '提交的注释'
# 代码没有提交的远程仓库前对最近一次的commit 进行修改,不会新增一个commit 对象,会修改最近一次的commit 的 id
# 相应暂存区的代码也会一并提交到本地仓库, 也可以使用 -a 不经过 add
$ git commit --amend
$ git commit -a --amend
git rm 删除文件,删除后要进行一次提交才能同步
-
git rm 删除跟踪的文件 同时从工作区和索引中删除文件,本地仓库还有
$ git rm a.txt
删除目录及目录下的文件
$ git rm -r newDir
暂存区删除文件,工作目录保留文件,此文件不进行版本控制,本地仓库还有
$ git rm --cached a.txt
-
git clean 删除未跟踪的文件 提前看一下未跟踪文件,并不会删除
$ git clean -n
提前看一下未跟踪文件和目录,并不会删除
$ git clean -dn
删除未追踪的文件
$ git clean -f
删除未追踪的文件和目录
$ git clean -df
git ls-files 查看暂存区的文件信息
可以通过git ls-files命令查看暂存区的文件信息。 参数信息如下,括号中是简写:
- --cached(-c): 查看暂存区中文件。git ls-files命令默认执行此选项.
- --midified(-m):查看修改的文件。
- --delete(-d):查看删除过的文件。
- --other(-o) :查看没有被Git跟踪的文件。
- --stage(-s):显示 mode 以及文件对应的Blob对象,进而我们可以获取暂存区中对应文件里面的内容。
$ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 a
git status 查看工作区文件的状态
$ git status
# 查看指定文件的状态
$ git status a.txt b.txt
# 查看指定后缀文件的状态
$ git status *.txt
git diff 查看代码库差异
查看工作区和暂存区文件的差异
$ git diff
# 查看指定文件的的差异
$ git diff a.txt b.txt
# 查看指定后缀文件的差异
$ git diff *.txt
查看工作区和本地库的差异
$ git diff head
#可以指定具体文件
$ git diff head a.txt
查看暂存区和本地仓库的差异
$ git diff --cached
#可以指定具体文件
$ git diff --cached a.txt
查看俩个 commit 的差异
#比较俩个commit 差异
$ git diff commit1 commit2
#比较俩个 commit 差异,指定具体文件
$ git diff commit1 commit2 a.txt
#比较当前分支最近俩次提交的差异
$ git diff head head^
git log 查看 commit 历史
通过图形界面工具来查看版本历史 gitk
常规用法
git log
# oneline 会将每个提交放在一行显示,在浏览大量的提交时非常有用
git log --pretty=oneline
# 或
git log --oneline
# 列出指定作者的提交
$ git log --oneline --author=<your name>
# 列出所有分支的提交
$ git log --oneline --all
# -p 展开显示每次提交的内容差异, 用 -2 则仅显示最近的两次更新
$ git log -p -2
# --graph 选项,查看当前分支
$ git log --graph
# --graph --all 选项,查看历史中什么时候出现了分支、合并
$ git log --graph --all
# 查看某个分支的
$ git log 分支名称
git blame 指定具体的文件查看
blame 英文是归咎于的意思
# 查看指定文件的当前分支的 commit 记录
$ git blame a.txt
# 查看指定文件的指定分支的 commit 记录
$ git blame branchName a.txt
shortlog 按提交者进行分组
# 将commit按照作者分组,默认按照作者名字排序 -n 按 commit 数量排序
# 默认是在当前分支
$ git shortlog -n
# 指定分支
$ git shortlog feature -n
#所有分支
$ git shortlog -a -n
限制时间范围查询
#在那天之前
$ git log --after="2014-7-1"
#在那天之前,在那天之后
$ git log --after="2014-7-1" --before="2014-7-4"
过滤 merges 查询
# 默认情况下git log会输出merge commit. 你可以通过--no-merges标记来过滤掉merge commit:
$ git log --no-merges
# 只输出 merges:
$ git log --merges
单独查询某个分支的提交
# master..feature这个range包含了在feature有而在master没有的所有commit
# 用查询分支单独的提交
$ git log master..feature
查询在某次提交后的所有提交
$ git log commitID
搜索提交历史,根据关键词
$ git log --grep [keyword]
查看所有的历史记录
进行版本回退可以用此命令查询到 git log 没有追踪的一些 commit
# 查看所有的历史记录
$ git reflog
查看分支的起点
git refolg show branchName
git checkout 重置当前工作区文件与暂存区保持一致
#指定具体文件
$ git checkout a.txt
git reset 重置
常用的 mixed 模式 ,重置暂存区的文件与上一次的提交(commit)保持一致,工作区文件内容保持不变
$ git reset
#或
$ git reset head
#可以指定具体文件
$ git reset a.txt
hard 回退历史版本这个操作要谨慎,回退了代码很难找回来
工作区,暂存区都会进行回退 HEAD master 进行了移动 恢复方法参考
简单的只能恢复到指定的版本,但是未提交的代码就没有办法了
## 查看所有提交的历史版本,找到恢复的commit id 628164
$ git refolg
$ git reset --hard 3628164
# 回退上一个版本
$ git reset --hard head^
# 回退上上一个版本
$ git reset --hard head^^
# 回退指定 commit id 的版本
git rest --hard 3628164
#可以指定具体文件
$ git reset --hard head^ a.txt
soft 回退
工作区,暂存区不会回退 HEAD master 进行了移动
# 回退上一个版本
$ git reset --sof head^
# 回退上上一个版本
$ git reset --sof head^^
# 回退指定 commit id 的版本
git rest --sof 3628164
#可以指定具体文件
$ git reset --sof head^ a.txt
mixed 回退,此参数也可不写
工作区不会回退,暂存区回退 HEAD master 进行了移动
# 回退上一个版本
$ git reset --mixed head^
# 或
$ git reset head^
# 回退上上一个版本
$ git reset --mixed head^^
# 回退指定 commit id 的版本
git rest --mixed 3628164
#可以指定具体文件
$ git reset --mixed head^ a.txt
git revert 撤销提交的 commit
revert 英文恢复的意思
-
使用-n是因为revert后,需要重新提交一个commit信息,然后在推送
如果不使用-n,指令后会弹出编辑器用于编辑提交信息 git revert -n commitId - 可以同时撤销几个连续的 commit git revert commitIdA..commitIdB
- git revert commitId 撤销某次的提交内容,会新生产一个 commit 反应撤销 commit 带来的变化
但是撤销的 commit 依然会保留在提交历史中
可以撤销的类型
-
普通的commit git revert
-
merge commit git revert -m
-
撤销普通的 commit 可以撤销任意一次或几次的 commit
master 分支有三次 commit 第一次 a47ae2d 增加a.txt 文件 第二次e415435 增加b.txt 文件 第三次21a340c 增加c.txt 文件
$ git log --oneline --all --graph * 21a340c (HEAD -> master) a b c * e415435 a b * a47ae2d a
撤销倒数第一个 commit,其他保留
$ git revert head^ #或 $ git revert e415435 Removing b.txt [master 9d399bc] Revert "a b" 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 b.txt # 去除 e415435 的提交的文件 b.txt ,新生成一个 commit 9d399bc # commit 9d399bc 只包含 a.txt , c.txt $ git log --oneline --all --graph * 9d399bc (HEAD -> master) Revert "a b" * 21a340c a b c * e415435 a b * a47ae2d a $ ls a.txt c.txt
-
撤销 merge commit
当前的分支图
graph LR d905789-.->388f80e head-.->d905789 subgraph master d905789(d905789 master->a) end subgraph oneb 388f80e(388f80e oneb->oneb a b) 3c856ad(3c856ad oneb->oneb a b c) 388f80e-->3c856ad end
$ git log --all --graph --oneline * 3c856ad (HEAD -> oneb) oneb a b c * 388f80e oneb a b * d905789 (master) master a
合并分支
$ git checkout master $ git merge --no-ff oneb $ git log --all --graph --oneline * b28c56f (HEAD -> master) Merge branch 'oneb' |\ | * 3c856ad (oneb) oneb a b c | * 388f80e oneb a b |/ * d905789 master a
graph LR d905789-.->388f80e 3c856ad-.->b28c56f head-.->b28c56f subgraph master d905789(d905789 master->a) b28c56f(b28c56f master -> Merge branch 'oneb') d905789-->b28c56f end subgraph oneb 388f80e(388f80e oneb->oneb a b) 3c856ad(3c856ad oneb->oneb a b c) 388f80e-->3c856ad end
撤销合并 git revert -m 1或2
1 代表 merge commit 需要合并的分支 2代表被合并进来的分支
保留历史提交 commit ,取消合并后 生成了一个新的 commit id 29388db
29388db 的内容已经回复成 merge 前的内容,只有 a.txt 文件$ git revert -m 1 b28c56f $ git log --all --graph --oneline * 29388db (HEAD -> master) Revert "Merge branch 'oneb'" * b28c56f Merge branch 'oneb' |\ | * 3c856ad (oneb) oneb a b c | * 388f80e oneb a b |/ * d905789 master a $ ls a.txt
tag 管理
tag 有俩种类型
-
轻量级标签
轻量级标签本质上是提交的"书签",它们只是一个名称和指向提交的指针
用于创建指向相关提交的快速链接
$ git tag v1
-
带注解的 tag -a 会生成创建者,创建时间,备注的元数据
$ git tag -a v1 -m "注释" $ git show v1 tag v1 Tagger: someone <xxx@gmail.com> Date: Wed May 11 17:32:52 2022 +0800 注释
增加 tag
在当前的分支最新的 commit 上增加 tag
$ git tag tagName
为某个commit 增加 tag
$ git tag tagName commitID
查询 tag
查询所有分支的 tag
$ git tag
过滤需要的tag
$ git tag -l V1.*
删除 tag
删除本地 tag,可以一次删除多个 tag , 多个 tag 用空格隔开
$ git tag -d tagName
删除远程 tag,可以一次删除多个 tag , 多个 tag 用空格隔开
$ git push origin -d tagName
重命名 tag
在需要删除的 tagOld 处命名新 tagNew,之后在删除 tagOld
$ git tag tagNew tagOld
$ git tag -d tagOld
推送 tag 到远程仓库
将本地有, 但是服务器上没有的的标签都push到远程
# 发送指定的 tag,可以发送多个 tag,用逗号隔开
$ git push origin tagName
# 一次发送所有的 tags
$ git push origin --tags
head detached head 分离状态
head 分离状态意思是 head 没有指向当前分支的最新commit或就根本没有指向本地分支
git checkout commitId 移动 head 到指定的 commitId head 就会进入分离状态
没有指向当前分支的最新commit
当前分支
$ git log --oneline --all
8f5527e (HEAD -> oneb) oneb1
7b89a07 (master) master main1,2,3
36c1746 master main1,2
d62fd44 master main1
执行 git checkout commitId 后,在处于分离状态的head 的基础上提交的 commit 不能 merge,
如果离开当前的游离head,提交的 commit 会被回收
$ git checkout 36c1746
Note: switching to '36c1746'.
You are in 'detached HEAD' state.
# head 指向的 36c1746 就是游离的head
$ git log --oneline --all
8f5527e (oneb) oneb1
7b89a07 (master) master main1,2,3
36c1746 (HEAD) master main1,2
d62fd44 master main1
#在这个游离的head-->36c1746 基础上增加 commit-->dc902b6
* dc902b6 (HEAD) 游离head基础上增加的内容
| * 8f5527e (oneb) oneb1
| * 7b89a07 (master) master main1,2,3
|/
* 36c1746 master main1,2
* d62fd44 master main1
# 切换到别的分支,在游离态基础上增加的 commit 不会被保持
$ git checkout oneb
Warning: you are leaving 1 commit behind, not connected to
any of your branches
dc902b6 游离head基础上增加的内容
If you want to keep it by creating a new branch, this may be a good time
to do so with:
git branch <new-branch-name> dc902b6
Switched to branch 'oneb'
# 游离态增加的commit 没有了
$ git log --oneline --all --graph
* 8f5527e (HEAD -> oneb) oneb1
* 7b89a07 (master) master main1,2,3
* 36c1746 master main1,2
* d62fd44 master main1
如果要保存游离态增加的commit 需要增加一个分支和这些新增加的commit 绑定
#给游离态增加分支
$ git branch twob dc902b6
$ git log --oneline --all --graph
* dc902b6 (HEAD, twob) 游离态增加的内容
| * 8f5527e (oneb) oneb1
| * 7b89a07 (master) master main1,2,3
|/
* 36c1746 master main1,2
* d62fd44 master main1
#切换到master,
$ git log --oneline --all --graph
* dc902b6 (HEAD -> twob) 游离态增加的内容
| * 8f5527e (oneb) oneb1
| * 7b89a07 (master) master main1,2,3
|/
* 36c1746 master main1,2
* d62fd44 master main1
git checkout origin/branchName 或 git checkout remoteCommitId
切换到远程分支或切换到远程分支的 commitId,并且本地不存在对应的远程分支,就会进入分离状态
$ git log --all --oneline --graph
* 7f91e01 (HEAD -> master, origin/master, origin/HEAD) add a.txt main1
| * 25bc85b (origin/twob) add two.txt twob1
|/
| * 985951a (origin/oneb) add one.txt oneb1
|/
* c178a02 Initial commit
#查看远程分支
$ git branch -r
origin/HEAD -> origin/master
origin/master
origin/oneb
origin/twob
#查看本地分支
$ git branch
master
#切换到远程分支后 985951a 就成为游离的 head
#git branch oneb 生成本地相应的分支就会结束游离状态
$ git checkout origin/oneb
Note: switching to 'origin/oneb'.
You are in 'detached HEAD' state.
$ git log --all --oneline --graph
* 7f91e01 (origin/master, origin/HEAD, master) add a.txt main1
| * 25bc85b (origin/twob) add two.txt twob1
|/
| * 985951a (HEAD, origin/oneb) add one.txt oneb1
|/
* c178a02 Initial commit
* master
分支管理
创建分支
创建分支最好切换回主分支后, 在当前的主分支 head 指针处 创建分支 不要在分支上在创建分支,管理起来太复杂
#在当前分支的最新 commit 处创建分支
$ git branch 分支名字
# 在某个commit 或 tag 处创建分支
$ git branch branchName commitId
$ git branch branchName tag
# 在某个分支(basicBranchName)的基础上创建分支(branchName)
$ git branch branchName basicBranchName
创建一个无父分支
这个指令的用法是拷贝当前分支的内容,但是不拷贝提交的 commit
拷贝内容会放入新分支的工作区和暂存区,需要提交一次才能进入仓库
基本不会用到
$ git checkout --orphan pages
切换分支
切换head 到分支 切换分支的时候,Git 会用该分支的最后提交的快照替换你的工作目录的内容 所以多个分支不需要多个目录
$ git checkout branchName
#或
$ git switch branchName
# 切换回上一个分支
$ git checkout -
创建并切换分支一起执行
# 创建分支并切换到创建的分支
$ git checkout -b branchName
# 或
$ git switch -c branchName
在指定的commit 或 tag 处创建分支并切换
# 创建分支并切换到创建的分支
$ git checkout -b branchName commitId
# 或
$ git switch -c branchName tag
在指定的分支处(basicBranchName)创建分支并切换
# 创建分支并切换到创建的分支
$ git checkout -b branchName basicBranchName
# 或
$ git switch -c branchName basicBranchName
切换分支时有未提交的内容
-
切换时丢弃未提交的内容
# 当前分支有未提交的内容的情况下切换分支 # -f 丢弃当前分支未提交的修改 强制切换分支 $ git checkout -f branchName
-
切换时保持未提交的内容,切回来的时候可以恢复保持的内容 stash 英文是保存的意思
# 如果在切换分支时候不想丢弃工作目录的修改而且不想进行commit # 对当前分支的现场进行保持,这个时候切到别到分支未提交二修改内容不会丢失 $ git stash # 切回保持的分支,查看保持的内容 $ git stash list # 或 $ git show stash # 切回保持的分支,恢复保持的内容,并删除保持的内容 git stash pop # 丢弃保持的内容 git stash clear
查看分支
# 查看本地的所有分支
$ git branch
删除分支
# 删除已经合并的分支,但是分支提交的 commit 会保留
$ git branch -d twob
# 删除未合并的分支,在分支上创建的 commit 一并会被删除
$ git branch -d -f twob
重命名分支名字
# 重命名当前分支
$ git branch -m newBranchName
# 重命名指定分支oldBranchName
$ git branch -m oldBranchName newBranchName
比较分支差异
#比较俩个分支差异
$ git diff 分支1名字 分支2名字
#比较俩个分支差异,指定具体文件
$ git diff 分支1名字 分支2名字 a.txt
#比较俩个commit 差异
$ git diff commit1 commit2
#比较俩个 commit 差异,指定具体文件
$ git diff commit1 commit2 a.txt
查看各个分支最后一个提交对象的信息
$ git branch -v
查看已经合并到当前分支的分支
#查看已经合并到当前分支的分支
$ git branch --merge
查看未合并到当前分支的分支
$ git branch --no-merge
对远程分支管理
新建远程分支
前提是本地要存在本地分支
远程分支在本地没有直接新建的方法,通过 push 新建. git push remote localBranName:remoteBranName 需注意的是,分支的推送顺序写法是<来源地>:<目的地> 如果省略远程分支名则表示将本地分支推送到与之存在"追踪关系"的远程分支,如果远程分支不存在,则会被新建.
# 如果远程不存在同名的 localBranName 分支,远程就会新建一个分支
$ git push o localBranName
# 也可以指定远程的分支名字和本地不一致
$ git push o localBranName:remoteBranName
在push 同时绑定本地和远程的的分支 -u 等同于 set-upstream-to
# 新建一个和本地同名的远程分支并绑定
$ git push o -u localBranName
# 新建一个和本地不同名的原厂分支并绑定
$ git push o -u localBranName:remoteBranName
查看远程分支
#查看原创分支
$ git branch -r
#查看本地和远程分支
$ git branch -a
删除远程分支
删除远程分支在本地的引用,远程仓库并不会删除此分支
$ git branch -r -d origin/branchName
删除远程分支及本地的引用
- 方法1
#省略本地分支名相当于推送了一个空的本地分支到远程分支上,就相当于删除了远程分支 $ git push origin :branchName
2.方法2
$ git push origin -d branchName
本地分支和指定的远程分支建立追踪关系
建立绑定关系
远程仓库的远程分支存在的情况下,进行绑定
-
--track
本地分支不存在的情况下,建立本地分支并和远程分支进行绑定 git branch --track localBranchName RemoteBranchName 或 git checkout --track localBranchName RemoteBranchName
远程仓库存在分支 o/rBran
# 获取远程分支 rBran $ git fetch o rBran # 在本地建立分支 localBran 并绑定远程分支 rBran $ git branch --track localBran o/rBran # 因为建立了 localBran -->o/rBran # 的绑定关系,所以在任何分支都可以执行下面命令 # 如过没有绑定关系执行下面命令,会在远程仓库兴建一个 # localBran 分支,而不是 rBran # 或者的执行 git push o localBran:rBran 来明确的表达来源和去向 # 建立绑定关系就知道 push o localBran 到远程的仓库是 rBran $ git push o localBran # 如果当前分支在 localBran,可以直接执行如下命令 $ git push o
-
--set-upstream-to 或 -u
本地分支存在的情况下,和远程分支进行绑定 git branch --set-upstream-to=RemoteBranchName localBranchName --set-upstream-to 可以用 -u 代替 git branch -u RemoteBranchName localBranchName
# 建立本地分支 localBran $ git branch localBran # 拉去远程分支 rBran $ git fetch o rBran # 设置本地的分支 localBran 的上游分支是 rBran # 如果当前分支是 localBran 可以省去 localBran $ git branch --set-upstream-to=rBran localBran # 或 $ git branch -u rBran localBran # 建立绑定关系 就知道 push o localBran 到远程的仓库是 rBran $ git push o localBran # 如果当前分支在 localBran,可以直接执行如下命令 $ git push o
查看本地和远程分支的关联
$ git remote show origin
# 或
# oneb 和 origin/oneb 进行了绑定
$ git branch -vv
master 99dbe1d v1
*oneb 527f5a4 [origin/oneb] v2
twob 1872f80 v3
删除在远程仓库不存在,但是本地存在的引用分支
git remote prune origin 可以用 git fetch p 代替 prune 英文修剪修整的意思,stale 英文不新鲜的 如果远程的某些分支删除了,但是本地任然保留远程分支的引用,可通过以下命令删除这些引用
# 查看远程分支的情况,threeb 已经在远程被删除了
$ git remote show o
* remote o
Fetch URL: git@gitee.com:someone_repos/test.git
Push URL: git@gitee.com:someone_repos/test.git
HEAD branch: master
Remote branches:
master tracked
oneb new (next fetch will store in remotes/o)
refs/remotes/o/threeb stale (use 'git remote prune' to remove)
# 所有在被远程仓库删除的分支,但被本地依然保留的,删除掉
$ git remote prune origin
# 或 可以通过指定名字的方式删除本地引用
$ git branch -d -r o/threeb
更新本地远程分支的引用
git remote update origin --prune 不常用 可以用 git fetch origin 代替
会把远程分支的更新或本地不存在的远程分支更新到本地
git remote update origin --prune
git merge --ff(快进合并) 或 --no-ff(不快进合并). 无冲突合并
git 合并两个分支时,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,叫做快进 fast-forward,合并不需要要解决冲突,分支修改覆盖主分支.
如图 oneb 分支是在主分支最后一次commit 处开始的 如果合并分支的化,只是简单的移动head 到 7f6ed0c commit处 ,oneb 分支的修改会覆盖主分支。
merge 前
graph LR
head -.->cddaf35
cddaf35 -.->1ccf9fa
subgraph 主分支
3b60fae(master 3b60fae->main1) --> cddaf35(master cddaf35->main1 main2 )
end
subgraph oneb 分支
1ccf9fa[oneb 1ccf9fa->onebone main2]
1ccf9fa-->7f6ed0c[oneb 7f6ed0c->onebone onebtwo]
end
git merge --ff
graph LR
head -.->master
master -.->7f6ed0c
oneb -.->7f6ed0c
subgraph 主分支
cddaf35 -->1ccf9fa
3b60fae(master 3b60fae->main1) --> cddaf35(master cddaf35->main1 main2)
1ccf9fa[oneb 1ccf9fa->onebone main2]
1ccf9fa-->7f6ed0c[oneb 7f6ed0c->onebone onebtwo]
end
$ git log --all --graph --oneline
* 7f6ed0c (oneb) onebone,two
* 1ccf9fa onebone,main2
* cddaf35 (HEAD -> master) main1,2
* 3b60fae master main1
# git merge oneb 等同于 git merge --ff oneb
$ git merge oneb
Updating cddaf35..7f6ed0c
Fast-forward
a.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
master 合并前 a.txt 最后的内容是 main1 main2, oneb 合并前 a.txt 最后的内容是 oneb1 oneb2 合并后 master 的内容 a.txt 被 oneb 分支覆盖了,不需要解决冲突的问题
$ cat a.txt
onebone
onebtwo
-
直接使用 git merge --ff oneb 的弊端
把分支 oneb 俩次 commit 也混入主分支,主分支的 commit 不干净了,这种情况下如果对 master 分支进行回退,会引起混乱.
因为 master 主分支是稳定的,如果回退到 oneb 分支的某次 commit ,代码的状态就不稳定了,合并后主分支的 commit log
是四次,最后俩个 commit 是混入 oneb 分支的 commit$ git log --oneline --all --graph * 7f6ed0c (HEAD -> master, oneb) onebone,two * 1ccf9fa onebone,main2 * cddaf35 main1,2 * 3b60fae master main1
回退上一个版本的情况,回退到 oneb 分支的第一个 commit 了
$ git reset --hard head^ HEAD is now at 1ccf9fa onebone,main2 $ cat a.txt onebone main2
-
git merge --no-ff oneb ,不使用快进,不会把 oneb 分支的 commit 混入 master
依然会保留合并的 commit 但是回退上一个版本,不会回退到合并的分支 commit 上去
graph LR
head -.->master
master -.->af728a8
oneb -.->7f6ed0c
subgraph 主分支
3b60fae(master 3b60fae->main1) --> cddaf35(master cddaf35->main1 main12)
1ccf9fa[oneb 1ccf9fa->onebone main2]
1ccf9fa-.-> 7f6ed0c[oneb 7f6ed0c->->onebone onebtwo]
7f6ed0c-.-> af728a8(master af728a8->onebone onebtwo)
cddaf35--> af728a8
cddaf35-.-> 1ccf9fa
end
$ git merge --no-ff oneb -m "master 合并了 oneb 分支"
Merge made by the 'recursive' strategy.
a.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
# 合并后主分支新增了一个 commit,但是没有引入 oneb 分支的俩个 commit
$ git log --oneline --all --graph
* af728a8 (HEAD -> master) master 合并了 oneb 分支
|\
| * 7f6ed0c (oneb) onebone,two
| * 1ccf9fa onebone,main2
|/
* cddaf35 main1,2
* 3b60fae master main1
$ cat a.txt
onebone
onebtwo
# 可以看到 master 回退上一个版本是回退到自己上一个 commit
$ git reset --hard head^
HEAD is now at cddaf35 main1,2
git merge --squash 合并时合并分支的多个commit 为一个 新生成的 commit
squash 英文挤压的意思
不论合并进来的的分支 oneb 有多少commit 都不会进入 master 分支,只是把分支的代码进来合并,
分支的 commit 不会混入,代码合并后只是合并了工作区和暂存区,要进行一次提交,合并的内容才能进入仓库. 比 --no-ff 更彻底
graph LR
head -.-> master
master -.-> e98d0e9
cddaf35-.-> 1ccf9fa
subgraph 主分支
3b60fae(master 3b60fae->main1) --> cddaf35(master cddaf35->main1 main12)
cddaf35-->e98d0e9(master e98d0e9-> squash oneb)
end
subgraph oneb 分支
1ccf9fa[oneb 1ccf9fa->onebone main2]---> 7f6ed0c[oneb 7f6ed0c->->onebone onebtwo]
end
在master 分支执行
$ git merge --squash oneb
Updating cddaf35..7f6ed0c
Fast-forward
Squash commit -- not updating HEAD
a.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
# 执行完上面命令后,用 oneb 分支的俩个 commit 覆盖了 a.txt 文件
# 此时 master 并没有混入 oneb 分支的俩个 commit
$ cat a.txt
onebone
onebtwo
# 合并的是影响了工作区和暂存区,需要在提交一次到仓库
# 此时 master 分支只有俩个 commit
$ git commit -m "squash oneb"
[master e98d0e9] squash oneb
1 file changed, 2 insertions(+), 2 deletions(-)
# master 分支合并oneb 分支后文件二内容
$ cat a.txt
onebone
onebtwo
git merge 有冲突,不符合快速合并
git mergetool 可以打开一个界面查看冲突
三个分支 master, oneb, twob
graph LR
head--> 73217f6
e96e045 -.->73217f6(73217f6 1 one)
e96e045 -.->74572ac(74572ac 1 two)
subgraph master
e96e045(e96e045 1)
end
subgraph oneb
73217f6
end
subgraph twob
74572ac
end
合并 oneb merge twob 手工解决冲突
graph LR
head--> 5bc8622(1 one two)
e96e045 -.->73217f6(73217f6 1 one)
e96e045 -.->74572ac(74572ac 1 two)
subgraph master
e96e045(e96e045 1)
end
subgraph oneb
73217f6 -->5bc8622
end
subgraph twob
74572ac-.->5bc8622
end
解决冲突
-
当前有俩个分支 oneb , twob
$ git log --oneline --graph --all * 74572ac (twob) twob 1 two | * 73217f6 (HEAD -> oneb) oneb 1 one |/ * e96e045 (master) master 1
-
当前分支 oneb 执行合并 twob,有冲突
$ git merge twob Auto-merging a.txt CONFLICT (content): Merge conflict in a.txt Automatic merge failed; fix conflicts and then commit the result.
-
冲突的内容, 内容 one 和 two 冲突了
$ cat a.txt 1 <<<<<<< HEAD #当前分支 one ======= # 分支分割线 two >>>>>>> twob` #要合并的分支
-
合并产生的冲突解决方法
-
手工修改冲突部分,修改文件选择要保留的部分执行
修改后执行 git add a.txt, git commit
$ git log --oneline --all --graph * 5bc8622 (HEAD -> oneb) Merge branch 'twob' into oneb |\ | * 5ad4825 (twob) two 1 two * | 27a637a oneb 1 one |/ * 2c72eba (master) master 1
-
放弃合并
$ git merge --abort
-
合并后的分支没有存在的意义了,删除
$ git branch -d twob
rebase 合并变基
对同一分支的多个commit 进行增删改
使用场景: 减少分支的 commit
合并前
graph LR
head -.-> 320e2e8
subgraph 主分支
652d566(652d566 master 1)-->
a181faa(a181faa master 1 2)-->
2ac075a(2ac075a master 3 4)-->
320e2e8(320e2e8 master 5 6)
end
git rebase -i 652d566 320e2e8 以 a181faa 为基底 把 2ac075a 和 320e2e8 合并进 a181faa,并生成一个新的 commit 080023d
合并后
graph LR
head -.-> 080023d
subgraph 主分支
652d566(652d566 master 1) -->
080023d(080023d master 1 2 合并了2个 commit)
end
当前主分支有4 个 commit
$ git log --all --graph --oneline -p
* 320e2e8 (HEAD -> master) master 5 6
| diff --git a/a.txt b/a.txt
| index beb3268..cd541f1 100644
| --- a/a.txt
| +++ b/a.txt
| @@ -1,2 +1,2 @@
| 1
| -3 4
| +5 6
* 2ac075a master 3 4
| diff --git a/a.txt b/a.txt
| index 85f57a3..beb3268 100644
| --- a/a.txt
| +++ b/a.txt
| @@ -1,2 +1,2 @@
| 1
| -1 2
| +3 4
* a181faa master 1 2
| diff --git a/a.txt b/a.txt
| index d00491f..85f57a3 100644
| --- a/a.txt
| +++ b/a.txt
| @@ -1 +1,2 @@
| 1
| +1 2
* 652d566 master 1
diff --git a/a.txt b/a.txt
new file mode 100644
-i 交互时操作, 652d566 在这个commit 之后的 commit 到 320e2e8 这个 commit 之间的 commit 参与操作,是一个开闭区间,闭 commit 是最后一个 commit 可以不写
需要操作的 commit 最新的在最后边,如果要合并,至少要有一个 pick 在最老的 commit 上 如下把俩个 commit 合并到 a181faa
# 会出现交互节目进行配置变基
$ git rebase -i 652d566 320e2e8
p a181faa master 1 2
s 2ac075a master 3 4
s 320e2e8 master 5 6
如下合并后的情况,对修改做覆盖操作,最新的会覆盖最老的 只是减少了 commit 的节点, 内容没有丢失,合并后会生成一个新的 commit
$ git log --all --oneline --graph
* 080023d (HEAD -> master) master 1 2 合并了2个 commit
* 652d566 master 1
$ cat a.txt
1
5 6
pick:保留该commit(缩写:p)
reword:保留该commit,但我需要修改该commit的注释(缩写:r)
edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
squash:将该commit和前一个commit合并(缩写:s)
fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
exec:执行shell命令(缩写:x)
drop:我要丢弃该commit(缩写:d
不同分支的变基
使用场景: 拉取远程仓库 master 分支,在此分支的基础上开了一个 fixBug 分支, fixBug 分支写好后,再次拉取
master 分支,此时 master 已经更新了,为了方便合并上去,对 fieBug 进行变基到 master 分支的最新 commit
当前的分支
graph LR
69fd867(69fd867 -> master 1)
8aeed24(8aeed24 -> master 1 2)
720ab90(720ab90 -> master b.txt 1)
25d23df(25d23df -> oneb 1 3)
0fef2ef(0fef2ef -> oneb 1 3 4)
subgraph master
69fd867 --> 8aeed24
8aeed24 --> 720ab90
end
subgraph oneb
69fd867 --> 25d23df
25d23df --> 0fef2ef
end
git rebase master 对分支 oneb 变基后 更改 oneb 分支原来的基 master 69fd867 为 master 720ab90
graph LR
69fd867(69fd867-> master 1)
8aeed24(8aeed24-> master 1 2)
720ab90(720ab90-> master b.txt 1)
625371d(625371d-> oneb 1 3 rebase master)
4e96885(4e96885-> oneb 1 3 4 rebase master)
720ab90-->625371d
subgraph master
69fd867-->8aeed24
8aeed24-->720ab90
end
subgraph oneb
625371d-->4e96885
end
*对分支 oneb 变基前的分支情况
$ git log --all --oneline --graph
* 0fef2ef (HEAD -> oneb) oneb 1 3 4
* 25d23df oneb 1 3
| * 720ab90 (master) master b.txt
| * 8aeed24 master 1 2
|/
* 69fd867 master 1
# master a.txt
$ cat a.txt
1
2
# master b.txt
$ cat b.txt
1
# oneb a.txt
$ cat a.txt
1
3
4
上图oneb 分支在 69fd867 处开始分支,并提交 2次 commit,同时主分支在之后也提交 2次 commit
现在想要把 master 主分支的最新代码同步到 oneb 分支,但是不想要合并到主分支 可以把 对 oneb 进行变基 oneb 原来相对 master 的基是 69fd867,现在想切到最新的 720ab90
在当前分支 oneb 上 执行 git rebase master,在 69fd867 之后,oneb 和 master 都对 a.txt 进行了修改,
同时 mater 还新增了一个 文件 b.txt,所以现在的冲突有俩个
$ git rebase master
error: could not apply 25d23df... oneb 1 3
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 25d23df... oneb 1 3
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
# 添新的文件 b.txt 会自动加入
# 差异的文件,需要手工更新
$ cat a.txt
1
++<<<<<<< HEAD
+2
++=======
+ 3
++>>>>>>> 25d23df (oneb 1 3)
# 手工修改 第一个冲突 oneb 25d23df commit 和 master 720ab90 commit冲突
$ car a.txt
1
2
3
$ git add a.txt
# oneb 25d23df 会被丢弃,用 625371d 代替,新的commit message: oneb 1 3 rebase master
$ git rebase --continue
[detached HEAD 625371d] oneb 1 3 rebase master
# 第二个冲突
$ car a.txt
1
++<<<<<<< HEAD
+2
+3
++=======
+ 3
+ 4
++>>>>>>> 0fef2ef (oneb 1 3 4)
# 再次手工修改差异代码 oneb 0fef2ef 和 新的 oneb 625371d commit 冲突
$ car a.txt
1
2
3
4
$ git add a.txt
# oneb 0fef2ef 会被丢弃,用 4e96885 代替,新的 commit message oneb 1 3 4 rebase master
$ git rebase --continue
[detached HEAD 4e96885] oneb 1 3 4 rebase master
# rebase 后 现在分支
$ git log --all --oneline --graph
* 4e96885 (HEAD -> oneb) oneb 1 3 4 rebase master
* 625371d oneb 1 3 rebase master
* 720ab90 (master) master b.txt
* 8aeed24 master 1 2
* 69fd867 master 1
oneb a.txt 经过俩次手工合并冲突后
$ cat a.txt
1
2
3
4
oneb b.txt 是通过变基 master 获得的新文件
$ cat b.txt
1
提取分支指定的几个 commit 到其他分支
使用场景 对于多分支的代码库,将代码从一个分支转移到另一个分支是常见需求。 这时分两种情况.一种情况是,你需要另一个分支的所有代码变动,那么就采用合并(git merge) 另一种情况是,你只需要部分代码变动(某几个提交)
git rebase [startpoint] [endpoint] --onto [branchName]
其中 [startpoint] [endpoint] 前开后闭,--onto的意思是要将指定的提交复制到哪个分支上。
当前的分支
graph LR
3f85b32 -.-> 09c0472
head -.-> 8e8e788
subgraph oneb
09c0472(09c0472 oneb -> oneb1)
c89a874(c89a874 oneb -> oneb1,2)
b29146b(b29146b oneb -> oneb1,2,3)
09c0472 --> c89a874 --> b29146b
end
subgraph master
3f85b32(3f85b32 master -> main1)
8e8e788(8e8e788 master -> main1,2)
3f85b32 --> 8e8e788
end
git rebase c89a874^ b29146b --onto master 复制 oneb 分支的 c89a874 , b29146b 到 master 变成 e5e3dce , 340dd3c
graph LR
3f85b32 -.-> 09c0472
head -.-> 340dd3c
subgraph oneb
09c0472(09c0472 oneb->oneb1)
c89a874(c89a874 oneb->oneb1,2)
b29146b(b29146b oneb->oneb1,2,3)
09c0472-->c89a874-->b29146b
end
subgraph master
3f85b32(3f85b32 master -> main1)
8e8e788(8e8e788 master -> main1,2)
e5e3dce(e5e3dce master -> oneb1,2 rebase)
340dd3c(340dd3c master -> oneb1,2,3 rebase)
3f85b32 --> 8e8e788
8e8e788 --> e5e3dce
e5e3dce --> 340dd3c
end
执行命令前的分支情况
$ git log --all --graph --oneline
* 8e8e788 (HEAD -> master) master main1,2
| * b29146b (oneb) oneb1,2,3
| * c89a874 oneb1,2
| * 09c0472 oneb1
|/
* 3f85b32 master main1
在 master 分支执行,把 c89a874 到 b29146b 俩个commit 复制到 master 分支,提示有冲突
c89a874^ 因为是开闭区间,为了包含 c89a874, 使用了 c89a874^
$ git rebase c89a874^ b29146b --onto master
error: could not apply c89a874... oneb1,2
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply c89a874... oneb1,2
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
$ git diff
diff --cc a.txt
index 83e65d7,d13059c..0000000
--- a/a.txt
+++ b/a.txt
@@@ -1,2 -1,2 +1,7 @@@
++<<<<<<< HEAD
+main1
+main2
++=======
+ oneb1
+ oneb2
++>>>>>>> c89a874 (oneb1,2)
冲突修改 a.txt 文件为
cat a.txt
one1
one2
# 执行 git add 添加修改,继续执行生成分离节点 e5e3dce
# 提示还有第二个冲突
$ git add .
$ git rebase --continue
[detached HEAD e5e3dce] oneb1,2 rebase first
1 file changed, 2 insertions(+), 2 deletions(-)
Successfully rebased and updated detached HEAD.
执行完上面步骤后的分支图 head 指向 340dd3c,但是 master 还是停留在原来的 8e8e788,需要把 master 指向 head
$ git log --all --oneline --graph
* 340dd3c (HEAD) oneb1,2,3
* e5e3dce oneb1,2 rebase first
* 8e8e788 (master) master main1,2
| * b29146b (oneb) oneb1,2,3
| * c89a874 oneb1,2
| * 09c0472 oneb1
|/
* 3f85b32 master main1
# 把master 指向 340dd3c
$ git checkout master
$ git reset --hard 340dd3c
HEAD is now at 340dd3c oneb1,2,3
# 现在的分支图
$ git log --all --oneline --graph
* 340dd3c (HEAD -> master) oneb1,2,3
* e5e3dce oneb1,2 rebase first
* 8e8e788 master main1,2
| * b29146b (oneb) oneb1,2,3
| * c89a874 oneb1,2
| * 09c0472 oneb1
|/
* 3f85b32 master main1
通过 git cherry-pick
操作上和 git rebase 类似
git cherry-pick命令的常用配置项如下
- -e,--edit 打开外部编辑器,编辑提交信息
- -n,--no-commit 只更新工作区和暂存区,不产生新的提交。
- -x 在提交信息的末尾追加一行(cherry picked from commit ...),方便以后查到这个提交是如何产生的。
- -s ,--signoff 在提交信息的末尾追加一行操作者的签名,表示是谁进行了这个操作。
提取一个 commit 到当前分支,会在当前分支产生1个新的 commit
$ git cherry-pick commId
提取 多个 commit 到当前分支,会在当前分支产生2个新的 commit
$ git cherry-pick commitIdA commitIdB
剪提取指定的开闭范围的 commit 到当前分支,会在当前分支产生多个新的 commit
$ git cherry-pick commitIdA..commitIdC
分支切割机 git rebase --onto master next topic
master 分支上创建了 next 分支 next 分支的基础上创建了 topic 分支
意思就是 在master之上 把从next分支切出的topic分支 移动到master 分支
远程仓库
设置或修改全局 用户名或邮箱
设置的用户是用来追踪代码的提交修改操作的和 GitHub 上配置账号邮箱密码没有关系
$ git config --global user.name "username"
$ git config --global user.email "email"
查看 用户名或邮箱
$ git config user.name`
`$ git config user.email
生成密钥 用来和远程仓库 ssh 通讯加密
$ ssh-keygen -t rsa -C "全局标记"
全局标记 只是作为唯一标记,可以随便起名 密钥保存在当前用户的 .ssh 文件夹下
生成过程中需要的密码用途是用来保护生成的公钥,可以不输入
$ cd ~/.ssh
# id_rsa 私钥 d_rsa.pub 是公钥
$ ls
id_rsa cat id_rsa.pub
$ cat id_rsa.pub
# 出现的内容就是 公钥
把公钥的内容复制到github ssh key,用以下命令测试和 github s是否链接成功
$ ssh -T git@github.com
Hi man-someone! You've successfully authenticated, but GitHub does not provide shell access.
配置多个公钥,比如在Gitee 开通俩个账号,就必须要有俩个公钥
# bhs_rsa 生成钥匙的文件名
$ ssh-keygen -t rsa -C "xxx@gmail.com" -f ~/.ssh/bhs_rsa
$ ls
bhs_rsa bhs_rsa.pub id_rsa id_rsa.pub known_hosts known_hosts.old
多个公钥,在gitee 和 github 分别配置好,还需要在本地配置 在.ssh 目录下的文件 config 配置如下
- Host 用来区分同一网站 gitee 不同账号设置的 ssh
- HostName 网站的根路径
- IdentityFile 本地公钥的文件名
- User 用户名
# someone private github
Host github.com
HostName github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
User someone
# someone private gitee
Host gitee.com
HostName gitee.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
User someone
# company bhs gitee
Host bhs.gitee.com
HostName gitee.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/bhs_rsa
User someone
现在有三个远程 remote
- github.com
- gitee.com
- bhs.gitee.com
假如三个远程 remote 都有一个空仓库 gitDemo,本地也有 gitDemo 仓库,现在要把本地的分支 master 推送到远程 需要和三个远程分别建立绑定关系 githubOrigin,giteeOrigin,bhsOrigin 是远程仓库的别名 方便后面的提交等操作 git@后面要紧跟配置.ssh config 配置文件里配置的 Host
$ git remote add githubOrigin git@github.com:man-someone/gitDemo.git
$ git remote add giteeOrigin git@gitee.com:someone_repos/git-demo.git
$ git remote add bhsOrigin git@bhs.gitee.com:BHS_SH_0/git-demo.git
分别向远程的空仓库进行第一次 master 提交
$ git push githubOrigin master
$ git push giteeOrigin master
$ git push bhsOrigin master
本地库和远程库关联
git 是无中心化的代码管理 remote 表示的是每一个参与者,如果有3个参与者那就是三个 remote
为了方便管理会把一个 remote 叫做 origin ,大家都和它进行同步,让它保持最新
这里叫成什么名字都可以,不一定是 origin ,origin 只是一种约定名字
-
把本地 remote 和远程的 GitHub 仓库进行关联,并给这个 远程的 url 起个别名 origin git@ 后经跟.ssh config 里配置的 Host
-
git remote add
git remote add origin git@github.com:man-someone/gitDemo.git
显示所有的远程,因为本地的 remote 仓库就是自己 这里就没有显示
# 本地仓库关联的远程仓库 $ git remote origin $ git remote -v origin git@github.com:man-someone/gitDemo.git (fetch) origin git@github.com:man-someone/gitDemo.git (push)
-
git clone
$ git clone git@github.com:man-someone/gitDemo.git $ git clone git@bhs.gitee.com:BHS_SH_0/git-demo.git
-
-
删除本地仓库关联的远程仓库链接 git remote rm <remote_name> 一旦以这种方式删除了一个远程仓库,那么所有和这个远程仓库相关的远程跟踪 的文件 分支以及配置信息也会一被删除
未合并的远程分支和未合并的 commit 也会一起删除
$ git remote rm origin
在查不到任何远程分支的信息
$ git branch -r
-
给关联的远程仓库更改名字 git remote rename <old_remote_name> <new_remote_name>
$ git remote rename origin origin_other
查看远程仓库情况
$ git remote show origin
git clone 远程拷贝到本地
#默认colon 是 master 分支
#默认绑定远程仓库名字是 origin
$ git clone git@gitee.com:someone_repos/git-demo.git
#可以自定义远程名字是 myOrigin,通过 -o
$ git clone -o myOrigin git@gitee.com:someone_repos/git-demo.git
#以下命令会把所有分支 clone下来,唯一就是自动切换到 oneb 分支,意义不大
$ git clone -b oneb git@gitee.com:someone_repos/git-demo.git
git clone git@gitee.com:someone_repos/git-demo.git
执行后默认在本地创建了本地 master 分支和远程 origin/master建立绑定关系,远程的其他分支并没有进行绑定关系,执行 git branch oneb 就会在本地生成一个 oneb 分支对应远程的 origin/oneb 分支
$ git log --all --oneline --graph
* 7f91e01 (HEAD -> master, origin/master, origin/HEAD) add a.txt main1
| * 25bc85b (origin/twob) add two.txt twob1
|/
| * 985951a (origin/oneb) add one.txt oneb1
|/
* c178a02 Initial commit
#远程分支
$ git branch -r
origin/HEAD -> origin/master
origin/master
origin/oneb
origin/twob
#本地分支
$ git branch
* master
只 clone 指定的分支 git clone -b oneb --single-branch
#增加参数 single-branch,就只 clone oneb 一个分支
$ git clone -b oneb --single-branch git@gitee.com:someone_repos/git-demo.git
克隆到指定的目录
$ git clone git@gitee.com:someone_repos/git-demo.git dirName
push 推送分支的内容到远程仓库
命令格式:git push remoteName localBranchName:remoteBranchName
如果 push 的远程分支 remoteBranchName 不存在运程仓库会新建 remoteBranchName 分支 如果省略 remoteBranchName 分支名 git push remoteName localBranchName 除非绑定了远程和本地的分支,否则推送到同名的远程分支
此分支会覆盖远程仓库的分支代码,可以一次推送多个分支上去
推送成功的前提是要推送的本地分支拥有远程分支最新的提交,如果没有是不让推送的,因为有冲突的话
远程是个服务器,它不会合并代码
这种情况要先fetch 或 pull 拉取远程最新的代码到本地合并冲突提交后在推送本地的更新 本地的更新要考虑远程的更新
推送指定的本地分支到远程
推送本地分支 oneb 到同名的远程分支,远程不存在 oneb 分支就新建一个 也可以同时推送多个本地分支,用逗号隔开
$ git push o oneb
推送本地分支 oneb 到不同命的的远程分支 remoteBranName ,远程不存在 remoteBranName 分支就新建一个
$ git push o oneb:remoteBranName
# push 同时建立绑定关系
$ git push o -u oneb:remoteBranName
# 建立绑定关系以后就可以省略 remoteBranName
$ git push o oneb
pull 所有分支
$ git push o --all
删除远程分支
$ git push origin -d remotebranchName
# 或
$ git push origin :remotebranchName
push tags
把本地的所有 tags 传递到远程仓库 git push origin master 不会把 tag 推送上去 tag 要单独推送
$ git push origin --tags
push --force
push 在远程是 fast-forward 合并 如果不能执行说明有冲突
git push
--force标识会忽略fast-forward merge这个前提, 通过删除上游的修改(你最后一次pull的修改)来使得远程仓储分支和你本地的匹配
git fetch 获取远程仓库相对于本地仓库的更新或获取远程仓库新的分支,不会合并
命令格式: git fetch remoteName remoteBranchName
一旦远程主机的版本库有了更新(Git术语叫做commit),需要将这些更新取回本地,这时就要用到git fetch命令 fetch 到的内容是只读的,不能更新,要采用,只能合并或变基 即使进入 fetch 下来的分支进行了内容更新,退出 fetch 下来的分支,更新的内容仍然会丢失
获取更新或获取本地不存在的分支
# 先查看远程仓库的分支情况
$ git remote show origin
# fetch 单独的分支
# 可以指定多个分支,用逗号隔开
$ git fetch origin remoteName
# fetch 所有分支
$ git fetch origin
查看远程更新的内容
- git checkout o/master 进入分支查看文件
- git cat-file -p commitId 先进入 commit 对象,在进入tree 对象,在进入 block 对象
- git log -p fetch_head 或 git show fetch_head 也可以查看上一个 commit , git show fetch_head^
- git diff master origin/master 通过比对差异,查看内容
获取远程更新后的俩种处理办法
-
合并或变基
$ git merge origin/master # 或 $ git rebase origin/master
-
在远程分支的基础上创建新的本地分支
$ git branch localBranchName o/remoteBranName
远程删除了分支,但是本地还保留与远程的分支的引用可以使用如下命令更新本地的远程分支引用
# p prune
$ git fetch o -p
# 或
$ git remote prune origin
git pull 合并本地和远程的分支
命令的格式是:git pull remoteName remoteBranchName:localBranchName
git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并
git pull 指定的远程分支合并到当前分支
拉取 remoteBranName 分支和当前的本地分支 oneb 合并
$ git checkout -b oneb
$ git pull origin remoteBranName
# 或
$ git pull origin remoteBranName:
git pull 指定的远程分支合并到指定的本地分支分支
拉去远程分支 remoteBranName 到本地 oneb 分支,如果 本地不存在
oneb 分支,就新建一个,如何存在就和本地的 oneb 分支合并
$ git checkout master
$ git pull origin remoteBranName:oneb
git pull 所有分支合并到本地
本地不存在对应的分支就新建同名本地分支
$ git push origin
git pull rebase 代替合并
pull 等同于 fetch 和 merge 俩个命令一起执行
pull 的时候也可以不用默认的 merge 用 rebase,用法同上
在本地新建 twob 分支 twob, pull 远程分支 oneb 作为本地分支 twob 的 rebase 基 twob 有原来的基 master 变为 o/oneb
$ git checkout -b twob
$ git pull --rebase origin oneb
获取 git ls-remote 显示远程的分支及最新的commit
可以和 git remote show origin 进行比对查看
$ git ls-remote origin
7f9a44dc803916d42383ee2f0cfa698cffdce04f HEAD
7f9a44dc803916d42383ee2f0cfa698cffdce04f refs/heads/master
12c9d606b92c21f2da17c4fee12ca0a1c61d0dc0 refs/heads/oneb
0276c09d5ea5fddebdaea857ab627a04190bc08e refs/tags/v1.0
e5bb54594c94f23392e528ea8c8e4be8ec416df0 refs/tags/v1.0^{}
git 2.23 版本新增加的命令
git 2.23 版本新增了switch、restore命令,因为git checkout 命令职责较多、不够明确,而switch命令则专门用来切换分支、创建并切换分支等 switch
# 创建分支
$ git switch branchName
#在某个commit 或 tag 处创建分支
$ git switch branchName commitID
$ git switch branchName tagId
# 创建分支并跳转到相应的分支
$ git switch -c branchName
restore
git restore --staged <file_name> 将暂存区的修改重新放回工作区(包括对文件自身的操作,如添加文件、删除文件) git restore <file_name> 用commit 恢复暂存区的修改(包括对文件自身的操作,如添加文件、删除文件),工作区不受影响
.gitignore
.gitignore 文件要在仓库一开始就进行提交,因为已经被跟踪的文件,即使在 .gitignore 里进行了配置 不进行跟踪,已经跟踪的文件不会受 .gitignore 配置的影响,只有新文件才会受影响
作为补救措施,可以执行 git rm --cached fileName 在暂存区和本地库移除此文件,但是工作区保留此
文件.
配置规则
git 对于 .ignore 配置文件是按行从上到下进行规则匹配的,意味着如果前的规则匹配的范围更大
则后面的规则将不会生效
配置规则: * 以斜杠“/”开头表示目录 * 以星号“*”通配多个字符 * 以问号“?”通配单个字符 * 以方括号“[]”包含单个字符的匹配列表 * 以叹号“!”表示不忽略(跟踪)匹配到的文件或目录
示例说明
-
以 .out 作为后缀的文件全部忽略 *.out
-
以 .out 作为后缀并且以字母 a 开头的文件全部忽略 a*.txt
-
以 .out 作为后缀并且以字母 a 开头并且以字母b 结尾的文件全部忽略 a*b.txt
-
根目录下的 newDir 目录下的所有文件忽略 /newDir/*
-
目录 bar 下的所有文件忽略,不论 bar 在那个目录下面 bar/*
-
不忽略 bar 目录下的 bar.txt文件 !bar/bar.txt
特别处理
-
被或略的文件如果非要加入版本管理可以执行 git add -f fileName
-
可以用 git-check-ignore -v fileName 检查文件是被那条规则过滤的 如下是被第一条规则过滤的
$ git check-ignore -v a1.txt .gitignore:1:a*.txt a1.txt
git hook
git 能在特定的重要动作发生时触发自定义脚本.有两组这样的钩子:客户端的和服务器端的.
- 客户端钩子由诸如提交和合并这样的操作所调用
- 服务器端钩子作用于诸如接收被推送的提交这样的联网操作
钩子都被存储在 Git 目录下的 hooks 子目录中. .git/hooks,默认存在的都是示例,其名字都是以 .sample 结尾,
如果你想启用它们,得先移除这个后缀。把一个正确命名且可执行的文件放入 Git 目录下的 hooks 子目录中,即可激活
该钩子脚本.这样一来,它就能被 Git 调用. git hook 使用 linux shell 脚本编写
git 裸仓库
初始化裸仓库,是没有工作区的,只能查看git 提交的历史信息,不能做其他操作,如果要增加 commit 及分支要
以此裸仓库为基础的 clone 一个普通仓库,普通仓库向裸仓库进行 push , pull , fetch 等操作,或其他普通
仓库进行关联后(git remote origin 裸仓库的路径),也可以向此裸仓库进行 push , pull , fetch 等操作
裸仓库就象 gitHub 中托管的仓库一样裸仓库默认用 .git 结尾
裸仓库可以向其他仓库进行 push , fetch 操作,只要不涉及工作区的操作,裸仓库都可做
git init --bare test.git
# test.git 的根目录下,查看本仓库的配置
# bare = true 标记这是一个裸仓库
$ cat config
[core]
repositoryformatversion = 0
filemode = false
bare = true
symlinks = false
ignorecase = true
把一个普通仓库变成裸仓库
把普通仓库的.git 目录当成裸仓库,原来仓库的工作区全部丢掉,并修改配置
git config --bool core.bare true
$ cd repo
$ mv .git ../repo.git
$ cd ..
$ rm -fr repo
$ cd repo.git
$ git config --bool core.bare true
git 开发流程
当在团队开发中使用版本控制系统时, 商定一个统一的工作流程是至关重要的. Git 的确可以在各个方面做很多事情, 然而, 如果在你的
团队中还没有能形成一个特定有效的工作流程, 那么混乱就将是不可避免的.
开发流程都采用"功能驱动式开发"(Feature-driven development,简称FDD)
它指的是,需求是开发的起点, 先有需求再有功能分支(feature branch)或者补丁分支(hotfix branch) 完成开发后, 该分支就合并到主
分支,然后被删除.
Git flow的优点是清晰可控,缺点是相对复杂.需要同时维护两个长期分支. 大多数工具都将master当作默认分支,可是开发是在develop分支
进行的,这导致经常要切换分支,非常烦人。
更大问题在于,这个模式是基于"版本发布"的, 目标是一段时间以后产出一个新版本(develop 推进到一定程度后). 但是, 很多网站项目是"持
续发布", 代码一有变动,就部署一次. 这时 master 分支和 develop 分支的差别不大, 没必要维护两个长期分支.
SIT(System Integration Testing)系统集成测试 UAT(User Acceptance Test)用户验收测试
git flow (版本发布)
master
生产环境,主分支.这个分支包含最近发布到生产环境的代码,最近发布的 release ,这个分支只能从其他分支合并,不能在这个分支直接修改
用于 uat 发布或最终发布.绝不可直接 push. 所有在 master分支上的 commit 应该 tag,此分支一直保留在仓库内.
develop
测试环境,主开发分支.基于 master 分支克隆,只能从其它分支合并, 比如 feature 分支.绝不可直接 push. 包含所有要发布到下一个
release 的代码,此分支一直保留在仓库内.不断的合并其他 feature 分支,在此分支基础上临时开 release 分支去做测试,release 测
试后在合并回 master 和 develop. 此分支一直保留在仓库内.
我们尽量不要在 develop 分支上写代码,要保证 develop 分支的相对稳定,所以这时我要就要基于develop 分支创建一个临时的开发分支,
然后在开发分支上编写代码,等功能开发完之后我们再把开发分支合并到develop上
feature 功能开发分支,基于 develop 分支克隆.用于新功能新需求的开发,合并到 develop 分支进入下一个release ,临时分支,合并后可以删除.
release
预上线分支,测试(sit环境)分支.当你需要发布一个新功能的时候,要基于 develop 分支创建一个 release分支,在 release 分支测试并修
复bug,完成 release 后,把 release 合并到 master 和 develop 分支,在master分支上打标签用于发布, 临时分支,合并后可以删除.
hotfix
补丁分支,正式环境的版本进行 bug 修复当我们在生产环境发现新的 bug 时候,我们需要基于 master分支创建一个 hotfix 分支,然后在
hotfix 分支上修复 bug ,完成 hotfix 后,我们要把 hotfix 分支合并回 master 和 develop 分支. 在master分支上打标签用于发布.
临时分支,合并后可以删除.
https://nvie.com/posts/a-successful-git-branching-model/
github flow(持续发布)
github flow 是 git flow 的简化版, 专门配合"持续发布", 它是 Github.com 使用的工作流程. 它只有一个长期分支,就是master,因此用起来非常简单
github 项目如果不是自己的发布的, clone 别人的项目是无法直接 push 上去的,只能 fetch,pull. 如果想参与进来可以提 issue 或 提 pr
如果在 github 上建立一个组织,可以把别人加入组织,在组织内的成员都可以 push 代码 或者把别人拉到项目里作为参与者
1.创建组织 2.创建项目主仓库 3.Clone 主仓库 内部项目 3.Fork 主仓库 和 Clone fork仓库 开源项目 4.添加上游地址 5.同步最新代码 6.创建功能分支 feat/2 7.提交代码合并分支 8.合并最新代码 (解决合并冲突) 9.推送代码 10.提交 Pull request 11.讨论审核代码 12.合并和部署 13.删除功能分子 feat/2
gitlab flow (版本发布)(持续发布)
gitlab flow 是 Git flow 与 Github flow 的综合.它吸取了两者的优点,既有适应不同开发环境的弹性,又有单一主分支的简单和便利.
它是 gitlab.com 推荐的做法.
原理解释
git 目录
Git 将目录分为三个区:
*工作目录 (working directory): 即目录中除了 .git 目录之外的所有文件, 也就是我们平时写代码的地方 *暂存区 (index or staging area): 就是平时 add 完文件之后改动暂存的地方, 在 .git/index 下. 这里 的改动会在下一次 commit 的时候被加入到 repo 里 *Git 仓库 (Git Repository): 存放 git 的所有信息的地方, 也就是 .git 目录
暂存区 存在的意义直接代码提交本地仓库不更方便吗?
- Git中的Commit操作它是一个原子性操作(想一想你可以在git commit时去选择提交哪些吗?),也就是它会把暂存区的文件全部提交, 而且要么全部成功,要么全部失败;
- 那么你什么时候去选择要提交哪些文件呢?Commit操作是将暂存区的内容全部提交,所以我们要回到暂存区中思考,从工作区到暂存区, 使用Git Add 文件名,我们可以选择性地向暂存区添加内容,然后将其分批提交,暂存区的意义是它将你准备提交的内容分批整体处理; 例如:有在有紧急 bug 这个时候只要把有紧急 bug 相关的文件提交
git where 查看 git 安装路径 winds 系统需要下载 tree.exe 文件到 git 安装目录的 usr 目录的 bin 目录里增加 tree 命令 tree -L 1 .git 查看 .git 下的目录 tree ,-L 1 指定层级为1 -L 2 指定层级为2
$ tree -L 1 .git
.git
|-- COMMIT_EDITMSG # 最近 Commit 时打的 Commit Message. 提供这个文件主要是为了与各种 editor 交互
|-- HEAD # 当前
|-- config # INI格式的配置文件
|-- description #仓库描述文件
|-- hooks
|-- index # 暂存区文件
|-- info
|-- logs # 日志
|-- objects # 对象数据库 存放代码改动
|-- refs # 引用
查看 objects 目录 保存的到底是什么
通过 cat 文件是乱码,被压缩成为二进制信息了,要通过 git cat-file [-t] [-p], -t可以查看object的类型, -p可以查看object储存的具体内容,
数据对象(blob)二进制大对象
数据对象的产生是在使用git add命令将文件或者目录加入到暂存区时产生的,Git会把一个文件中要存储的数据和一个头部信息 一起做SHA-1散列运算,将得到的散列值作为这个文件的路径,散列值前两字符用于命名子目录,余下的38个字符则用作文件名
下面我们将通过具体过程来进行展示和分析。把 a.txt加入暂存区 修改三次文件每次修改完第1次内容为1 第二次为 1 2 第3次 1 2 3,通过 add 添加到 暂存区
objects 目录下生成三个文件代表三次提交,和文件名没有关系只是表示的是每次 add 的内容
$ ls 或 $ ll
a.txt
$ tree .git/objects
.git/objects
|-- 56
| `-- a6051ca2b02b04ef92d5150c9ef600403cb1de
|-- 5f
| `-- 5fbe759f10b9807b18f3898afd7505dbbfcbd2
|-- 7a
| `-- 754f414cd8ac6c069ac2e25baff0f55bff8b4a
|-- info
-- pack
a.txt 文件内容
$ cat a.txt
1
2
3
查看三个 add 的类型和内容
$ git cat-file -t 56a6
blob
$ git cat-file -p 56a6
1
git
$ git cat-file -t 7a75
blob
$ git cat-file -p 7a75
1
2
$ git cat-file -t 5f5f
blob
$ git cat-file -p 5f5f
1
2
3
通过 git show 5f5f 也可以查看内容 显示的内容与加入 objects 的文件中的内容是一样的,所以其实这个散列值路径文件中保存的只是源文件内容的一种压缩形式 文件的内容是这样加入到objects 的,但是 名字、路径、格式 保存在哪里呢
index 暂存区 保持文件的目录信息及指向 objects
$ git ls-files -s
100644 5f5fbe759f10b9807b18f3898afd7505dbbfcbd2 0 a.txt
100644:代表文件的类型为 blob, 最新的内容保存在: 5f5fbe759f10b9807b18f3898afd7505dbbfcbd2 这个文件里 ,文件名是 : a.txt
数据对象(blob) 和 index 暂存区的关系
数据对象(blob) 保持每次提交的内容,如果本次提交的内容之前提交过,就重复利用之前的内容,就不新建文件用于保持本次提交的内容 index 保持文件信息和文件指向的 数据对象(blob) 的 hash 文件名
添加目录并增加文件内容为 1 2, add 到暂存区 目录
$ tree
.
|-- a.txt
`-- newDir
`-- b.txt
暂存区内容
$ git ls-files -s
100644 5f5fbe759f10b9807b18f3898afd7505dbbfcbd2 0 a.txt # 1 2 3
100644 7a754f414cd8ac6c069ac2e25baff0f55bff8b4a 0 newDir/b.txt # 1 2
数据对象
$ tree .git/objects
.git/objects
|-- 56
| `-- a6051ca2b02b04ef92d5150c9ef600403cb1de # 1
|-- 5f
| `-- 5fbe759f10b9807b18f3898afd7505dbbfcbd2 # 1 2 3
|-- 7a
| `-- 754f414cd8ac6c069ac2e25baff0f55bff8b4a # 1 2
--pack
树对象(tree) commit(提交对象)
代码文件
$ cat a.txt
1
2
$ cat newDir/b.txt
1
暂存区内容
$ git ls-files -s
100644 7a754f414cd8ac6c069ac2e25baff0f55bff8b4a 0 a.txt
100644 56a6051ca2b02b04ef92d5150c9ef600403cb1de 0 newDir/b.txt
commit 后出现的tree 和 commit 文件
$ tree .git/objects
.git/objects
|-- 38
| `-- b746c1b3987269e9034729b0686483e5c6a5fb ## commit ->e46a 指向目录及文件和文件内容
|-- 56
| `-- a6051ca2b02b04ef92d5150c9ef600403cb1de ## blob 1
|-- 5b
| `-- dbe4af037c9493218efd2ba4ca00c12681b81e ## tree b.txt ->56a6
|-- 7a
| `-- 754f414cd8ac6c069ac2e25baff0f55bff8b4a ## blob 1 2
|-- e4
| `-- 6a7e036d2c27ec964cc6de44307e641f3fd564 ## tree a.txt->7a75 newDir ->5bdb
|-- info
-- pack
7 directories, 5 files
当前的仓库是这样的,就是给工作区的目录及文件和文件内容进行了一次快照
graph TD;
38b7[38b7 commit] --> e46a[e46a tree];
e46a --> 7a75[7a75 blob -> a.txt 1 2];
e46a --> 5bdb[5bdb tree -> newDir b.txt];
5bdb --> 56a6[56a6 blob -> b.txt 1 ];
Tag 对象
通常在软件发布的时候会打一个tag,用于标注这次发布的相关信息, 这样做的好处是,将来如果这个版本出现了问题,可以通过tag迅速定位到当前版本,进行错误修复。 在执行git commit 后可以执行
$ git tag -a v1.6 -m '第一个版本'
v1.6 就是这个tag的名称,通常以版本号命名。注意:tag是打在最近的一次Commit记录上的,比如我最近一次提交记录的 commit id 是 38b7,那么执行完上面命令后,tag就打在了这个Commit ID上。
object 内出现了一个新的文件,类型为tag ,内容指向最近一次 commit id
$ tree .git/objects
.git/objects
|-- 2f
| `-- 4ee9f9ccdde83eaad7ed4a514bdfe7483c3ed7
$ git cat-file -t 2f4e
tag
$ git cat-file -p 2f4e
object 38b746c1b3987269e9034729b0686483e5c6a5fb
type commit
tag v1.6
tagger someone <xxx@gmail.com> 1651588134 +0800
第一个版本
$ git log
commit 38b746c1b3987269e9034729b0686483e5c6a5fb (HEAD -> master, tag: v1.6)
Author: someone <xxx@gmail.com>
Date: Tue May 3 19:37:18 2022 +0800
oneCommit
可以过滤 tag
$ git tag #列出所有
v1.6
$ git tag -l 'v1.*' #tag 太多可以进行过滤
v1.6
用 tag 快速查看 提交的详细信息
$ git show v1.6
tag v1.6
Tagger: someone <xxx@gmail.com>
Date: Tue May 3 22:28:54 2022 +0800
第一个版本
commit 38b746c1b3987269e9034729b0686483e5c6a5fb (HEAD -> master, tag: v1.6)
Author: someone <xxx@gmail.com>
Date: Tue May 3 19:37:18 2022 +0800
oneCommit
diff --git a/a.txt b/a.txt
new file mode 100644
index 0000000..7a754f4
--- /dev/null
+++ b/a.txt
@@ -0,0 +1,2 @@
+1
+2
\ No newline at end of file
diff --git a/newDir/b.txt b/newDir/b.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/newDir/b.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
为历史提交添加 tag 信息或修改历史 tag
$ git tag -a v2.0 -m '第二个版本' 38b746c1b3987269e9034729b0686483e5c6a5fb
用图示来描述 git 仓库变化
当前文件目录及文件内容(# 后面是内容)
$ tree
.
|-- a.txt #1 2
`-- newDir
`-- b.txt #1
当前仓库快照
graph TB;
a.txt ---暂存区指向仓库7a75----> 7a75;
newDir/b.txt ---暂存区指向仓库56a6----> 56a6;
subgraph 工作目录
a[a.txt -> 1 2 ];
b[newDir -> b.txt -> 1 ];
end
subgraph 暂存区
a.txt ;
newDir/b.txt ;
end
subgraph git_仓库
Head --> master;
master --> 38b7[38b7 commit tag v2.0];
38b7 --> e46a[e46a tree];
e46a --> 7a75[7a75 blob -> a.txt 1 2];
e46a --> 5bdb[5bdb tree -> newDir b.txt];
5bdb --> 56a6[56a6 blob -> b.txt 1];
end
修改并 add , commit 当前文件目录及文件内容(#后面是内容)
$ tree
.
|-- a.txt #1 2 3
`-- newDir
`-- b.txt #1
当前仓库快照
graph TB;
a.txt ---暂存区指向仓库5f5f----> 5f5f;
newDir/b.txt ---暂存区指向仓库56a6----> 56a6;
subgraph 工作目录
a[a.txt -> 1 2 3 ];
b[newDir -> b.txt -> 1 ];
end
subgraph 暂存区
a.txt ;
newDir/b.txt ;
end
subgraph git_仓库
Head --> master;
master --> 9f4a[9f4a commit tag v3.0];
1eca(1eca tag v3.0) --> 9f4a;
9f4a --> 02a4[02a4 tree ]
02a4 --> 5f5f[5f5f blob -> a.txt 1 2 3]
02a4 --> 5bdb
1b8b(1b8b tag v2.0) --> 38b7[38b7 commit tag v2.0];
9f4a -.指向父 commit .->38b7
38b7 --> e46a[e46a tree];
e46a --> 7a75[7a75 blob -> a.txt 1 2];
e46a --> 5bdb[5bdb tree -> newDir b.txt];
5bdb --> 56a6[56a6 blob -> b.txt 1];
end
通过对比两次仓库的变化,Git储存的是全新的文件快照,而不是文件的变更记录。也就是说,就算你只是在文件中添加一行,Git也会新建一个全新的blob object。那这样子是不是很浪费空间呢?这其实是Git在空间和时间上的一个取舍,思考一下你要checkout一个commit,或对比两个commit之间的差异。如果Git储存的是文件的变更部分,那么为了拿到一个commit的内容,Git都只能从第一个commit开始,然后一直计算变更,直到目标commit,这会花费很长时间。而相反,Git采用的储存全新文件快照的方法能使这个操作变得很快,直接从快照里面拿取内容就行了。当然,在涉及网络传输或者Git仓库真的体积很大的时候,Git会有垃圾回收机制gc,不仅会清除无用的object,还会把已有的相似object打包压缩。
Git怎么保证历史记录不可篡改 通过SHA1哈希算法和哈系树来保证。假设你偷偷修改了历史变更记录上一个文件的内容,那么这个问卷的blob object的SHA1哈希值就变了,与之相关的tree object的SHA1也需要改变,commit的SHA1也要变,这个commit之后的所有commit SHA1值也要跟着改变。又由于Git是分布式系统,即所有人都有一份完整历史的Git仓库,所以所有人都能很轻松的发现存在问题。
为什么要把文件的权限和文件名储存在tree object里面而不是blob object呢,想象一下修改一个文件的命名。如果将文件名保存在blob里面,那么Git只能多复制一份原始内容形成一个新的blob object。而Git的实现方法只需要创建一个新的tree object将对应的文件名更改成新的即可,原本的blob object可以复用,节约了空间。
git 提交规范
Commit Message 格式
<type>(<scope>): <subject>
<body>
<footer>
可以看出分为三个部分,头部、主体、底部,其中主体和底部是可选的 body:主要对本次 commit 的详细描述,可以分成多行 footer:一些备注,主要放置不兼容变更和 Issue 关闭的信息,为了方便代码的快速提交,body 和 footer 部分可以省略。
不兼容变更指的是更改了当前功能,即接口逻辑跟之前版本发生很大变化,必须要前后端同时发布,否则会有一段时间服务不可用。 一般的做法是引入接口版本号,新老版本接口并存
头部
<type>(<scope>): <subject>
type 类型,修改的类型级别,是必选:
- feat:新功能(feature)
- fix:修补 bug
- docs:文档(documentation)
- style: 格式(不影响代码运行的变动),比如增加逗号,缩进,空格等
- refactor:重构(即不是新增功能,也不是修改 bug 的代码变动)
- perf: 优化相关,比如性能提升,用户体验等
- test:增加测试
- chore:构建过程或辅助工具的变动,比如改变构建流程,或增加或更改依赖库或工具
- revert:版本回滚
scope 修改范围:
- 主要是这次修改涉及到的部分,最好简单的概括, 影响范围(组件或文件),是可选的,比如应用层等 subject 修改的副标题:
- 主要是具体修改的功能点,第一人称现在时,动词开头,小写开头,英语 50 字符以内,结尾无句号,是必选
实例
git commit -m "feat(增加订单): 建立订单服务,持久化订单到数据库"
如果是用JIRA 管理开发,可以直接填写 JIRA 编号,例如 CODE-100
git commit -m "feat(增加订单):CODE-100 "
规范提交的方式
在当前用户的git 配置文件增加如下配置在提交的时候 输入 git commit 会自动出一个模板进行输入提交 [commit] template = C:/Users/user/gitCommitTemplate.txt
代码评审
代码评审发生在 Feature 分支向 Develop 分支合并前:
代码评审流程,在GitHub里就是提交一个 pr
不同的颜色块,代表不同的角色 创建一个 Merge Request,,在没有合并前, 可以进行多次 Push ,以应对代码评审。 开发人员推动整个代码评审流程的执行,包含及时通知组员评审代码、通知组长合并代码等
为了非法的提交代码设置保护分支,有代码只能通过创建 Merge Request 的方式合并到 develop 和 master 分支;为了代码库的安全,需要回收 Maintainers 权限,除组长外的开发人员都是 Developer 权限。
其他注意事项
- 提交合并请求时,先同步最新代码 提交合并代码前,建议先执行 git fetch 和 git merge/rebase 将 develop 分支下的最新代码更新到开发分支,再提交合并请求,避免造成冲突。
- 合并代码到 master 分支,通过 develop 分支提测且测试通过后,将 develop 分支的代码合并到 master 分支进行发版,版本发布完成时及时打标签