文章目录
- 导语
- 1.Git 简介
- 2.Git 客户端安装
- 3.常用命令
- 3.1 设置和配置(Setup and Config)
- 3.2 获取或创建项目(Getting and Creating Projects)
- 3.3 基本快照(Basic Snapshoting)
- 3.4 分支与合并(Branching and Merging)
- 3.5 分享与更新项目(Sharing and Updating Projects)
- 3.6 修补(Patching)
- 3.7 调试(Debugging)
- 3.8 管理(Administration)
- 3.9 检查与比较(Inspection and Comparison)
- 3.10 基本快照(Basic Snapshotting)
- 3.11 管理(Administration)
- 4.别名设置
- 参考文献
导语
大咖好呀,我是恋喵大鲤鱼。
这里记录本人使用 Git 的点滴,以需要完成的功能为切入点来讲解需要使用的命令,供大家参考。当然,再结合官方文档 Git Reference 及 Git 常用命令大全这类较全面的 Git 命令介绍的文章,能帮助我们更好地掌握 Git 的使用。
1.Git 简介
Git 是一款由 Linux 之父 Linus Torvalds 于 2005 年开发的免费、开源的分布式源码管理(SCM,Source Code Management)工具,也称为版本控制系统(VCS,Version Control System),最初的目的是为了管理 Linux 内核的开发。
相比于其它的 SCM 工具,比如 Subversion(SVN)、Mercurial、CVS、Perforce、ClearCase 和 TFVC 等,Git 因其高效的性能、便捷的分支管理、免费开源等优秀特性,可敏捷高效地管理任何或小或大的项目。自诞生以来,迅速成为最流行的分布式版本控制系统,没有之一。
2.Git 客户端安装
访问 Git 官方网站下载最新版本的 Git for Windows 安装程序。
运行安装程序,通常会弹出一个命令行界面,引导你通过安装过程。
选择安装目录,选择组件(例如使用图形用户界面的选项),以及设置你的环境变量。
完成安装后,打开命令提示符或PowerShell,输入 git --version 来验证安装是否成功。
另外常见的 Git 客户端还有 GitHub Desktop 和 TortoiseGit ,这里不再赘述。感兴趣的同学可以试用一下。
3.常用命令
3.1 设置和配置(Setup and Config)
git config
3.2 获取或创建项目(Getting and Creating Projects)
git clone
git clone 命令用于将远程仓库克隆到本地。这一个操作类似于 SVN 的 check out,只有将远程仓库克隆到本地,才可以通过对本地的代码进行增删改后再提交至远程服务端。
(1)克隆远端仓库到本地。
# 克隆远端仓库
git clone <repository path>
# 克隆远端仓库,同时克隆仓库依赖的子模块(子模块是独立的仓库)
git clone --recursive <repository path>
如果仓库托管在 Github,那么可以在 Github 页面查看。点击 “Clone or download” 按钮,如下图所示:
3.3 基本快照(Basic Snapshoting)
git add
git add 命令用于将工作区的变更添加到暂存区。
(1)批量提交。
在进行修改、删除和新增操作后,需要提交多个文件或文件夹到暂存区,此时不需要一个一个进行git add
,这样做的话效率太低,使用git add
命令批量提交修改、删除和新增的文件或文件夹。
git add -A, --all, --no-ignore-removal
添加所有更新的内容,包括修改、新增的和删除的文件
git add .
添加新增和删除的文件,只对当前目录及其子目录有效。2.x 版本开始,可以添加修改的文件
git add -u,--update
添加修改和删除的文件,不包括新增文件
(2)查各命令选项的具体含义。
git add -h
(3)其它不常用选项。
git add -n, --dry-run [FILE]
不实际添加文件,仅仅是展示他们是否存在或者将被忽略
git add -v, --verbose
冗余模式
git add -i, --interactive
交互式
git add -f, --force
允许添加其他被忽略的文件
git add --ignore-errors
跳过因错误不能被添加的文件
git commit
git commit 命令用于将工作区或暂存区的变更提交至仓库。
每次使用 git commit 都会在本地版本库通过 SHA1 生成一个40 位的哈希值,这个哈希值也叫 commit-id。commit-id 在版本回退的时候是非常有用的,它相当于一个快照,可以在未来的任何时候通过 git reset 命令回退到指定版本。
(1)提交暂存区的变更到仓库并备注。
git commit -m <comment>
(2)将工作区被修改和被删除的文件,以及暂存区的变更提交至版本库并备注。必须要备注,不然无法提交。
git commit (-a | --all) -m <comment>
# 等同于
git add -u
git commit -m <comment>
注意,新加的文件(即没有被 Git 系统管理的文件)是不能被提交到本地仓库的。建议一般不要使用 -a | --all 参数,正常的提交过程还是先使用 git add 将被改动的文件添加到暂存区,再用 git commit 提交到本地版本库。
(3)修改最近一次提交的消息。
git commit --amend
上面的命令会打开编辑器以修改最近一次提交的消息。
修改完成后会生成一个新的提交对象,从而导致新的 commit id。这是因为 Git 将新的提交消息创建为一个新的提交对象,而不是修改原始的提交对象。
需要注意的是,使用 git commit --amend
命令修改提交消息时,只能修改最近的一次提交。如果需要修改更早的提交消息,可以使用 git rebase -i
命令来进行交互式重写历史。
(4)使用新的提交记录( commitid 与 comment)覆盖最近一次提交记录
git commit --amend -m <comment>
注意,使用 git push 推送至远端时,需要使用 -f 选项强制推送,不然会提示本地落后于远端,需要先使用 git pull 更新。强制推送时,千万注意不要覆盖了别人的提交。一般在自己的开发分支使用 git push -f 不会有什么问题。
使用 --amend 参数,一般出于两种考虑:
- 最近一次提交有 bug 需要修复,但又不想保留最近一次的提交记录。
- 减少提交记录的数量,保持提交记录的干净整洁。
(5) 查看帮助。
commit 还有许多参数有其他效果,一般来说了解上述操作足以应对日常开发。
git commit --help
git status
git status命令用于显示工作区和暂存区的状态。
(1)查看工作区与暂存区中文件的变更情况。
git status
git rm
git rm 命令用于从工作区或暂存区删除文件或目录。注意,git rm无法删除未受版本控制的文件(untracked file)。
git rm [<options>] [--] <file>...
-n, --dry-run 用于查看删除之前会删除哪些东西,并不会有实际的影响
-q, --quiet 不列出被删除的文件
--cached 仅从暂存区删除文件,可用于文件脱离版本控制
-f, --force 覆盖最新的检查,强制删除
-r 递归删除,可用于删除目录
--ignore-unmatch 未匹配到文件不报错
git mv
git mv 命令用于移动或重命名文件或目录。
git mv [<options>] <source>... <destination>
-v, --verbose 冗长模式执行,输出过程信息
-n, --dry-run 只查看影响的文件或目录,实际不做重命名处理
-f, --force 强制移动或重命名,即使目标文件或目录已经存在
-k 跳过移动或重命名错误
3.4 分支与合并(Branching and Merging)
git branch
git branch 用于管理分支,包括查看、创建、删除、重命名和关联。
(1)查看分支。
# 查看本地分支
git branch
# 查看远端分支
git branch -r
# 查看所有分支
git branch -a
# 查看本地分支 commit id 与 commit comment
git branch -v|--verbose
# 查看本地分支commit id与commit comment,以及关联的上游分支
git branch -vv
(2)创建分支。
# 基于当前分支创建本地分支不切换
git branch <branchname>
# 基于当前分支创建本地分支并切换
git checkout -b <branchname>
# 或
git switch -c <branchName>
# 将创建的本地分支推送到远端,远端分支不存在则创建。
git push origin <local_branchname>:<remote_branchname>
(3)更新分支。
# 使用远端分支更新本地分支
git pull [远程仓库名] [远程分支名]:[本地分支名]
# 使用关联的远端分支更新当前本地分支
git pull
(5)合并分支。
# 合并某分支到当前分支
git merge <srcbranch>
(6)删除分支。
# 删除本地分支
git branch (-d | --delete) <branchname>
# 强制删除本地分支
git branch (-D | -df | --delete --force) <branchname>
# 删除远端分支
git push origin :<remote_branchname>
# 或
git push origin (-d | --delete) <branchname>
# 注意,该命令无法删除远端分支,只是删除 git branch -r 列表中的远端追踪分支
git branch -dr origin/<branchname>
(7)本地分支关联远端分支。
# 第一种情况,远端分支已经存在。不指定 local_branchname 为当前分支。
git branch (--set-upstream-to=origin/<branchname> | -u origin/<branchname>) [<local_branchname>]
# 第二种情况,远端分支不存在
# 1.将当前本地分支推送至远端并关联(远端分支与本地分支同名)
git push (-u | --set-upstream) origin HEAD
# 2.将当前本地分支推送至远端并关联(指定远端分支名)
git push (-u | --set-upstream) origin <remote_branchname>
# 3.将本地分支推送至远端并关联(指定本地与远端分支名)
git push (-u | --set-upstream) origin <local_branchname>:<remote_branchname>
(8)删除本地分支与远端分支的关联。
git branch --unset-upstream [<local_branch>]
(9)重命名本地分支。
# 重命名当前分支
git branch (-m | --move) <newbranch>
# 重命名指定分支
git branch (-m | --move ) <oldbranch> <newbranch>
# 强制重命名本地分支
git branch (-M -f | --move --force) [<oldbranch>] <newbranch>
(10)重命名远端分支。
Git 没有直接修改远端分支名的命令,我们可以通过删除重建的方式来间接重命名远端分支。
# 先删除远端分支
git push origin -d <branch_name>
# 再重命名当前本地分支
git branch (-m | --move) <newbranch>
# 将当前本地分支推送至远端并关联
git push origin (-u | --set-upstream) HEAD
git checkout
git checkout 主要用于分支切换与工作区文件的恢复。
(1)撤销工作区文件的修改。
git checkout -- <file name...>
(2)切换分支。
git checkout <branchName>
(3)切换分支,如果目标分支不存在则新建。
git checkout -b <branchName>
(4)将远端分支拉取到本地。
git checkout -b <branchName> origin/<branchName>
(5)切换当前分支到某个提交,即移动 HEAD 指针指向某个提交。
git checkout <commit>
git switch
(1)简介
git switch 从 git 2.23 版本开始引入,是一个比较新的命令,主要用于分支的创建和切换,实现了 git checkout 命令分支创建与切换的功能。git switch 命令的语义更加贴合其作用,更容易理解,建议使用。
(2)格式
git switch [<options>] [<branch>]
(3)选项
-c <branch>
--create <branch>
创建一个新分支然后切换过去
-C <branch>
--force-create <branch>
强制创建一个新分支然后切换过去
-t
--track
将远端分支拉取到本地
(4)示例
git switch dablelv
切换到指定分支
git switch -c dablelv_new
基于当前分支创建一个新分支然后切换过去
git switch -
切回到之前的分支
git switch -t origin/dablelv
将远端分支 dablelv 拉取到本地
git stash
当一个分支的开发工作未完成,却要切换到另外一个分支进行开发的时候,除了commit 原分支的代码改动的方法外,可以使用 git stash 来保存当前分支的工作进度。
命令格式:
git stash list [<options>]
git stash show [<options>] [<stash>]
git stash drop [-q|--quiet] [<stash>]
git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]
git stash branch <branchname> [<stash>]
git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [-m|--message <message>]
[--] [<pathspec>…]]
git stash clear
git stash create [<message>]
git stash store [-m|--message <message>] [-q|--quiet] <commit>
常用示例:
git stash [push]
保存当前工作进度,会把暂存区和工作区的改动保存起来。执行完这个命令后,在运行git status命令,就会发现当前是一个干净的工作区,没有任何改动。使用 git stash save 'message...' 可以添加一些注释。注意,git stash 是 git stash push 的简写
git stash list
显示保存进度的列表。也就意味着,git stash命令可以多次执行。
git stash pop
恢复最新的进度到工作区。git默认会把工作区和暂存区的改动都恢复到工作区。
git stash pop --index
不仅恢复工作区,还恢复暂存区,即恢复最新的进度到工作区和暂存区。
git stash pop stash@{stash_id}
恢复指定的进度到工作区。stash_id是通过git stash list命令得到的。
注意:通过git stash pop命令恢复进度后,会删除当前进度。
git stash apply --index
不仅恢复工作区,还恢复暂存区,即恢复最新的进度到工作区和暂存区。不删除当前进度。
git stash apply stash@{stash_id}
恢复指定的进度到工作区,不删除当前进度。stash_id是通过git stash list命令得到的。
注意:git stash apply除了不删除恢复的进度之外,其余和git stash pop命令一样。
git stash drop [stash_id]
删除一个存储的进度。如果不指定stash_id,则默认删除最新的存储进度。
git stash clear
删除所有存储的进度。
git tag
同大多数 VCS 一样,Git 也可以对某一时间点的版本打上标签,用于版本的发布管理。
一个版本发布时,我们可以为当前版本打上类似于 v.1.0.1、v.1.0.2 这样的 Tag。一个 Tag 指向一个 Commit ID,同时还可以为 Tag 添加备注,如当前的版本功能。
(1)新建标签。
Git 使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。
# 轻量级标签
git tag <tagname>
# 含附注的标签
git tag -a <tagname> -m <comment>
# 示例
git tag v1.0
git tag -a v1.1 -m "my version 1.1"
(2)将本地标签推送到远端。
# 推送本地指定标签
git push origin <tagname>
# 推送本地所有标签
git push origin --tags
(3)查看标签。
# 查看所有本地标签
git tag
v1.1.0
v1.1.1
# 按照创建时间降序排列
git tag --sort=-creatordate
# 按照创建时间升序排列
git tag --sort=creatordate
# 查看所有远端标签
git ls-remote --tags
# 模糊查询标签。支持使用通配符 *
git tag -l <tagname pattern>
git tag -l "*.1.1"
v1.1.1
# 查看标签详细信息
git show <tagname>
执行 git tag 命令进入交互模式列出所有标签后,可以按下 q 键退出交互。
(4)删除标签。
# 删除本地标签
git tag -d <tagname>
# 删除远端标签
git push origin (-d | --delete) <tagname>
git push origin :refs/tags/<tagname>
(5)重命名标签。
# 无重命名标签选项,只能先删除然后新建
git tag -d <tagname> && git tag <tagname>
git merge
git merge 用于分支合并。比如当开发分支上的代码达到上线的标准后,此时需要使用 git merge 将开发分支合并到 master 分支。
常见用法:
# 将原分支合并到当前分支。原分支的历史提交记录会被保留
git merge <srcbranch>
# 将原分支合并到当前分支。原分支的历史提交记录不会被保留,然后使用 git commit 创建一个新提交,这样会使提交历史干净整洁,推荐使用
git merge --squash <srcbranch>
git merge 与 git merge --squash 的区别:
一图看懂二者的区别。
判断是否使用 --squash 选项最根本的标准是,待合并分支上的历史记录是否有意义。如果没有意义,建议使用 --squash 选项将其废弃。
3.5 分享与更新项目(Sharing and Updating Projects)
git pull
git pull 用于取回远程仓库某个分支的更新与本地指定分支合并。
实际上 git pull = git fetch + git merge。其基本用法格式如下:
git pull [<options>] [<repository> [<refspec>…]]
refspec 为分支名。
常用示例:
(1)例如将远程仓库 origin 的 master 分支拉取过来,与本地的 branchtest 分支合并。
git pull origin master:branchtest
(2)后面的冒号与本地的分支名可以省略,表示与本地当前分支合并。
git pull origin master
(3)远程仓库名和远程分支名均可省略,表示使用与本地当前分支关联的远端分支更新本地分支。
git pull
(4)git push 出现error: failed to push some refs to '仓库地址'
的错误,原因是远程仓库中代码版本与本地不一致冲突导致的,解决办法是先git pull
,再git push
。
git pull 与 git pull --rebase 的区别?
git pull --rebase = git fetch + git rebase。使用 --reabase 选项可以使项目提交历史变成直线,没有分叉,非常整洁。git rebase是变基操作,使得本地分支的修改变成在远端最新版本基础上进行,于是少了一步将远端分支的最新版本merge到本地分支的记录,即 git pull --rebase 可以消除Merge branch 'master' of <repository path>
这种commit 记录。建议使用 -r(–rebase)选项。
git pull --rebase 的过程可以使用下图表示:
git push
git push 命令用于将本地分支的更新推送到远端分支,命令格式与 git pull 相似。
git push [<options>] [<repository> [<refspec>…]]
如果命令行没有使用<repository>
参数指定推送的仓库,则会采用branch.*.remote
配置。如果缺少配置,则默认为 origin。
(1)使用将当前本地分支更新关联的远端分支。
git push
(2)将本地分支推送至远端并关联。
# 将当前分支推送至远端并关联(远端分支与本地分支同名)
git push (-u | --set-upstream) origin HEAD
# 将当前分支推送至远端并关联(指定远端分支名)
git push (-u | --set-upstream) origin HEAD:<remote_branchname>
# 将本地分支推送至远端并关联(指定本地与远端分支名)
git push (-u | --set-upstream) origin <local_branchname>:<remote_branchname>
(3)使用本地分支更新远端分支。
git push <仓库名> <本地分支名>:<远端分支名>
(4)删除远端分支。
# 方式一:推送一个空分支到远端分支
git push origin :<remote_branchname>
# 方式二:使用 git push -d
git push origin (-d | --delete) <branchname>
(5)本地与远端分支版本回退,需要使用 -f, --force 选项。
git reset --hard <commitid>
git push (-f | --force)
git remote
git remote 用于管理一组被跟踪的远程代码仓库。
选项说明:
-h
显示帮助信息
-v, --verbose
查看远端仓库名称与地址。仓库地址在名称的后面,仓库名默认为 origin
子命令说明:
git remote
查看远端仓库名称,默认为 origin
git remote rename <old> <new>
修改远程仓库名称,默认为 origin
git remote remove <name>
删除与远程仓库的关联
git remote add <name> <url>
添加新的远程仓库关联
git remote set-head <name> (-a | --auto | -d | --delete | <branch>)
设置或删除远程仓库的默认分支。默认分支一般为 master
git remote [-v | --verbose] show [-n] <name>
显示远程仓库相关分支信息
git remote prune [-n | --dry-run] <name>
清理与远程仓库关联的过时分支。如果使用 --dry-run,则只显示过时分支,不进行清理。使用该命令后,被删除的远程分支将不会出现在 git branch -r 命令结果中
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
更新远程分支列表
git remote set-branches [--add] <name> <branch>...
为远程仓库添加分支列表
git remote get-url [--push] [--all] <name>
查看远程仓库地址。如果使用 --push,则查询 push 地址,而非 fetch 地址
git remote set-url [--push] <name> <newurl> [<oldurl>]
改变远程仓库地址
git remote set-url --add <name> <newurl>
为远程仓库添加新地址
git remote set-url --delete <name> <url>
删除远程仓库所有正则匹配 <url> 的地址
git submodule
git submodule 用于管理子模块。
有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。 现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。
Git 通过子模块来解决这个问题。 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。
命令选项:
-q, --quiet
只打印错误信息
-b, --branch <branch>
要添加为子模块的仓库分支。分支的名称记录在 .gitmodules 文件中的 submodule.<name>.branch,用于 update --remote。特殊值 . 用于指示子模块的分支名称应与当前仓库的当前分支名称相同。如果未指定该选项,则默认为远程 HEAD。
<path>…
子模块的路径。当指定时,这将限制命令只操作在指定路径上找到的子模块。(此参数对于add是必填的)。
常见操作:
(1)查看子模块。
git submodule
(2)添加子模块。
git submodule add <repository-url> <path>
该命令会将一个远程仓库添加为子模块,并将其克隆到指定的目录。同时会在主仓库中生成一个 .gitmodules 文件来记录子模块的信息。
(3)克隆包含子模块的仓库。
对于你的主仓库合作者来说,如果只是 git clone 去下载主仓库的内容,那么你会发现子模块的文件夹是空的!
此时我们可以初始化子模块,如果不指定子模块路径,则初始化所有子模块。
git submodule init [<path>...]
初始化完子模块后,然后更新子模块,则会根据子模块的配置信息,拉取子模块的代码为主项目里指定的 commit id。
git submodule update [<options>] [--] [<path>...]
如果第一次使用 git clone 下载主仓库,建议使用如下命令把主仓库和子模块一次性下载下来:
git clone --recursive <project-url>
(4)拉取子模块的远端更新。
如果子模块在远端发生了版本变更,有多种方法可以获取子模块最新版本。
方法一:cd submodule 后 git pull。
在 submodule 中,所有 Git 操作就当作一个普通的 Git 仓库就行,你可以切换分支、提交代码、拉取更新等。
这个方法,你可以拉取到 submodule 的最新代码。但是如果这时候的 commit id 跟主项目里记录的 submodule 的 commit id 不一致,你会在主项目仓库看到 diff,你可能需要提交主项目更新。
方法二:使用如下命令,这相当于在每个子模块中执行 git pull。
git submodule update --remote [<path>...]
如果你不带参数子模块路径,就会更新所有子模块。
当我们更新子项目后,相当于把主项目记录的 submodule 的 commit id 给更新了,需要提交下主项目的变更。
(5)推送子模块的变更到远端。
如果在本地修改了子模块,需要推送子模块到远端仓库。此时在主项目中使用 git status 能够看到关于子模块尚未暂存以备提交的变更,但是于主项目而言是无能为力的,使用 git add/commit 对其不会产生影响。
在此情景下,通常需要进入子模块文件夹,按照子模块内部的版本控制提交代码。
注意,当在子模块做 commit 后,此时主项目中所记录的子模块的 commit id 也会更新,需要提交下主项目的变更。
(6)删除子模块。
git submodule deinit [-f|--force] (--all|[--] <path>…)
如果加上参数 -f|–force,则子模块工作区内即使有本地的修改,也会被移除。
执行完上面的命令,会自动在 .git/config 中删除子模块的配置信息。然后我们还需要使用 git rm 删除子模块目录。
git rm <path>...
小结:
虽然 Git 提供的子模块功能已足够方便好用,但仍请在为主仓库项目添加子模块之前确保这是非常必要的。毕竟有很多编程语言提供了类似的依赖包管理工具,如 Go Modules、Node.js npm 和 Ruby’s rubygemsor 等,可以更好地完成类似的功能。
主项目的合作者并不会自动地看到子模块仓库的更新通知,所以,更新子模块后一定要记得提醒一下主项目的合作者执行 git submodule update --remote
。
3.6 修补(Patching)
git rebase
(1)简介
rebase 是变基操作,作用与 merge 类似,用于将另一个分支合并到当前分支。
rebase 是将一系列提交按照原有次序依次应用到另一分支上,使得提交历史变成一条直线,而 merge 是把最终结果合在一起,提交历史可能会出现分叉,并多出一个 merge 的提交记录。
(2) rebase 与 merge 的区别
比如基于 master 分支创建了一个新的分支 experiment,开发任务分叉到两个不同分支,又各自提交了更新,那么提交历史日志如下:
现将 experiment 分支合并到 master 分支有两种方式,一是 git rebase,二是 git merge。
(a)merge 方式
切换到 master 分支,执行合并操作,会将 experiment 分支的最新快照 C3 合并到 master 分支的最新快照 C2 中并生成一个新的快照(并提交)。提交历史将出现分叉,示意如下:
操作命令如下:
git checkout master
git merge experiment
(b)rebase 方式
git rebase 实现原理是首先找到两个分支的最近共同祖先 C1,提取 experiment 分支相对于该祖先的历次提交存为临时的差异补丁 patch 文件,存在 .git/rebase 目录下;然后在 master 分支最新提交 C2 的基础上,将保存的 patch 文件中的提交依次应用到 master 分支,并生成新的提交(commit id)。
最后回到 master 分支,进行一次快进合并,即将 experiment 分支合并到 master 分支。
操作命令如下:
# 第一步,变基
git checkout experiment
git rebase master
# 或者
git rebase master experiment
# 第二步,合并
git checkout master
git merge experiment
(3)rebase 的风险
变基帮助我们拥有直线提交历史,也并非完美无缺,为避免出错,使用时需要遵守一条准则:不要对已推送至远程仓库的提交进行变基操作。
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至远程仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,相当于删除之前的提交。你的同伴再次 git pull 时,会将存放在本地的你已经删除的提交再次合并,如果你的同伴将合并后的提交推送到服务器上,实际上是将那些已经被你变基抛弃的提交又恢复了回来,这会令人感到混乱。
(4)变基 vs 合并
变基和合并都可以完成分支合并,到底哪种方式更好?在回答这个问题之前,让我们退后一步,讨论一下提交历史的意义。
有一种观点认为,仓库的提交历史记录实际发生过什么。 它是针对历史的文档,本身就有价值,不能乱改。 从这个角度看来,改变提交历史是一种亵渎,你使用谎言掩盖了实际发生过的事情。 如果由合并产生的提交历史是一团糟怎么办? 既然事实就是如此,那么这些痕迹就应该被保留下来,让后人能够查阅。
另一种观点则正好相反,他们认为提交历史是 项目过程中发生的事。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 持这一观点的人会使用 rebase 及 filter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。
现在,让我们回到之前的问题上来,到底合并还是变基好?这并没有一个正确的答案。 Git 是一个非常强大的工具,它允许你对提交历史做许多事情,但每个团队、每个项目对此的需求并不相同,按实际需要来选择即可。
个人建议,git merge 足以完成分支合并,易于理解,基于惰性与简明原则,没有必要使用变基。
(5)常用命令选项
git rebase 的基本命令格式如下:
git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
git rebase --continue | --abort | --skip | --edit-todo
(a)git rebase --onto
截取特性分支上的另一个特性分支,然后变基到其他分支。比如,假设当前本地仓库提交历史如下:
A---B---E---F---G master
\
C---D---H---I---J next
\
K---L---M topic
此时topic分支的上游分支是next分支,如果要将topic分支上的提交(K,M,L)跳过next分支,直接放到master分支上,就需要加上–onto参数:
git rebase --onto master next topic
上述命令的意思是:取出topic分支,找出topic和next分支的共同祖先之后的提交,然后放在master分支上,执行后提交历史变为如下:
A---B---E---F---G master
\ \
\ K'---L'---M' topic
\
C---D---H---I---J next
(b)git rebase –continue/abort/skip
这三个命令分别表示继续执行变基操作、终止变基、跳过某一文件继续进行。因为在rebase的过程中,有可能会出现文件冲突。这种情况下,首先要解决冲突,解决冲突后可以选择继续执行rebase或者结束rebase,一般的做法为:
git add filename
git rebase --continue
或者可以选择终止变基:
git rebase --abort
或者跳过该patch。
git rebase --skip
(c)git rebase -i, --interactive
该命令相比其他命令,使用频率要高得多。git rebase -i <commitid>
可以进行交互式变基,相比于 git rebase <branch>
用来变基,它经常用来操作当前分支的提交,git 会将 <commitid>-HEAD
之间的提交列在一个变基脚本中,每一笔提交根据用户设置的命令,会进行不同的操作,如修改提交信息、移除指定提交、合并两个提交为一个(压缩提交)、拆分提交等。
如要对最近 4 次提交进行重新操作,则:
git rebase -i HEAD~4
此时将会弹出如下形式的变基脚本:
1 pick af98479 [Description]:test4
2 pick 3cc9d66 test3
3 pick a7e819e usera commit03 branch2
4 pick efc5b15 usera commit04 branch2
5
6 # Rebase de7b118..efc5b15 onto de7b118 (4 command(s))
7 #
8 # Commands:
9 # p, pick = use commit
10 # r, reword = use commit, but edit the commit message
11 # e, edit = use commit, but stop for amending
12 # s, squash = use commit, but meld into previous commit
13 # f, fixup = like "squash", but discard this commit's log message
14 # x, exec = run command (the rest of the line) using shell
15 # d, drop = remove commit
16 #
17 # These lines can be re-ordered; they are executed from top to bottom.
18 #
19 # If you remove a line here THAT COMMIT WILL BE LOST.
20 #
21 # However, if you remove everything, the rebase will be aborted.
22 #
23 # Note that empty commits are commented out
这里我们可以修改pick为下面给出的其他命令,比如如果要修改提交信息,就使用r或reword,各指令的含义如下:
p,pick:直接使用该次提交
r,reword:使用该次提交,但重新编辑提交信息
e,edit:停止到该次提交,通过`git commit --amend`追加提交,完毕之后不要忘记使用`git rebase --continue`完成这此rebase
s,squash:压缩提交,将和上一次提交合并为一个提交
x,exec:运行命令
d,drop:移除这次提交
git cherry-pick
git cherry-pick 用于应用某些现有提交引入的更改。
比如 master 分支有如下提交记录,A -> B -> C,程序在运行过程中,提交 B 引入的特性存在一个隐藏很深的 bug,现在需要将 B 从分支踢出,但是需要保留提交 C。此时需要将分支回退(reset) 到 A,然后使用 cherry-pick 将 C 的更改应用到 A。这里要注意,cherry-pick 时 C 一定要存在,不然会出错,这就要求我们在回滚 master 分支前,基于 master 先创建一个新的分支来保留提交 C。
命令格式:
git cherry-pick <commit>…
3.7 调试(Debugging)
git blame
git blame 命令用于查看文件的每一行是谁修改的。
git blame [file]
git blame [-L n,m] [file] //指定文件的行范围为n,m
git version
git version 用于查看 git 版本。
git version
3.8 管理(Administration)
git clean
git clean 命令用于删除工作目录所有 untracked 的文件或目录。基本用法如下:
git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...
-q, --quiet 不打印被删除的文件或目录名称
-n, --dry-run 查看删除之前会删除哪些东西,并不会有实际的影响
-f, --force 强制删除
-i, --interactive 交互式删除
-d 删除目录
-e, --exclude <pattern> 添加<pattern>到忽略规则中,用于忽略符合规则的文件或目录
-x 同时可删除被忽略的文件或目录
-X 只删除被忽略的文件
注意,未指定目录<paths>
默认为当前目录。一般情况下,git reset --hard 会和 git clean -df 一起使用,让你的工作目录完全回退到最近一次 commit 的时候。
3.9 检查与比较(Inspection and Comparison)
git log
git log 用于看历史提交日志,最近的排在最上方,显示提交对象的哈希值、作者、提交日期和提交说明。如果记录过多,则按 Page Up、Page Down、↓、↑ 键来控制显示,按 q 退出历史记录列表。
命令格式:
git log [<options>] [<revision range>] [[--] <path>…]
常用选项:
-- <path>…
显示指定文件的提交日志
-, -n, --max-count=<number>
显示最近 number 次的提交日志
--since, --after=<date>
显示指定日期之后的提交日志
--until, --before=<date>
显示指定日期之前的提交日志
--author, --committer=<pattern>
显示指定提交者提交的日志
--merges
只展示 merge 信息
--no-merges
不展示 merge 信息
--abbrev-commit
精简 commit id,只展示 40 个十六进制数字构成的 commit id 的首部
--graph
以文本字符绘制的“图形”展示
--pretty[=<format>]
如果没有参数,则缺省为 oneline
--format=<format>
以指定格式展示,<format> 可取值 oneline, short, medium, full, fuller, email, raw, format:<string> 和 tformat:<string>,其中<string>为格式控制字符串。缺省值为 medium。常用的是 oneline
--oneline
等价于 --pretty=oneline --abbrev-commit
--grep=<pattern>
根据提交消息的内容来过滤和查找提交历史。
(4)常用示例
(a)展示提交历史。
git log
(b)单行展示,显示简短 commitid(%h)、提交日期(%cd)、提交者的名字(%cn)和提交说明(%s)。
git log --pretty=format:"%h %cd %cn : %s"
955599561 Tue Mar 10 16:21:51 2020 +0800 tom : 性能优化
692028af8 Tue Mar 10 10:26:08 2020 +0800 bill : 修改 bug
11a8e2021 Tue Mar 10 10:26:08 2020 +0800 jerry : 首次提交
(c)以图形方式展示提交历史。
git log --graph
(d)以图形方式展示提交历史,提交信息以单行展示。
git log --graph --oneline
* 2fa056a8c Merge branch 'master' of http://git.code.oa.com/nfa/goserver_proj
|\
| * d336234bc (tag: Tag_20190312_V003) set gopath
| * b8c3bbd55 (tag: Tag_20190312_V002) fix command.go proto path
| * 2f6a13fc2 (tag: Tag_20190312_V001) fix proto path
| * 7a147abe2 fix proto path
| * 99e295279 (tag: Tag_20190311_B1000) Merge branch 'kbAbOp_eddie' into 'master'
| |\
| | * 809801eb9 add cityhash
| | * b588a5d40 接口改造
| * | f1c0ca554 fix
| |/
| * 92e4249a9 update conf&log
| * bd8cf2755 Merge branch 'master' of http://git.code.oa.com/nfa/goserver_proj
| |\
| * | 886fd1ed3 change log
* | | 0d911b64d 提交新结构
| |/
|/|
* | 8c7eca23a ad
|/
* 769b68c61 KbADProxyServer first commit
* 8cea1f0b4 信息流商业化的所有go服务代码放此项目,方便公用代码
(e)显示从指定的"最后一个标签"(last tag)到当前 HEAD(最新提交)之间的提交日志。
git log <last tag> HEAD --pretty=format:%s
git diff
git show
(1)简介
git show 用于显示各种类型的对象,包括 blobs、trees、tags 和 commits。
对于 commits,它显示日志消息和文本差异。
对于 tags,它显示标签消息和引用对象。
对于 trees,它显示 tree 的名称。
对于简单的 blobs,它显示了普通的内容。
(2)命令格式
git show [<options>] [<object>…]
(3)常用选项
<object>…
待展示的对象,默认为 HEAD
--pretty[=<format>], --format=<format>
以指定格式展示,<format> 可取值 oneline, short, medium, full, fuller, email, raw, format:<string> 和 tformat:<string>,
其中<string>为格式控制字符串。缺省值为 medium。常用的是 oneline
--abbrev-commit
精简 commit id,只展示 40 个十六进制数字构成的 commit id 的首部
--oneline
等价于 --pretty=oneline --abbrev-commit
--name-only
只显示发生变更的文件名
(4)常用示例
(a)查看某个 tag 指向的版本信息。
git show v1.0.0
(b)显示某个 tag 指向的版本的目录树。
git show v1.0.0^{tree}
.gitignore
.orange-ci.yml
PRJ_ROOT
README.md
bin/
pkg/
src/
(c)显示某次提交的详细信息,包括文件差异。
git show <commitid>
(d)只显示某次提交发生变化的文件名。
git show --name-only <commitid>
3.10 基本快照(Basic Snapshotting)
git reset
- 简介
git reset 将当前 HEAD 重置为指定状态,可用于版本回退。
可将分支重设(reset)到指定的<commit>
,如果不显示指定 commit,默认是 HEAD,即最近一次提交。
比如我们 commit 后,可能发现这次 commit 的内容有错误,那么有两种处理方法:
(1)修改错误内容,再次 commit。
(2)使用 git reset 撤销这一次错误的 commit。
第一种方法比较直接,但会多一次 commit 记录,建议使用 git reset 进行版本回退,方便快捷,错误的 commit 记录不会被保留下来。
- 命令格式
git reset [-q] [<tree-ish>] [--] <paths>…
git reset (--patch | -p) [<tree-ish>] [--] [<paths>…]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
- 选项说明
-q, --quiet
静默模式,只打印错误信息
--soft
重置 HEAD,保留暂存区和工作区。版本库的修改会回退到暂存区,工作区的修改保持不动
--mixed
重置 HEAD 和暂存区,保留工作区。版本库与暂存区的修改都将回退到工作区,即回滚到了所有 git add 和 git commit 的执行之前的状态。为默认模式
--hard
重置 HEAD、暂存区和工作区。暂存区和工作区的修改都将被丢弃。请谨慎使用,暂存区的修改很难找回,工作区的修改无法找回。
--merge
重置 HEAD 和暂存区,保留工作区。与 --mixed 不同的是,暂存区的修改不会回退到工作。如果工作区的某个文件与暂存区不同,则命令执行失败。该选项很少使用
--keep
重置 HEAD 与暂存区,保留工作区。与 --mixed 不同的是,暂存区的修改不会回退到工作。与 --merge 的区别是,如果工作区的某个文件与暂存区不同,则命令不会执行失败。该选项很少使用
-p, --patch
以 patch 的方式展示出来需要 reset 的代码, git reset -p 和 git add -p 就是一对互为反向的操作,后者是把工作目录下变更的代码以 patch 的方式展示出来,以互动的方式应用到 index 上,前者则是一个反向操作
-N, --intent-to-add
任何新加入到 HEAD 的文件,再回退到工作区后都将标记为 tracked,即受版本控制
(4)常用示例
- 放弃当前版本的所有修改。
git reset --hard
git reset --hard HEAD
- 分支版本跳转。
git reset --hard [commit]
commit 可取值 HEAD 当前版本,上一个版本 HEAD^(或 HEAD~1),上上一个版本就是 HEAD^^(或 HEAD~2),以此类推。缺省为 HEAD
- 将暂存区的修改回退到工作区。
git reset <file>...
git reset HEAD <file>...
git reset --mixed <file>...
git reset --mixed HEAD <file>...
git revert
(1)简介
git revert 用一个新的提交来消除历史提交所做的修改,历史 commit 记录都会保留,并且这次撤销
操作会产生一次新的提交。
与 git reset 的区别主要有:
(a)实现的方式不用。git revert 使用一次新的提交来回退到指定版本,不会改变历史的提交历史。git reset 移动 HEAD 指针指向历史某次提交,历史提交记录将被改变。因此,git revert 一般用在公共分支上,git reset 一般用在私有分支上。
(b)使用的场景不同。git revert 一般只用于版本回退,撤销已经提交的更改,并且要求暂存区与工作区是干净的。git reset 一般用于撤销未提交的修改,比如使用git reset --hard
放弃暂存区与工作区的修改。
(2)格式
git revert [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<keyid>]] <commit>…
git revert (--continue | --skip | --abort | --quit)
(3)选项
-e
--edit
版本回退在提交之前编辑提交消息。如果从终端运行命令,则这是默认设置。
--no-edit
git revert 将不会启动提交消息编辑器
(4)示例
(a)回退到上一版本。
git revert HEAD^
git restore
(1)简介
restore 是由 git 2.23 版本引入,是一个比较新的命令,用于恢复暂存区或者工作区中的文件。
恢复工作区中的文件,也可以使用git checkout -- <file>
。可见,restore 实现了 checkout 的文件恢复功能,restore 更加符合恢复语义,建议使用。
(2)格式
git restore [<options>] [--source=<branch>] [--] [<pathspec>…]
--
表示后面的参数均不是选项。
(3)选项
-W, --worktree
恢复工作区指定文件,为缺省选项
-S, --staged
将暂存区指定文件回退到工作区
-s, --source=<tree>
指明文件所在的分支,缺省为当前分支
<pathspec>…
限制受操作影响的路径。可以是文件,也可以是目录。
(4)示例
- 将暂存区的文件回退到工作区。
git restore -S <file>...
- 恢复工作区中的文件。
git restore <file>...
- 将工作区和暂存区中的所有文件还原到最新的提交状态。
git restore --source=HEAD --staged --worktree .
3.11 管理(Administration)
git reflog
(1)简介
git reflog 用于管理存储在引用日志 reflog 中的记录的信息。用于查看分支版本所有的变更记录,包括因版本回退出现在 HEAD 之后的变更记录。git log 只能查看 HEAD 之前的版本记录,不能查看 HEAD 之后的版本记录。
reflog 可以很好地帮助我们恢复误操作的数据,比如我们错误地 reset 到了一个旧的提交,这个时候我们可以使用 reflog 去查看在误操作之前的信息,并且使用 git reset 恢复到之前的状态。
(2)格式
该命令采用各种子命令,并根据子命令使用不同的选项:
git reflog [show] [log-options] [<ref>]
git reflog expire [--expire=<time>] [--expire-unreachable=<time>]
[--rewrite] [--updateref] [--stale-fix]
[--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>…]
git reflog delete [--rewrite] [--updateref]
[--dry-run | -n] [--verbose] ref@{specifier}…
git reflog exists <ref>
show 子命令是缺省命令,用于显示命令行中提供的引用的日志,引用默认为 HEAD。
expire 子命令用于修剪旧的 reflog 条目。超过 expire 时间的条目,或者早于 expire-unreachable 时间且当前提示无法访问的条目将从 reflog 中删除。一般情况下不会使用该命令。
delete 子命令从 reflog 中删除单个条目。其参数必须是精确条目,例如git reflog delete master@{2}
。一般情况下不会使用该命令。
exists 子命令检查 ref 是否具有 reflog。如果 reflog 存在则退出为零状态,如果不存在则退出为非零状态。
(3)选项
--all
处理所有引用的 reflog
--single-worktree
仅处理当前工作树的 reflog
-n, --dry-run
不要删除任何条目,只展示会被修剪的东西
--verbose
打印额外信息
(4)示例
(a)查看当前分支所有的变更记录。
git reflog
4.别名设置
为了简化输入,提高效率,推荐使用如下 Git 命令的简短别名。将下面的内容粘帖至文件~/.bashrc
,然后执行命令. ~/.bashrc
或source ~/.bashrc
。
alias gcl='git clone'
alias gs='git status'
alias ga='git add'
alias gcm='git commit -m'
alias gcam='git commit -am'
alias gb='git branch -vv'
alias gc='git checkout'
alias gph='git push'
alias gpl='git pull'
参考文献
git reference
Git 中文参考
10分钟学会Git教程 - 安装Git、建仓库、添加和推送文件至库
Git常用命令大全
Git push 报错 "error: failed to push some refs to " 解决
git命令行删除远程分支
git stash命令总结
代码回滚:git reset、git checkout和git revert区别和联系
git reset soft,hard,mixed之区别深解
1.6 起步 - 初次运行 Git 前的配置
简单对比git pull和git pull --rebase的使用
git clean.git reference
Pro Git Online.C3.6 变基
Git整理(四) git rebase 的使用
这一次彻底搞懂 Git Rebase
git rebase:永远不要衍合那些已经推送到公共仓库的更新