版本管理工具 Git 的使用
就软件开发来说, 不管是个人开发还是团队协作, 版本管理工具肯定必不可少. 其中分布式版本管理工具 git 由于其优良的性能, 可靠的安全性最为被接受, 也是最为普及的.
在完整地看了几个教程并且实战了一段时间后, 我将日常使用的命令以及个人理解总结成本文. 本文涉及内容有初级, 有高级, 如果能完全掌握这些命令绝对能在工作中祝你一臂之力. 建议收藏以备以后检索相关命令. 😁
分布式存储与集中式存储的区别
- 都有一个中央仓库
- 团队合作开发中每个人的仓库最终都必须同步到中央仓库
- 中央式存储没有个人仓库的概念, 因此只有做完一个小功能后上传到中央仓库才会有一个记录节点. 这样会导致一个提交里面包含很多代码, 不利于回溯.
- 分布式存储有个人仓库, 每个人做完一个极小的功能后可以先
commit
, 然后再 push, push 到中央仓库后也会将自己的诸多小commit
传上来.(如果不需要那么多小commit
的话可以使用rebase
依次合并多个小commit
然后再统一push
) - 分布式存储由于在每个机器上都会有完整的存储仓库, 因此本地占用会比较大, 初次 clone 也会稍微费事一些
- 结合以上特点, 由于软件开发主要是文本代码, 而这些资料的占用空间并不大, 而且软件开发对历史回溯要求比较高, 因此一般都是使用分布式存储. 集中式存储主要用于某些占用体积超大的项目开发领域, 比如大型游戏.
Git 特点
Git
的管理是目录级别, 而不是设备级别的, 即两个不同的目录存着同样地址的仓库的话会被认为是两个不同的仓库, 可以用于模拟为两个人.git
是以 line 为最小单位进行判断的, 即如果序号1
的行被 branch 修改了, 序号2
的行被master
修改了, 那么在merge branch
与master
的时候结果就是序号1
与2
都被修改了. 但是两个branch
都在序号1
的行修改了, 那么就会冲突, 会提示手动解决冲突.一个仓库中的文件都会被 git 系统监视, 一共有以下 3 种状态
未被跟踪
: 新建文件都不会被跟踪. 需要手动add
进入暂存区已保存
: 已经在系统中保存了此文件, 被放在保存区
未放入暂存区(changes not staged for commit)
已暂存
:commit(changes to be commited)
, 被放入暂存区
. 等待commit
中
其实
reset
的三个状态正好对应了文件的几种保存状态hard
: 回退commit
, 将两个commit
之间的改动放入暂存区, 再将暂存区所有文件放到保存区, 再将保存区
文件删除. 最硬的mixed
: 回退commit
, 将两个commit
之间的改动全部放入暂存区
, 再将暂存区所有文件放到保存区
soft
: 回退commit
, 两个commit
之间的改动全部放入暂存区
. 最软的.
reset
的三种模式中, 只有hard
会直接将文件直接转为未提交commit
之前的状态一个已被跟踪的文件被改动了, 只要保存更改就会被放入保存区. 此时可以
- 使用
git checkout -- filename
来永久撤销文件修改. - 使用
git add
命令将更改放入暂存区
等待commit
.
- 使用
未放入
staged area
的修改不会被commit
.如果一个文件未被跟踪 (
untracked file
), 那么Git
系统只会提示你在git
文件夹中有一个未被跟踪的文件, 哪怕对齐进行修改git
也不会关心其修改内容. 只要add
一次就可以永久跟踪.show
显示的是commit
状态及细节,diff
显示的是文件的修改状态和细节在对文件修改时, 只会在当前
branch
进行修改, 在push
时, 也只会将此内容push
到远端的此 branchgit
中的head
和branch
都是引用 (reference
), 其内存储的都是各个commit
的sha1
值. 引用都以文件形式存储在.git
目录中, 当git
工作时, 通过这些文件的内容判断整个仓库的结构.每个仓库只能有一个 head, branch 可以有多个
head
指向的branch
不能被删除, 必须签出到另一个 branch 方可删除git
中的branch
只是一个对commit
的引用, 删除branch
并不会删除任何commit
(不过如果一个commit
不在任何branch
上, 那么这个commit
就是一个野commit
, 其在一定时间后会被Git
的回收机制自动删除)checkout
的本质是移动head
到指定的commit
, 即如果后面跟的是branch
, 此命令会签出 此branch
所对应的commit
. 如果后面跟的是commit
, 那么直接签出该branch
, 比如git checkout 78a4bc
,git checkout HEAD^
,git checkout 78a4bc^
. 不过需要注意如果根据commit
值来签出, 那么会导致head
变为detached
状态, 哪怕签出的位置在某个branch
上也不行. 想离开这种detached
状态可以使用git checkout <branch name>
命令merge
会创建一个新的commit
来使两个branch
的最新commit
进行融合, 但是merge
会出现几种冲突情况:两个
branch
中的同一个文件的同一行被各自branch
修改了(只能手动选择保留部分了)目标
commit
(需要被merge
的commit
) 与head
所指向的commit
并不存在分叉, 而是head
领先于目标commit
: 此时merge
不会创建一个新的commit
, 因为没有需要合并的, 什么也不会做目标
commit
与head
所指向的commit
不存在分叉, 但是head
所指向的commit
落后于目标commit
: 此时merge
依然不会创建一个新的 commit(因为没哟需要合并的内容), 此时会将head
指向的commit
快速向前移动 (fast-forward
). [这其实非常常见: 本地没有提交, 但是同事开发了新内容并合并到了master
上, 本地pull
的时候会先 进行fetch
, 然后merge
, 此时merge
的目标commit
即领先于 head 指向的commit
, 这个时候就会进行fast-forward
)
merge
后branch
不会被自动删除没有被
merge
的branch
在删除时会失败(但是如果确认某个branch
完全没有作用了, 一定要删除, 那么可以将git branch -d
改为git branch -D
来强制删除)pull
=fetch
+merge
push
并不会上传head
到远端, 即远程仓库永远只会指向master
. 这也是为什么从远端clone
下来后在第一次使用时总是指向master
的.不是最新的几次提交不能用
git reset
. 不是最新的一次提交不能用git commit --amend
rebase
可以合并commit
, 修改之前某次commit
, 删除之前某次commit
, 属于 git 中的高深用法.在
git
中有两个偏移符号^
与~
,^
表示根据head
向左偏移,~
表示根据branch
向左偏移tag
可以理解为 不可移动的branch
, 通常用来为项目节点做标记cherry-pick
: 把选中的commit
一个个合并
Git 常用命令
git help <command>
: 对 git 的某一命令查看帮助, e.g.git help config
git help --web log
: 在浏览器中查看git log
的用法clone
: 克隆仓库git clone <link> [foldername]
: 从远端链接拉取项目, link 可以为https
连接, 也可以为ssh
连接, 最后的 foldername 表示可以指定文件夹名称git clone -b master <link>
: 克隆远程仓库到本地, 并 checkout 到master
分支git clone -b 0.0.5 <link>
: 克隆远程仓库到本地, 并 checkout 到 tag0.0.5
上git clone --recursive <link>
: 递归克隆, 在项目包含子模块时非常有用git clone --depth=1 <link>
: 克隆深度为 1, 只克隆最后一条, 减少克隆时间git clone --bare <link>
: 裸克隆, 没有工作区内容,不能进行提交修改,一般用于复制仓库git clone --mirror <link>
: 镜像克隆, 也是裸克隆, 区别于包含上游版本库注册
status
: 状态git status
: 查看当前branch
的当前状态与最近一次commit
相比, 暂存区&保存区&以及未跟踪文件的状态, 提交前一定要用git status -s
: 以短格式输出git status --ignore-submodules
: 忽略子模块git status --ignored
: 显示忽略的文件
init
: 初始化git init
: 将当前目录直接作为git
的工作路径并生成一个.git
文件夹git init [repo name]
: 将repo name
作为工作路径并在repo name
文件夹下剩下一个.git
文件夹git init repo.git --bare
: 创建一个repo.git
的文件夹并将其作为 git 库, 其内直接包含.git
文件夹内的所有文件, 相当于去除了工作区
git config [range] [command] [option]
: 配置 git 环境range
: 有三种级别, system, global, local, sytem 针对当前系统所有用户的所有 repo, gloabl 针对当前用户的所有 repo, local 只针对当前 repo, 他们的关系是如果下一级的 config 没有对某项配置做自定义, 就自动引用上一级的 config 相关配置--system
: 将 config 配置写入/etc/gitconfig
--global
: 将 config 配置写入~/.gitconfig
文件--local
: 默认级别, 将 config 配置写入当前 repo 的.git/config
文件中
command
: 命令--list
: 列出当前 repo 的所有 config 信息(如果使用了 global 则列出 global 的 config 信息)--unset
: 取消 config 中某项配置(后跟 option), 也可以通过编辑.gitconfig
或config
文件来达到同样目的--unset-all
: 取消 config 中所有配置--remove-section
: 移除某组配置--rename-section
: 重命名某组配置
option
: 具体配置--user.name
: 设置用户名, 安装 git 必设置--user.email
: 设置用户邮箱, 安装 git 必设置--core.editor
: 设置 git 的默认编辑器, 默认为 vi 或 vim--merge.tool
: 设置 git 的合并工具alias.st status
: 设置 git 的某些快捷别名, e.g.git config --global alias.st status
的作用是让git st
指向git status
git config --global --unset user.name
: 删除相关配置, 可以是--global
, 也可以是--local
git config --global -e
: 编辑当前仓库配置文件, 等价于vim ~/.gitconfig
git config --global https.proxy http://127.0.0.1:1087
: 配置代理git config --global http.proxy http://127.0.0.1:1087
: 配置代理push
: 推送本地仓库到远程git push origin test
: 将当前branch
push
到远程的test
分支上事实上
git push
命令也可以进行push
, 不过git push
只能push
从远端pull
或者clone
下来的branch
, 对于由本地直接创建的branch
就无能为力了, 或者本地创建的仓库使用git push --set-up-stream origin branch1
命令指定了本仓库对应的远程仓库分支, 这样也能直接使用git push
git push origin test -f
: 强制push
, 在本地仓库与远程仓库有差别被拒绝的时候但是自己很清楚的时候使用. 但是如果冲突发生在master
的话就不要用了. 很危险.git push origin <local branch1>:<remote branch2>
: 将本地branch1
推送到远程branch2
上remote branch2
不写的话表示将本地分支branch1
推动到远程同名branch
上local branch2
不写的话代表将空明推送到远程branch2
上, 也就是表示删除远程branch2
git push origin 9790eff:master
: 将本地9890eff
以前的所有commit
推送到远端git push origin :<old name>
|git push origin <new name>
: 重命名远程分支(方法 1), 原理是先删除远程某分支, 然后将本地当前分支推送到新命名的远程分支上
fetch
: 从远端获取仓库对应分支的最新状态git fetch -a
: 从远端获取仓库所有分支的更新(不合并任何分支)git fetch origin
: 手动指定了要fetch
的remote
, 在不指定分支时通常默认为master
git fetch origin dev
: 指定远程remote
和FETCH_HEAD
, 并且只拉取该分支的提交git fetch origin branch1:branch2
: 从服务器拉取远程分支branch1
到本地为branch2
, 并使branch2
与branch1
合并
git pull
: 从远端拉取仓库最新状态并与本地仓库合并git pull -a
: 从服务器远端拉取仓库的所有分支的更新, 并将当前分支对应的远程分支的更新合并到本地当前分支上(不合并其他分支)git pull orign test
: 从服务器拉取远端名为test
的branch
并与本地当前的branch
合并(这个命令适用于在本地git branch <name>
或git checkout -b <name>
刚建立了一个新的本地分支, 然后从服务器的指定分支拉取commit
到本地此新分支上)git pull origin master --rebase
: 以变基方式拉取远端主分支到本地master
分支, 主要用于第一次拉取远端分支git pull origin branch1:branch2
: 从服务器远端拉取branch1
分支合并到本地的branch2
分支, 如果本地没有branch2
分支的话则新建. 然后将拉取到的分支合并到当前所处的分支上git pull origin master:master
: 从服务器拉取远程master
到本地master
上, 然后合并, 然后将 master 分支 merge 到当前所处的分支上. 这个比git fetch
多了一步(合并master
到本分支), 因此要慎用, 最好用git fetch
, 然后自行判断git pull origin master:master --rebase
: 与上一种功能类似, 不过是将当前分支rebase
到master
分支上
remote
: 远程仓库git remote -v
: 查看远程仓库地址git remote set-url origin <url>
: 在有 origin 的基础上直接重新设置远程仓库地址git remote rm origin
: 删除远程仓库地址git remote add origin <url>
: 在没有远程仓库的基础上添加远程仓库地址git remote show origin
: 查看远程仓库信息(比如 push 与 pull 地址, 远程仓库当前 head 指向, 远程仓库当前分支)git remote rename oldname newname
: 重命名远程仓库
git show
: 查看最近一次commit
(head
所指向的commit
) 修改的文件和内容git show --stat
: 查看最近一次commit
的统计信息(修改了多少处)git show 5e68b0d8
: 查看sha
值为5e68b0d8
的commit
修改内容git show 5e68b0d8 a.txt
: 查看sha
值为5e68b0d8
的commit
中 a.txt 文件的具体修改情况
git diff
: 显示目前的保存区与最近一次commit
的原工作目录相比有什么差异. 即, 在git add
后会向暂存区提交什么内容git diff --staged
: 查看暂存区与最近一次commit
的原工作目录相比有什么差异. 即, 这条指令可以让你提前知道你commit
会提交什么内容. 这个命令与git diff --cached
完全等价git diff master..branch
: 比较master
与branch
之间的不同git diff 0023cdd..fcd6199
: 比较两个commit
之间的不同git diff README.md
: 查看当前分支README.md
文件的变动git diff adt312d
: 查看adt312d
这个commit
与当前最新commit
的异同
git ls-files
: 列出本分支下所跟踪的文件列表git add
: 添加改动到暂存区 (staged area
), 虽然add
后添加的是文件名, 但实际上添加的是改动(如果a.txt
改动后被add
, 然后再次被改动a.txt
的另一处, 那么在git status
时会警告a.txt
既在暂存区又不在)git add ./README.md
: 仅暂存当前目录下的README.md
文件git add <file1> <file2>...
: 一次暂存多个文件git add .
: 提交本路径下的全部更改(新文件, 修改文件, 删除文件)(会忽略.gitignore
中列出的新增文件)git add -A
: 同git add .
, 不过添加的是本仓库的所有路径下的更改(会忽略.gitignore
中列出的新增文件)git add -u
: 只提交已经跟踪文件的修改(不理会新文件)git add --ignore-removal .
: 只提交新文件与修改文件(不理会删除文件)
merge
: 合并分支git merge test
: 将名为test
的branch
合并到当前head
所指向的分支git merge --abort
: 在出现merge conflict
状况时放弃此次merge
, 会恢复到merge
之前的状态.git merge --continue
: 解决冲突后继续merge
git mergetool
: 在合并出现问题时使用此工具进行手动合并git merge develop -q
: 以安静模式合并, 吧 develop 分支合并到当前分支并不输出任何消息git merge develop --no-edit
: 合并时使用默认的合并消息git merge develop --no-commit
: 合并分之后不进行提交
cherry-pick
: 挑选commit
git cherry-pick commit1 commit2 commit3
: 将三个commit
合并入本branch
git cherry-pick commit1 commit2 commit3 --no-commit
: 将三个commit
的内容放入本branch
的暂存区但是先不合并git cherry-pick -x commit1
: 在合并时将commit1
的原有作者信息进行保留
git commit
: 提交通过add
命令放入暂存区的改动git commit -a
: 提交全部更改(默认将所有修改文件及删除文件添加进暂存区)git commit -m "message"
: 使用message
作为commit
的标题直接提交git commit --amend
: 对最新的commit
进行修改(此操作不会直接在原commit
上进行修改, 而是将新修改内容与最新commit
内容进行融合, 据此创建一个新的commit
并进行替换)git commit --amend --no-edit
: 在amend
的基础上, 不进入修改 message 界面git commit --amend --reset-author
: 默认情况下amend
并不会重置第一次author
的时间, 使用--reset-author
可以重置author
时间为当前时间git commit --author "HanleyLee <hanley.lei@gmail.com>"
: 指定 author 的方式进行 commitGIT_COMMITTER_NAME="HanleyLee" GIT_COMMITTER_EMAIL="hanley.lei@gmail.com" git commit --author "HanleyLee <hanley.lei@gmail.com>"
: 同时设置author
与committer
git commit --allow-empty-message
: 允许提交空消息, 通常必须有消息git commit -v
: 在填写信息的界面显示所有变动(diff
格式的)
git stash
: 将保存区与暂存区的文件(未commit
的)临时放入一个空间(注意: 未跟踪的文件不会被stash
)git stash save "test"
: 保存时添加注释git stash list
: 查看当前保存列表git stash pop
: 将stash
空间保存的文件还原到保存区中git stash pop stash@{1}
: 恢复指定 stash, 具体编号可以通过git stash list
查找git stash apply
: 与 pop 命令相同, 不过不会从 stash 列表中移除git stash clear
: 清空所有保存的 stashgit stash drop stash@{0}
: 清除指定 stashgit stash drop
: 清除最近一次branch
分支管理git branch test
: 从head
所指向的commit
处创建一个名为test
的新的branch
git branch branch1 origin/branch1
: 从本地下载的远程branch1
处在本地建立一个branch1
分支git branch -a
: 显示本地及远程所有分支git branch -r
: 显示远程端所有分支git branch -vv
: 查看本地分支所关联的远程分支git branch -d test
: 删除名为test
的branch
git branch -r -d origin/hanley
: 删除本地已经下载的远程分支, 同时要执行以下两个命令中任意一个:git push origin --delete <branch name>
: 使用删除命令直接删除远程branch
git push origin :<branch name>
: 使用推送命令将一个空branch
推送到远程以达到删除该 tag 的效果
git branch -m <old name> <new name>
: 重命名本地分支git branch -f <branch> <commit>
: 重新定义 branch 的起始节点到某个commit
git branch -m <old name> <new name>
|git push origin :<old name>
|git push --set-upstream origin <new name>
: 重命名远程分支(方法 2), 先本地重命名, 然后删除远程某分支, 最后推送并设定推送到的远程branch
名git branch --merged
: 查看已经合并的branch
git branch --no-merged
: 查看未合并的branch
git branch --merged | xargs git branch -d
: 删除已经合并的branch
git reflog show --data=iso master
: 查看本地master
分支的创建时间
checkout
: 签出git checkout <filename>
: 将保存区的文件永久discard
文件的修改. 降级! (使用版本库的版本替换工作区的版本)git checkout test
: 签出名为test
的branch
对应的commit
git checkout HEAD^^
: 将HEAD
向左两位的commit
签出(即倒数第三位)git checkout head~3
: 将head
向左三位的commit
签出(即倒数第四位)git checkout 3d122b
: 将sha
值为3d122b
的commit
签出, 此时会导致head
变为detached
状态, 想离开这种detached
状态可以使用git checkout <branch name>
命令git checkout -b test
: 创建一个名为test
的branch
并签出到该branch
对应的commit
git checkout -b test origin/test
: 在本地创建名为test
的branch
并从远端的test
分支拉取下来合并git checkout --detach
: 使head
与branch
分离, 使head
直接指向commit
git checkout -
: 返回上一个 head 点git checkout -t origin/dev
: 切换到远端分支, 通常是在本地没有远程分支才会本命令git checkout -f master
: 强制切换到 master, 如果有未保存的文件会被丢弃git checkout -- .
: 撤销当前目录下所有文件的改动git checkout -- README.md
: 仅撤销README.md
文件的改动
blame
: 责怪~git blame <filename>
: 查看某个文件的修改历史记录, 含时间, 作者, 以及内容git blame -L 11,12 <filename>
: 查看谁改动了某文件的 11~12 行git blame -L 11 <filename>
: 查看 11 行以后的所有改动人git blame -l <filename>
: 显示完整的 hash 值git blame -n <filename>
: 显示修改的行数git blame -e <filename>
: 显示作者邮箱git blame -enl -L 11 <filename>
参数组合查看修改者
restore
: 重置git restore <filename>
: 将保存区的此文件更改全部重置, 降级! 与git checkout <filename>
功能相同git restore --staged <filename>
: 将暂存区文件转移至保存区, 降级!git checkout -- *
git checkout -- *.md
git checkout -- 123.md 345.md
reflog
: head 记录git reflog
:reference log
的缩写. 可查看Git
仓库的head
的所有移动记录. 可以在误删 branch 等情况下使用git reflog master
: 查看关于master
的所有head
的移动记录.
rm
: 移除git rm <filename>
: 删除对文件的跟踪, 并删除本地文件(未添加到暂存区时使用)git rm -f <filename>
: 删除对文件的跟踪, 并删除本地文件(添加到暂存区时使用).f
是强制的意思git rm --cached <filename>
: 取消对某个文件的跟踪. 而不删除本地文件git rm -r --cached <foldername>
: 取消对某个文件夹的跟踪.r
为递归的意思.git rm -r *
会将当前目录下的所有文件与子目录删除git rm -rf .
: 清除当前目录下的所有文件, 不过不会删除.git
目录-n
: 所有的rm
命令后面加上此命令后, 不会删除任何东西, 仅作为提示使用
git clean <command>
: 清理未被tracked
的文件,git reset
只能让跟踪的文件回复到某个版本状态, 对于未跟踪的文件无能为力, 如果想要完全移除未跟踪的文件, 那么就要使用git clean -df
, 此命令常与git reset
配合使用(默认情况下,git clean
命令只会移除没有忽略的未跟踪文件, 如果也需要移除已被gitignore
忽略的文件, 则需要加x
)-f
: 强制删除. 如果Git
配置变量clean.requireForce
未设置为false
,git clean
将拒绝删除文件或目录, 除非给定-f
,-n
或-i
.-f <path>
: 删除指定路径下untracked files
-d
: 除了未跟踪的文件之外, 还要除去未跟踪的目录.-X
: 仅删除当前目录下gitignore
里忽略的文件, 那些既不被git
版本控制, 又不在gitignore
中的文件会被保留.-x
: 不使用gitignore
的忽略规则, 删除本路径下所有的untracked files
-n
: 将此命令加在上面三个命令前, 先看看会删除哪些文件(相当于演习一遍). e.g.git clean -n -xfd
-i
: 使用交互式删除, 每一个文件的删除都有提示, 更加安全
tag
: 不可移动的标识点, 通常用来作为里程碑标记, 最广泛的使用就是作为版本标记git tag
: 显示所有tag
git ls-remote --tags origin
: 列出远程所有标签git tag <tag name>
: 为最新 commit 的创建 taggit tag <tag name> <commit name
: 为之前的某个 commit 点创建 taggit show <tag name>
: 显示指定 tag 信息git tag -d <tag name>
: 删除本地的指定 tag; 如果想要删除远程的 tag, 需要先在删除本地 tag, 然后使用:git push origin --delete tag <tag name>
: 使用删除命令直接删除远程 taggit push origin :refs/tags/<tag name>
: 使用推送命令将一个空 tag 推送到远程以达到删除该 tag 的效果
git push origin <tag name>
: 推送指定 tag 到远程git push origin --tags
: 推送所有本地 tag 到远程git ls-remote --tags origin
: 显示远程所有 tag(不加 origin 也可以)git tag -l | xargs git tag -d
&&git fetch origin --prune
: 先删除本地所有分支, 然后从远端拉取所有分支, 适用于远端的 tag 被修改但是本地 tag 仍然是旧的git tag -l v1.*
: 筛选符合条件的 taggit tag -a v1.4 -m "my version 1.4"
: 创建含标注的 tag, 并为此标注直接添加信息(标注可通过 git log 查看)git tag -a v1.2 9fceb02
: 为之前的提交打 tag, 会进入填写 message 界面git fetch --tags
: 拉取远程所有 tag
revert
: 添加与之前commit
完全相反的commit
git revert HEAD^
: 增加一条与当前head
指向的commit
的内容完全相反的commit
. 从而达到”中和”的效果以对其进行撤销. 用在错误内容已经合并在master
但是需要修改的时候.
reset
: 重置到某个commit
git reset --hard HEAD^^
:HEAD^^
表示需要恢复到的commit
, 因此这个命令表示将 track 的文件直接恢复到上上一个commit
, 其后的所有commit
全部丢弃(如下图所示, 虽然commit
不被任何branch
指向了, 但是Git
不会立刻删除它, 还是可以通过sha1
值来复原的), 一般与 git clean 联合使用git reset HEAD ./README.md
: 仅重置某文件到HEAD
rebase
: 变基git rebase master
: 在branch
上执行此命令. 将branch
上从与master
交叉的commit
之后的所有commit
依次提交到master
最新commit
之后(就是将节点 5, 6 的内容在master
分支再次提交一次). 如果想指定基础点参考 这里git rebase -i <commit>
: 交互式变基, 可合并 commit, 剔除 commit 等git rebase -i HEAD~10
: 将当前 head 向前数 10 个的所有 commit 进行变基git rebase --continue
: 解决冲突后, 解决冲突, 并git add
, 然后使用本命令可继续 rebase 操作, 不需要git commit
git rebase --abort
: 产生冲突时, 放弃本次 rebase, 恢复到 rebase 之前的状态git rebase origin/main
: 变基对象为 origin 远程分支git rebase 的作用是变基, 在合并时可以先切换到 branch1, 然后对 master 进行 rebase, 没有冲突的话直接完成操作, 有冲突的话先解决冲突, 然后
git add
, 然后git rebase --continue
, 这样 branch1 的commit 就完全挪到了 master 之上, 这时再切换到 master 分支上, 使用git rebase branch1
(或git merge branch1
), 可以达到fast-forword
的效果. 最后可以 push 到远程仓库, 这样自己的新的 commit 就在 master 上整齐地排列着了.
git log
: 显示commit
的提交记录(并不会显示add
操作)--patch
: 是git log -p
的全写, 可以查看每个commit
的细节--stat
: 查看每次文件提交更改的统计信息(修改了多少处)--oneline
: 以一行的格式查看本branch
的commit
记录(仅 sha2, branch, commit title 以及顺序信息)--merges
: 只显示合并的commit
--no-merges
: 不显示合并的commit
--graph
: 图表形式(竖线, 斜线)显示 commit 顺序及关系--decorate
: 装饰作用, 显示当前 head, tag, branch 的效果--all
: 显示所有commit
信息, 否则可能会只显示本branch
的commit
, 省略其他分支的commit
-S Swift
: 显示关键字中有Swift
的commit-[number]
: 显示最近多少次的commit
记录--pretty=format:"[option]"
: 以固定格式输出commit
信息%H
: 提交的完整哈希值%h
: 提交的简写哈希值%T
: 树的完整哈希值%t
: 树的简写哈希值%P
: 父提交的完整哈希值%p
: 父提交的简写哈希值%an
: 作者名字%ae
: 作者的电子邮件地址%ad
: 作者修订日期(可以用 –date=选项 来定制格式)%ar
: 作者修订日期, 按多久以前的方式显示%cn
: 提交者的名字%ce
: 提交者的电子邮件地址%cd
: 提交日期%cr
: 提交日期(距今多长时间)%s
: 提交说明$d
: decorate 效果(显示当前 head, tag, branch)%C(<color>)
: 为跟在后面一个参数设置颜色或字体, e.g.git log --pretty=format:"%C(bold red)%h%Creset -%C(bold green)%d %C(bold yellow)%s %Creset- %C(red)%cd %Creset- %C(dim green)%an" --date=format:'%Y-%m-%d %H:%M:%S' --graph
- 一个颜色+一个内容
- 颜色以%C开头, 后边接几种颜色, 还可以设置字体, 如果要设置字体的话, 要一块加个括号
- 能设置的颜色值包括: reset(默认的灰色), normal, black, red, green, yellow, blue, magenta, cyan, white.
- 字体属性则有bold, dim, ul, blink, reverse.
- 内容可以是占位元字符, 也可以是直接显示的普通字符
--date=<option>
: 通过预置的选项设置 log 中 commits 的日期格式relative
: 显示距当前时间多少, e.g. “2 hours ago”local
: 显示本地时间, e.g “Wed Feb 26 18 : 03 : 14 2020”default
: 显示当前时区及时间, e.g. “Wed Feb 26 18 : 03 : 14 2020 +0800”iso
: 以 ISO 8601 格式显示时间rfc
: 以 RFC 2822 格式显示时间short
: 只显示日期, e.g. “2020-02-26”raw
: 显示 git 的原始格式, e.g. “1582711394 +0800”
--date=format:'<option>'
: 通过占位符设置log 中 commits 的日期格式 e.g.--date=format:'%Y-%m-%d %H:%M:%S'
%a
: Abbreviated weekday name%A
: Full weekday name%b
: Abbreviated month name%B
: Full month name%c
: Date and time representation appropriate for locale%d
: Day of month as decimal number (01 – 31)%H
: Hour in 24-hour format (00 – 23)%I
: Hour in 12-hour format (01 – 12)%j
: Day of year as decimal number (001 – 366)%m
: Month as decimal number (01 – 12)%M
: Minute as decimal number (00 – 59)%p
: Current locale’s A.M./P.M. indicator for 12-hour clock%S
: Second as decimal number (00 – 59)%U
: Week of year as decimal number, with Sunday as first day of week (00 – 53)%w
: Weekday as decimal number (0 – 6; Sunday is 0)%W
: Week of year as decimal number, with Monday as first day of week (00 – 53)%x
: Date representation for current locale%X
: Time representation for current locale%y
: Year without century, as decimal number (00 – 99)%Y
: Year with century, as decimal number%z, %Z
: Either the time-zone name or time zone abbreviation, depending on registry settings; no characters if time zone is unknown%%
: Percent sign
配置完自己喜爱的配色后, 使用可以为 git 命令定义别名方便下次使用, e.g.
git config --global alias.lg "log --pretty=format:'%C(bold red)%h%Creset -%C(bold green)%d %C(bold yellow)%s %Creset- %C(red)%cd %Creset- %C(dim green)%an' --date=format:'%Y-%m-%d %H:%M:%S' --abbrev-commit --graph"
git log --oneline --graph --all
: 以图表, 简洁形式显示 commit 信息git shortlog -sn
: 列出提交者贡献数量, 只会打印作者和贡献数量git shortlog -n
: 以提交贡献数量排序并打印出 messagegit shortlog -e
: 采用邮箱格式化的方式进行查看贡献度
git bisect
: 黑魔法, 查看哪一个 commit 产生的 buggit symbolic-ref refs/remotes/origin/HEAD
: 获得远程默认分支, master 或 maingit archive [option] [branch/commit] [from path]
: 导出代码(可支持下载远程仓库指定文件夹, 不支持 GitHub)--format tar.gz
: 导出的格式, 使用git archive --list
可以查看当前 git 所支持的所有格式; 如不指明, 则使用 –output 的文件名推断文件格式--output "./output.tar.gz"
: 将存档写入<file>
而不是 stdout.--remote git@192.168.1.203:iOS/ZRCombineViewer.git
: 指定远程仓库位置, 如不指定则导出当前目录下仓库的代码--exec=<git-upload-archive>
: 与–remote一起用于指定git-upload-archive远程端的路径.--verbose
: 实时显示最新进展--prefix=<prefix>/
: 在所有文件命前加入前缀1
2
3
4# 写入压缩文件
git archive --remote git@192.168.1.203:iOS/ZRCombineViewer.git -o test.zip master fastlane/actions fastlane
# 下载后解压到指定文件夹
git archive --remote git@gitlab.com:HanleyLee/helloworld.git --format=tar --prefix=junk/ master | (mkdir ~/Desktop/t1 && cd ~/Desktop/t1/ && tar xf -)
Git 使用流程
- 在
GitHub
或者其他仓库管理平台创建一个仓库. 并复制仓库地址 - 在终端中使用
git clone
将远程仓库下载到本地 - 在开发时将自己仓库创建出一个分支用于自己开发新功能.
- 新功能开发完毕后将 branch 上传到中央仓库让同事进行检查, 如果有问题的话继续修改直至没有问题. 如果没有问题的话就
checkout
到本地仓库的master
pull
以使本地master
与远端master
保持同步merge
自己的branch
到本地master
,push master
到远端
- 实际上为了保证项目的安全, 中大型项目的远端仓库
master
都是被禁止直接push
的, 因此步骤 4 的流程就会变成:- 让同事检查是否有问题, 没有问题就提交通过远端的
branch
提交pull request
(pull request
是对于master
分支来说的, 希望master
能够pull
本branch
) - 成功
pull request
后删除本branch
- 让同事检查是否有问题, 没有问题就提交通过远端的
Git 实践
使用裸仓库在多台电脑上同步配置文件
创建裸仓库并配置
1
2
3
4git init --bare $HOME/.hlconfig.git
echo 'alias hlconfig="/usr/bin/git --git-dir=$HOME/.hlconfig.git/ --work-tree=$HOME"' >> $HOME/.zshrc
source ~/.zshrc
hlconfig config --local status.showUntrackedFiles no将配置文件添加到此裸仓库中
1
2
3
4
5hlconfig status
hlconfig add .vimrc
hlconfig commit -m "Add vimrc"
hlconfig remote add origin https://www.github.com/username/repo.git
hlconfig push origin master在另一台机器上 clone 此裸仓库并覆盖配置
1
2
3
4
5
6echo 'alias hlconfig="/usr/bin/git --git-dir=$HOME/.hlconfig.git/ --work-tree=$HOME"' >> $HOME/.zshrc
source ~/.zshrc
echo ".hlconfig.git" >> $HOME/.gitignore_global
git clone --bare https://www.github.com/username/repo.git $HOME/.hlconfig.git
hlconfig config --local status.showUntrackedFiles no
hlconfig checkouthlconfig checkout 的时候, 如果另一台机器上已经有了相关配置文件的话会提示失败, 需要移动这些文件到另一处, 或者使用
hlconfig checkout -f
强制覆写这些文件
在 hlconfig add
了需要同步的配置文件后, 如果配置文件被修改了, 我们可以通过:
hlconfig add -u
来添加更新的内容到git
中hlconfig commit -a
直接将修改的内容进行commit
切记不可以使用
hlconfig add .
或hlconfig add -A
, 因为这两个命令是等价的, 会将本仓库路径控制下的所有新文件, 修改文件, 删除文件添加到版本管理中, 由于我们的裸仓库的管理目录是$HOME
, 因此会把此目录下的所有文件添加到版本管理中, 这并不是我们想要的结果.(当然我们可以为此裸仓库配置.gitignore
文件以规避此种问题, 但是与本裸仓库的使用理念又不同了)
为什么要使用裸仓库而不是普通仓库来管理
本例中创建的仓库是.hlconfig.git
, 是一个裸仓库, 因为是裸仓库, 因此
- 没有工作区, 其内没有
.git
文件夹 - 文件夹内的内容直接是
.git
文件夹的所有内容. - 在指定了
git-dir
与work-tree
后能管理此电脑上的所有文件 - 仓库可以放在电脑上的任意位置
如果使用普通仓库来管理:
- 必须在
$HOME
中直接建立仓库, 目的是将所有$HOME
作为工作区
裸仓库与普通仓库
git init repo
初始化一个正常仓库, git init repo.git --bare
初始化一个裸仓库.
裸仓库不包含工作区, 不能直接在裸仓库目录下使用 git status
, git add
等常用的命令. 因为不包含工作区, 因此只会记录 git
的提交历史, 当前 head
, branch
等基本信息, 这样特别适合与在服务端创建用来接收 push
的公共仓库.
归根到底, 最大的区别就是 裸仓库直接将 .git
文件夹中的内容取出放到了裸仓库的根目录下
普通仓库(test1)与裸仓库(test2)的目录结构对比:
1 |
|
正常仓库与裸仓库的 config
文件对比
正常仓库
1
2
3
4
5
6
7[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true裸仓库
1
2
3
4
5
6[core]
repositoryformatversion = 0
filemode = true
bare = true
ignorecase = true
precomposeunicode = true可以看到最直观的差异在于
bare
配置项是否为true
, 此外不加裸仓库配置中有一项logallrefupdates = true
, 作用根据名字就可以看出来, 记录所有的ref
(引用) 更新.
此外, 如果我们想在本地使用裸仓库作为管理 $HOME
的仓库, 可以通过指定 git
目录, 指定工作路径的方式来实现 git status
命令:
1 | git --git-dir=/Users/hanley/.hlconfig.git/ --work-tree=/Users/hanley status |
committer
和 author
的区别
author
是做出修改的人, committer
是最后提交到 git
中央仓库的人
恢复一个彻底删掉的 commit
1 | git reflog |
规范化提交 commit
可以在 .gitconfig
中配置全局 commit 模板:
1 | [commit] |
然后在 ~/.stCommitMsg
中声明
1 | type: |
type: 本次提交的类型
fix
: 修复bug
add
: 增加新功能(大)
feature
: 增加新功能(小)
update
: 更新
style
: 代码格式改变
opt
: 优化
test
: 增加测试代码
revert
: 撤销上一次的commit
build
: 构建工具或构建过程等的变动, 如:gulp
换成了webpack
,webpack
升级等
adapt
: 代码适配, 适配其他组件或工具description: 是对本次提交的简短描述.
不超过 50 个字符.
推荐以动词开头, 如:
设置
,修改
,增加
,删减
,撤销
等
[Fix #42] Fix table view cell text overflow: GitHub 上通过 commit 信息来关闭相关 issue, 或在末尾加上(#42)达到相同效果
使用.gitignore
文件
git 是根据.gitignore
文件来判断是否监视一个文件(夹)的, 如果文件在.gitignore
中被列出, 那么即使该文件被添加, git 也不会提示对其进行跟踪, 如果一个文件夹下被加入到.gitignore
文件, 那么其自身及其内文件(夹)都不会被跟踪.
如果在添加.gitignore
文件之前已经不想同步的内容已经被 git 跟踪了, 那么需要将其移除出跟踪区, 使用git rm --cached <filename>
.
使用git clean -fX
可以将被忽略的文件全部删除(一般不用)
不同步指定文件
.gitignore
文件可以让 git
忽略某些文件, 或者在忽略全部文件的情况下不忽略某些文件
1 | # 在没有`.gitignore`的情况下创建一个新文件 |
只同步指定文件
1 | touch .gitignore |
如果需要只同步子文件夹下的某个文件, 有两种方法
- 先设置同步子目录
- 然后设置不同步子目录所有内容
- 再设置同步子目录指定文件
1 | /* |
全局忽略文件
git config --global core.excludesfile .gitignore_global
在本地用户根目录下创建
.gitignore_global
文件, 在其中设置需要全局忽略的文件类型(下面的忽略按需使用)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32"#"是.gitignore_global中的注释行
# Compiled source
*.pyc
*.com
*.class
*.dll
*.exe
*.o
*.so
# Packages
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs and databases
*.log
*.sql
*.sqlite
# OS generated files
.DS_Store*
ehthumbs.db
Icon?
Thumbs.db
submodule 使用
git submodule
: 显示所有 submodule, 等同于git submodule status
添加 submodule 到现有项目
- Run
git submodule add -b <branch> --name <name> <repository-path-or-url>
- Add the
.gitmodule
file and submodule folder to the superproject index - Commit both files on the superproject
- Run
从当前项目移除submodule
git submodule deinit -f <submodule_path>
rm -rf .git/modules/<submodule_path>
git rm -f <submodule_path>
复制含 submodule 项目到本地
Clone the superproject as usual
Run
git submodule init
to init the submodulesRun
git submodule update
to have the submodules on a detached HEAD或者执行
git clone --recurse-submodules ssh://user@domain.tld/repo.git
git diff --submodule
: 查看 submodule 所有改变git submodule update --remote
: 更新 submodule 到他们对应的分支上git submodule update --remote <submodule-name>
: 更新指定的 submodule 到他们对应的分支上git push --recurse-submodules=check
: 在所有 submodule 推送更新完后在推送本项目到远程git push --recurse-submodules=on-demand
: 先推送 submodule 的更新, 然后推送主项目的更新git submodule foreach '<arbitrary-command-to-run>'
:
解决合并的冲突
1 | <<<<<<<到=======是在当前分支合并之前的文件内容 |
- 合并是产生了冲突
- 使用
git mergetool
工具对冲突文件逐个修改, 或者使用gui
工具进行修改 git add
经过修改后的文件git commit
&&git push
使用交互式 rebase 修改 / 删除之前某次提交的 commit
git rebase -i 目标 commit
1
2git rebase -i HEAD^^
// 在 git 中有两个偏移符号 ^ 与 ~, ^ 表示根据 head, ~表示根据 branch 向左偏移. 这个命令表示将当前 commit rebase 到 HEAD 之前 2 个的 commit 上.在编辑界面中指定需要操作的
commits
将
pick
修改为edit
(含义是使用这个commit
, 但是停下来等待修正) 『使用pick
代表选取, 如果直接删除这一行就代表跳过这个commit
, 那就是把这个commit
删除了』退出编辑界面
根据提示修改最后使用
amend
进行修正提交操作完成之后用
git rebase --continue
来继续rebase
过程各个commit
回复到原位
git rebase –onto 撤销历史 commit
如上图所示, git rebase commit3
会将 4
与 5
自动链接到目标 commit 3
之后, 因为 rebase
的起点是 Git
自动指定的, 起点判定为当前 branch
与要 rebase
到的 branch
的交点, 在此例中就是 2
. 因此 2
之后的 commit
都会被 rebase
到 3 之后.
1 | git rebase --onto commit3 commit4 branch1 |
通过这一特性可以选择性地删除 commit
1 | git rebase --onto HEAD^^ HEAD^ branch1 |
合并同一 branch 多个 commit
目标: 将 123
与 2
合并
使用
git log --oneline
查看当前branch
的commit
记录使用交互式变基,
git rebase -i dc8d
, 修改2
与123
的pick
为squash
(也可以用fixup
, 代表丢弃子commit
名称)保存退出并并根据提示为新
commit
赋予名称
git diff 信息理解
- 被比较的文件:
diff
命令会对两个文件进行比较, 一个文件被设定为a
, 另一个被设定为b
- 元数据: 刚开始的
2a3483c
与53ed7d1
代表两个文件的hashes
. 后面的100644
代表这是一个普通文件, 如果是10755
则代表是一个可执行文件,120000
代表是符号链接. - a/b 文件标识: 使用
-
作为a
的标志, 使用+
代表b
的标志 - 区块头:
-
代表来自文件a/Test/stash.txt
,1
代表从第一行开始,2
代表从第一行开始的2
行代码. 因此整句连起来:a
文件(旧文件)从第一行开始的2
行内有数据被改动,b
文件(新文件)从第一行开始的3
行内有数据改动 - 改动:
+
代表是新文件的改动. 如果是-
则是旧文件的改动, 而旧文件的改动就是删除内容, 新文件的改动就是增加内容
git 插件
gitup
git 仓库批量拉取更新
安装
1 | brew install gitup |
常用命令
gitup --help
: 查看 gitup 帮助信息gitup .
: 更新当前路径下的所有仓库--depth <num>
: 指定递归深度, 默认为 3
gitup ~/repos/foo ~/repos/bar ~/repos/baz
: 指定多个仓库进行批量拉取更新gitup -a ~/repos/foo ~/repos/bar ~/repos/baz
:--add
, 将多个仓库添加到 gitup 的书签中, 便于使用gitup
命令直接一键更新gitup -l
:--list
, 列出gitup
目前所有add
的仓库gitup -b ~/.config/gitup/bookmarks
:--bookmark-file
, 自定义指定书签文件gitup -d ~/repo
:--delete
, 删除已经add
的仓库gitup -n
:--clean
,--cleanup
, 删除路径已经变更的仓库的书签gitup
: 拉取更新所有被add
到书签的仓库gitup -c
:gitup --current-only
, 默认情况下 gitup 将会拉取远端所有分支, 使用此命令可以只更新当前分支gitup -f
:gitup --fetch-only
, 默认情况下会pull
, 此命令会只fetch
gitup -p
:gitup --prune
: 默认情况下会在本地保留远端已经删除的branch
, 此命令会保持远端与本地端branch
完全一致
diff-so-fancy
git diff 高亮优化显示
安装
1 | brew install diff-so-fancy |
配置
1 | Configure git to use d-s-f for *all* diff operations |
GitHub 连接方式
目前 GitHub
有两种连接方式, HTTPS
连接与 ssh
连接, 在 clone
时要选择对应的链接.
特点
- 本账户向本账户下仓库提交代码可以直接使用
https
或者将自己电脑生成的ssh key
加入到GitHub
账户. - 本账户使用
HTTPS
方式向另一账户下的仓库提交代码需成为对方账户的collaborator
. - 本账户使用
ssh
方式向另一账户的仓库提交代码需要将本账户所在电脑生成的ssh key
加入到对方账户. ssh
方式连接在每次push
时无需再每次访问时输入密码- 如果
GitHub
账户使用了二重验证还希望使用HTTPs
那么必须使用access token
作为密码进行登录.
SSH 连接方式
- 在终端使用
ssh-keygen
方式生成 ssh key - 将
pub
公钥加入到 GitHub 账户中 - 在仓库页面选择
ssh
链接进行复制 - 在本地文件夹进行
clone
查看 / 更改连接方式
通过 git remote -v
查看当前与远程的连接方式
修改为 HTTPs: git remote set-url origin [https://github.com/xmanrui/autoftp.git](https://github.com/xmanrui/autoftp.git)
修改为 ssh: git remote set-url origin [git@github.com](mailto:git@github.com):xmanrui/timerecord.git