前面说了删除提交的方法,但是如果是多人合作的话,如果某个提交已经Push到远程仓库,是不可以用那种方法删除提交的,这时就要撤销提交

发布时间:2026/7/3 21:42:10
前面说了删除提交的方法,但是如果是多人合作的话,如果某个提交已经Push到远程仓库,是不可以用那种方法删除提交的,这时就要撤销提交 git revert commit-id这条命令会把指定的提交的所有修改回滚并同时生成一个新的提交。Resetgit reset会修改HEAD到指定的状态用法为git reset [options] commit这条命令会使HEAD提向指定的Commit一般会用到3个参数这3个参数会影响到工作区与暂存区中的修改--soft: 只改变HEAD的State不更改工作区与暂存区的内容--mixed(默认): 撤销暂存区的修改暂存区的修改会转移到工作区--hard: 撤销工作区与暂存区的修改cherry-pick当与别人和作开发时会向别人贡献代码或者接收别人贡献的代码有时候可能不想完全Merge别人贡献的代码只想要其中的某一个提交这时就可以使用cherry-pick了。就一个命令git cherry-pick commit-idfilter-branch这条命令可以修改整个历史如从所有历史中删除某个文件相关的信息全局性地更换电子邮件地址。五、GIT分支分支被称之为GIT最强大的特性因为它非常地轻量级如果用Perforce等工具应该知道创建分支就是克隆原目录的一个完整副本对于大型工程来说太费时费力了而对于GIT来说可以在瞬间生成一个新的分支无论工程的规模有多大因为GIT的分支其实就是一指针而已。在了解GIT分支之前应该先了解GIT是如何存储数据的。前面说过GIT存储的不是文件各个版本的差异而是文件的每一个版本存储一个快照对象然后通过SHA-1索引不只是文件包换每个提交都是一个对象并通过SHA-1索引。无论是文本文件二进制文件还是提交都是GIT对象。GIT对象每个对象(object) 包括三个部分类型大小和内容。大小就是指内容的大小内容取决于对象的类型有四种类型的对象blob、tree、 commit 和tag。“blob”用来存储文件数据通常是一个文件。“tree”有点像一个目录它管理一些“tree”或是“blob”就像文件和子目录一个“commit”指向一个tree它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据如提交时间、提交说明、作者、提交者、指向上次提交commits的指针等等。一个“tag”是来标记某一个提交(commit) 的方法。比如说我们执行了以下代码进行了一次提交$ git add README test.rb LICENSE2$ git commit -m initial commit of my project现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。概念上来说,仓库中的各个对象保存的数据和相互关系看起来如下图如果进行多次提交仓库的历史会像这样分支引用所谓的GIT分支其实就是一个指向某一个Commit对象的指针像下面这样有两个分支master与testing而我们怎么知道当前在哪一个分支呢其实就是很简单地使用了一个名叫HEAD的指针如上图所示。HEAD指针的值可以为一个SHA-1值或是一个引用看以下例子git的所有版本信息都保存了Working Directory下的.git目录而HEAD指针就保存在.git目录下如上图所有目前为止已经有3个提交通过查看HEAD的值可以看到我们当前在master分支refs/heads/master当我们通过git checkout取出某一特定提交后HEAD的值就是成了我们checkout的提交的SHA-1值。记录我们当前的位置很简单就是能过HEAD指针HEAD指向某一提交的SHA-1值或是某一分支的引用。新建分支git branch branch-name有时需要在新建分支后直接切换到新建的分支可以直接用checkout的-b选项git checkout -b branch-name删除分支git branch -d branch-name如果在指定的分支有一些unmerged的提交删除分支会失败这里可以使用-D参数强制删除分支。git branch -D branch-name检出分支或提交检出某一分支或某一提交是同一个命令git checkout branch-name | commit分支合并(merge)当我们新建一个分支进行开发并提交了几次更新后感觉是时候将这个分支的内容合回主线了这是就可以取出主线分支然后把分支的更新merge回来git checkout mastergit merge testing如果master分支是testing分支的直接上游即从master延着testing分支的提交历史往前走可以直接走到testing分支的最新提交那么系统什么也不需要做只需要改变master分支的指针即可这被称之为Fast Forward。但是一般情况是这样的你取出了最新的master分支比如说master分支最新的提交是C2假设共3次提交C0-C1-C2)在此基础上你新建了分支当你在分支上提交了C3、C5后想将br1时merge回master时你发现已经有其他人提交了C4这时候就不能直接修改master的指针了不然会丢失别人的提交这个时候就需要将你新建分支时master所在的提交C2后的修改C4与你新建分支后在分支上的修改C3、C5)做合并将合并后的结果作为一个新的提交提交到masterGIT可以自动推导出应该基于哪个提交进行合并C2如果没有冲突系统会自动提交新的提交如果有冲突系统会提示你解决冲突当冲突解决后你就可以将修改加入暂存区并提交。提交历史类似下面这样图来自Pro-Git)merge后的提交是按时间排序的比如下图我们在rename提交处新建分支test在test上提交Commit from branch test然后回到master提交commit in master after committing in branch再将test分支merge进master这时看提交提交历史Commit from branch test是在commit in master...之前的尽管在master上我们是在rename的基础上提交的commit in master...而GIT会在最后添加一个新的提交Merge branch test表示我们在此处将一个分支merge进来了。这种情况会有一个问题比如说在rename提交处某人A从你这里Copy了一个GIT仓库然后你release了一个patch通过git format-patch给A这时候test分支还没有merge进来所以patch中只包含提交commit in master...然后你把test分支merge了进来又给了A一个patch这个patch会包含提交Commit from branch test而这个patch是以rename为base的如果commit in master...和Commit from branch test修改了相同的文件则第二次的patch可能会打不上去因为以rename为base的patch可能在新的Code上找不到在哪个位置应用修改。分支衍合(rebase)有两种方法将一个分支的改动合并进另一个分支一个就是前面所说的分支合并另一个就是分支衍合这两种方式有什么区别呢分支合并merge是将两个分支的改动合并到一起并生成一个新的提交提交历史是按时间排序的即我们实际提交的顺序通过git log --graph或一些图形化工具可能很明显地看到分支的合并历史如果分支比较多就很混乱而且如果以功能点新建分支等功能点完成后合回主线由于merge后提交是按提交时间排序的提交历史就比较乱各个功能点的提交混杂在一起还可能遇到上面提到的patch问题。而分支衍合rebase是找到两个分支的共同祖先提交将要被rebase进来的分支的提交依次在要被rebase到的分支上重演一遍即回到两个分支的共同祖先将branch假如叫experiment的每次提交的差异保存到临时文件里然后切换到要衍合入的分支假如是master依次应用补丁文件。experiment上有几次提交在master就生成几次新的提交而且是连在一起的这样合进主线后每个功能点的提交就都在一起而且提交历史是线性的对比merge与rebase的提交历史会是下图这样的图来自Pro-GItmergerebaserebase后C3提交就不存在了取而代之的是C3而master也成为了experiment的直接上游只需一次Fast Forwardgit merge后master就指向了最新的提交就可以删除experiment分支了。衍合--ontogit rebase --onto master server client这条命令的意思是检出server分支与client分支共同祖先之后client上的变化然后在master上重演一遍。父提交HEAD表示当前所在的提交如果要查看当前提交父提交呢git log查看提交历史显然太麻烦了而且输入一长串的Commit-ID也不是一个令人愉悦的事。这时可借助两个特殊的符号~与^。^ 表示指定提交的父提交这个提交可能由多个交提交^之后跟上数字表示第几个父提交不跟数字等同于^1。~n相当于n个^比如~3^^^表示第一个父提交的第一个父提交的第一个父提交。远程分支远程分支以(远程仓库名)/(分支名)命令远程分支在本地无法移动修改当我们clone一个远程仓库时会自动在本地生成一个名叫original的远程仓库下载远程仓库的所有数据并新建一个指向它的分支original/master但这个分支我们是无法修改的所以需要在本地重新一个分支比如叫master并跟踪远程分支。Clone了远程仓库后我们还会在本地新建其他分支并且可能也想跟踪远程分支这时可以用以下命令git checkout -b [branch_name] --track|-t remote/remote-banch和新建分支的方法一样只是加了一个参数--track或其缩写形式-t可以指定本地分支的名字如果不指定就会被命名为remote-branch。要拉取某个远程仓库的数据可以用git fetch:git fetch remote当拉取到了远程仓库的数据后只是把数据保存到了一个远程分支中如original/master而这个分支的数据是无法修改的此时我们可以把这个远程分支的数据合并到我们当前分支git merge remote/remote-branch如果当前分支已经跟踪了远程分支那么上述两个部分就可以合并为一个git pull当在本地修改提交后我们可能需要把这些本地的提交推送到远程仓库这里就可以用git push命令由于本地可以由多个远程仓库所以需要指定远程仓库的名字并同时指定需要推的本地分支及需要推送到远程仓库的哪一个分支git push remote local-branch:remote-branch如果本地分支与远程分支同名命令可以更简单git push remote branch-name 等价于 git push remote refs/heads/branch-name:refs/for/branch-name如果本地分支的名字为空可以删除远程分支。前面说过可以有不止一个远程分支f添加远程分支的方法为git remote add short-name url六、标签-tag作为一个版本控制工具针对某一时间点的某一版本打tag的功能是必不可少的要查看tag也非常简单查看tag使用如下命令git tag参数-l可以对tag进行过滤git tag -l v1.1.*Git 使用的标签有两种类型轻量级的lightweight和含附注的annotated。轻量级标签就像是个不会变化的分支实际上它就是个指向特定提交对象的引用。而含附注标签实际上是存储在仓库中的一个独立对象它有自身的校验和信息包含着标签的名字电子邮件地址和日期以及标签说明标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。