前言
学习Git,从底层开始了解git的命令语句所执行的操作。看完本文,希望你能够了解Git,“实践是检验真理的唯一标准”,掌握Git,需要你在日常工作中,不断深化其命令以及底层逻辑。
版本控制系统
版本控制系统是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。版本控制系统不仅可以应用于软件源代码的文本文件,而且可以对任何类型的文件进行版本控制。
版本控制系统发展可以分为三个阶段:
- 本地版本控制系统
- 集中式版本控制系统
- 分布式版本控制系统
本地版本控制系统
RCS,现今许多计算机系统上都还看得到它的踪影。 RCS 的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。
集中版本控制系统
集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。 这类系统,诸如 CVS、Subversion 以及 Perforce 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
以上两者都存在的致命性问题:
只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。
分布式版本控制系统
分布式版本控制系统(Distributed Version Control System,简称 DVCS)
客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
Git使用
Repository
版本库又名仓库,一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
初始化仓库 —— git init
创建目录
pwd命令用于显示当前目录。
初始化仓库
通过git init命令把这个目录变成 Git 可以管理的仓库
多了一个.git的目录,这个目录是 Git 来跟踪管理版本库的,如果你没有看到 .git 目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看到了。
克隆现有的仓库 —— git clone
git clone
git clone 命令,Git 克隆的是该 Git 仓库服务器上的几乎所有数据,而不是仅仅复制完成你的工作所需要文件。
执行 git clone 命令的时候,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。
克隆仓库的命令是 git clone <url>
在当前目录下创建一个名为 你拉取项目的名字 的目录,并在这个目录下初始化一个 .git 文件夹, 从远程仓库拉取下所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝。 如果你进入到这个新建的 你拉取项目的名字 文件夹,你会发现所有的项目文件已经在里面了,准备就绪等待后续的开发和使用。
在
clone时出现Timeout情况时,可以设置Git代理(此时需开启魔法)git config --global http.proxy "127.0.0.1:7890"然后继续
clone
自定义本地仓库名称
通过额外的参数指定新的目录名git clone <url> 自定义仓库名
这会执行与上一条命令相同的操作,但目标目录名变为了 自定义仓库名。
Git 支持多种数据传输协议。 上面的例子使用的是 https:// 协议,不过你也可以使用 git:// 协议或者使用 SSH 传输协议,比如 user@server:path/to/repo.git 。
编辑并添加文件
在已经准备好的 Git 仓库中编辑一个readme.txt文件,内容如下:
bash Git is a version control system. Git is free software.
接下来,我们可以通过2个命令将刚创建好的readme.txt添加到Git仓库
-
用命令
git add告诉 Git,把文件添加到仓库:$ git add readme.txt执行上面的命令,没有任何显示,说明添加成功
提交变动到仓库
用命令git commit告诉 Git,把文件提交到仓库:
$ git commit -m "wrote a readme file" [master (root-commit) 50ed06b]
git commit命令,-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
git commit命令执行成功后会告诉你:
- 1 file changed:1个文件被改动(我们新添加的readme.txt文件)
- 2 insertions:插入了两行内容(readme.txt有两行内容)
为什么 Git 添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件,比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
查看Git仓库当前转态变化
成功地添加并提交了一个readme.txt文件,接下来让我们继续修改readme.txt文件,改成如下内容:
Git is a distributed version control system. Git is free software.
查看 git status 结果
git status命令可以让我们时刻掌握仓库当前的状态,上面的命令输出告诉我们,readme.txt被修改过了,但还没有准备提交的修改。
比较变动
虽然 Git 告诉我们readme.txt被修改了,但并没有告诉我们具体修改的内容是什么,假如刚好是上周修改的,等到周一来班时,已经记不清上次怎么修改的readme.txt,这个时候我们就需要用git diff这个命令查看相较于上一次暂存都修改了些什么内容了
运行git diff命令,如:git diff readme.txt
git diff顾名思义就是查看 difference,显示的格式正是 Unix 通用的 diff 格式,可以从上面的输出看到,我们在第一行添加了一个distributed单词。
综合操作
知道了对readme.txt作了什么修改后,再把它提交到仓库就放心多了,提交修改和提交新文件是一样的两步
git add,如:git add readme.txt- 在执行第二步之前,再次查看仓库转态
git status,git status告诉我们,将要被提交的修改包括readme.txt,下一步,就可以放心地提交了
- 在执行第二步之前,再次查看仓库转态
git commit,如:git commit -m "add distributed"- 提交后,我们再用
git status命令看看仓库的当前状态
- 提交后,我们再用
查看日志
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline参数:
git log --pretty=oneline
git lg命令,就可以更清楚地看到提交历史的时间线
Git回退
假设我们需要将 readme.txt 回退到上一个版本,也就是 wrote a readme file 的这个版本,
首先,Git 必须知道当前版本是哪个版本,在 Git 中,用HEAD表示当前版本,也就是最新的提交e55063a,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100
现在,我们要把当前版本add distributed回退到上一个版本wrote a readme file,就可以使用git reset命令:
$ git reset --hard HEAD^
现在让我们看看readme.txt的内容是不是版本wrote a readme file:
$ cat readme.txt
Git is a version control system. Git is free software.
果然还原到最初wrote a readme file这个版本了。
Git 的版本回退速度非常快,因为 Git 在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git 仅仅是把HEAD从指向add distributed
Git重置
git reflog
现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的commit id怎么办?
好在 Git 提供了一个命令git reflog用来记录你的每一次命令,当你用git reset --hard HEAD^回退到wrote a readme file版本时,再想恢复到add distributed,就可以通过git reflog命令找到add distributed的commit id。
git reflog
$ git reflog
从上面的输出可以看到,add distributed的commit id是e55063a,现在,我们就可以通过 git reset --hard e55063a切换到最新的版本上了。
工作区和暂存区
Git 和其他版本控制系统如 SVN 的一个不同之处就是有暂存区的概念
工作区(Working Directory)
就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工作区
版本库(Repository)
工作区有一个隐藏目录.git,这个不算工作区,而是 Git 的版本库。
Git 的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有 Git 为我们自动创建的第一个分支master,以及指向 master 的一个指针叫HEAD。
前面讲了我们把文件往 Git 版本库里添加的时候,是分两步执行的:
- 第一步是用
git add把文件添加进去,实际上就是把文件修改添加到暂存区; - 第二步是用
git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建 Git 版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往 master 分支上提交更改。
git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的
版本管理
Git 处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。 与许多其它版本控制系统不同,Git 鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。
撤销修改(git commit --amend)
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 --amend 选项的提交命令来重新提交:git commit --amend
$ git commit --amend
这个命令会将暂存区中的文件提交。 如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令), 那么快照会保持不变,而你所修改的只是提交信息。
文本编辑器启动后,可以看到之前的提交信息。 编辑后保存会覆盖原来的提交信息。
例如,你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
最终你只会有一个提交——第二次提交将代替第一次提交的结果。
当你在修补最后的提交时,并不是通过用改进后的提交 原位替换掉旧有提交的方式来修复的, 理解这一点非常重要。从效果上来说,就像是旧有的提交从未存在过一样,它并不会出现在仓库的历史中。
修补提交最明显的价值是可以稍微改进你最后的提交,而不会让“啊,忘了添加一个文件”或者 “小修补,修正笔误”这种提交信息弄乱你的仓库历史。
取消暂存的文件(git reset)
接下来我们看看如何操作暂存区和工作目录中已修改的文件。 这些命令在修改文件状态的同时,也会提示如何撤消操作。例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交, 但是却意外地输入 git add * 暂存了它们两个。如何只取消暂存两个中的一个呢? git status 命令提示了你:
$ git add *
$ git status
On branch master Changes to be committed: (use "git reset HEAD ..." to unstage)
renamed: LICENSE -> LICENSE.md
modified: readme.txt
在 “Changes to be committed” 文字正下方,提示使用 git reset HEAD <file>... 来取消暂存。 所以,我们可以这样来取消暂存 readme.txt 文件:git reset HEAD
$ git reset HEAD readme.txt Unstaged changes after reset: M readme.txt
$ git status
这个命令有点儿奇怪,但是起作用了。 readme.txt 文件已经是修改未暂存的状态了。
git reset确实是个危险的命令,如果加上了--hard选项则更是如此。 然而在上述场景中,工作目录中的文件尚未修改,因此相对安全一些。
撤消对文件的修改(git --checkout)
git checkout – file
$ git checkout -- readme.txt
$ git status
请务必记得
git checkout -- <file>是一个危险的命令。 你对那个文件在本地的任何修改都会消失——Git 会用最近提交的版本覆盖掉它。 除非你确实清楚不想要对那个文件的本地修改了,否则请不要使用这个命令。
删除文件
在 Git 中,删除也是一个修改操作,添加一个新文件test.txt到 Git 并且提交
$ git add test.txt
$ git commit -m "add test.txt"
一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删了:$ rm test.txt,这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了。
现在你有两个选择,
-
一是确实要从版本库中删除该文件,那就用命令
git rm删掉,并且git commit$ git rm test.txt rm 'test.txt'现在,文件就从版本库中被删除了。
小提示:先手动删除文件,然后使用git rm 和git add效果是一样的。
-
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本
$ git checkout -- test.txtgit checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
注意:从来没有被添加到版本库就被删除的文件,是无法恢复的!
-
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
git checkout -- file -
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令
git reset HEAD <file>,就回到了场景1,第二步按场景1操作 -
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,可以用命令
git reset --hard commit_id,不过前提是没有推送到远程库
分支管理
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的快照
在进行提交操作时,Git 会保存一个提交对象(commit object)。 知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。
首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象,
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 master 分支会在每次提交时自动向前移动。
创建、合并、删除分支
Git 会把仓库中的每次提交串成一条时间线,这条时间线就是一个分支。在 Git 里,每个仓库都会有一个主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git 用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点。
每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。
-
当我们创建新的分支,例如
dev时,Git 新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上。Git 创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化。不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变。 -
假如我们在
dev上的工作完成了,就可以把dev合并到master上。Git 怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并。所以 Git 合并分支也很快!就改改指针,工作区内容也不变 -
合并完分支后,甚至可以删除
dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支
例
- 创建
dev分支,切换到dev分支:
$ git checkout -b dev
# git checkout命令加上-b参数表示创建并切换,相当于以下两条命令
$ git branch dev
$ git checkout dev
- 查看当前分支(列出所有分支)
$ git branch
- 在
dev分支上正常提交,比如对readme.txt做个修改,加上一行:Creating a new branch is quick.,然后提交
$ git add readme.txt
$ git commit -m "branch test"
dev分支的工作完成,我们就可以切换回master分支
$ git checkout master
-
切换回
master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变 -
把
dev分支的工作成果合并到master分支上$ git merge devgit merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。注意到上面的
Fast-forward信息,Git 告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。 -
合并完成后,就可以放心地删除
dev分支了$ git branch -d dev -
删除后,查看
branch,就只剩下master分支了$ git branch
因为创建、合并和删除分支非常快,所以 Git 鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全
git switch
切换分支使用git checkout <branch>,而 Git 中撤销修改则是git checkout -- <file>,同一个命令,有两种作用,确实有点令人迷惑
切换分支这个动作,用switch更科学。因此,最新版本的 Git 提供了新的git switch命令来切换分支
-
创建并切换到新的
dev分支,可以使用$ git switch -c dev -
直接切换到已有的
master分支,可以使用$ git switch master
git switch命令,比git checkout要更容易理解。
分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward(--no-ff)模式,Git 就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息
例
-
创建并切换dev分支
$ git switch -c dev -
修改
readme.txt文件,并提交一个新的commit$ git add readme.txt $ git commit -m "add merge" -
切换回
master$ git switch master -
合并
dev分支,请注意--no-ff参数,表示禁用Fast forward$ git merge --no-ff -m "merge with no-ff" dev # 因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去 -
合并后,用
git log看看分支历史$ git log --graph --pretty=oneline --abbrev-commit * # 可以看到,不使用Fast forward模式
分支策略
按照基本原则进行分支管理
master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活- 干活都在
dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,并在master分支发布1.0版本
你和你的小伙伴们每个人都在dev分支上干活(即在dev分支上继续分支),每个人都有自己的分支,时不时地往dev分支上合并就可以了
master 和 dev 两个常用分支外,我们还会有其他类型的分支使用策略:
- bug 分支
- feature 分支
bug分支
在开发过程中,bug 就像家常便饭一样。有了 bug 就需要修复,在 Git 中,由于分支是如此的强大,所以,每个 bug 都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
例
当你接到一个修复一个代号101的 bug 的任务时,很自然地,你想创建一个分支 issue-101 来修复它,但是,等等,当前正在dev上进行的工作还没有提交。并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该 bug,怎么办?
git stash
Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作
$ git stash
现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复 bug。
-
确定要在哪个分支上修复 bug,假定需要在
master分支上修复,就从master创建临时分支$ git checkout master $ git checkout -b issue-101 -
现在修复bug,将你的bug进行修改后,然后提交
$ git add readme.txt $ git commit -m "fix bug 101" -
修复完成后,切换到
master分支,并完成合并,最后删除issue-101分支$ git switch master $ git merge --no-ff -m "merged bug fix 101" issue-101 -
回到
dev分支干活$ git switch dev $ git status # 工作区是干净的,刚才的工作现场存到哪去了 -
查看刚才的工作场
$ git stash list -
工作现场还在,Git 把
stash内容存在某个地方了,但是需要恢复一下,有两个办法-
用
git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除 -
用
git stash pop,恢复的同时把stash内容也删了$ git stash pop
-
-
再用
git stash list查看,就看不到任何stash内容了$ git stash list -
可以多次
stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令$ git stash apply stash@{0}
在master分支上修复了bug后,我们要想一想,dev分支是早期从master分支分出来的,所以,这个bug其实在当前dev分支上也存在。
那怎么在dev分支上修复同样的bug?重复操作一次,提交不就行了?在 Git 中还有比这更简单的方法可以实现。
同样的 bug,要在dev上修复,我们只需要把8842ff5 fix bug 101这个提交所做的修改“复制”到dev分支。注意:我们只想复制8842ff5 fix bug 101这个提交所做的修改,并不是把整个master分支merge过来。
git cherry-pick
为了方便操作,Git 专门提供了一个cherry-pick命令,让我们能复制一个特定的提交到当前分支
$ git branch
$ git cherry-pick 8842ff5
Git 自动给dev分支做了一次提交,注意这次提交的commit是0944c8c,它并不同于master的8842ff5,因为这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick,我们就不需要在dev分支上手动再把修 bug 的过程重复一遍。
既然可以在master分支上修复bug后,在dev分支上可以“重放”这个修复过程,那么直接在dev分支上修复 bug,然后在master分支上“重放”行不行?当然可以,不过你仍然需要git stash命令保存现场,才能从dev分支切换到master分支。
feature分支
新的功能不断添加进来
添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
例
接到了一个新任务:开发代号为Vulcan的新功能。
-
准备开发
$ git switch -c feature-vulcan -
开发完后
$ git add vulcan.md $ git status $ git commit -m "add feature vulcan" -
切回
dev,准备合并$ git switch dev # feature分支和bug分支是类似的,合并,然后删除 -
在回到分支后,开发的新功能需取消,进行销毁分支
$ git branch -d feature-vulcan # 会报错,是因为feature-vulcan分支没有被合并,如果删除,将丢失修改,如果强行删除,使用-D参数 $ git branch -D feature-vulcan
Git解决冲突
-
准备新的
feature1分支,继续新分支开发$ git switch -c feature1修改
readme.txt最后一行,改为:Creating a new branch is quick AND simple. -
在
feature1分支上提交$ git add readme.txt $ git commit -m "AND simple" -
切换到
master分支$ git switch master在master分支上把
readme.txt文件的最后一行改为:Creating a new branch is quick & simple. -
在
master分支上提交$ git add readme.txt $ git commit -m "& simple"
现在,master分支和feature1分支各自都分别有新的提交
这种情况下,Git 无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突
$ git merge feature1
Git发出警告,readme.txt文件存在冲突,必须手动解决冲突后再提交,git status可以列出冲突文件
$ git status
查看readme.txt的内容,Git 用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存Creating a new branch is quick and simple.
再次提交
$ git add readme.txt
$ git commit -m "conflict fixed"
用带参数的git log也可以看到分支的合并情况
$ git log --graph --pretty=oneline --abbrev-commit *
删除feature1分支:
$ git branch -d feature1
git cherry-pick
上面在进行bug分支时,曾用到了git cherry-pick。
对于多分支的代码库,将代码从一个分支转移到另一个分支是常见需求。
这时分两种情况。
- 需要另一个分支的所有代码变动,那么就采用合并
git merge。 - 需要部分代码变动(某几个提交),这时可以采用
Cherry pick。
用法
git cherry-pick命令的作用,就是将指定的提交commit应用于其他分支。
$ git cherry-pick <commitHash>
# 会将指定的提交`commitHash`,应用于当前分支。这会在当前分支产生一个新的提交,当然它们的哈希值会不一样
配置项
git cherry-pick命令的常用配置项如下。
-e,--edit: 打开外部编辑器,编辑提交信息。-n,--no-commit: 只更新工作区和暂存区,不产生新的提交。-x: 在提交信息的末尾追加一行cherry picked from commit ...,方便以后查到这个提交是如何产生的。-s,--signoff: 在提交信息的末尾追加一行操作者的签名,表示是谁进行了这个操作。-m parent-number,--mainline parent-number: 如果原始提交是一个合并节点,来自于两个分支的合并,那么Cherry pick默认将失败,因为它不知道应该采用哪个分支的代码变动。-m:配置项告诉 Git,应该采用哪个分支的变动。它的参数parent-number是一个从1开始的整数,代表原始提交的父分支编号。git cherry-pick -m 1 <commitHash>表示,Cherry pick采用提交commitHash来自编号1的父分支的变动。一般来说,1号父分支是接受变动的分支,2号父分支是作为变动来源的分支。
例
代码仓库有master和feature两个分支
Master: a - b - c -dFeature: e - f - ga,b,c,d,e,f,g是分支上提交的
-
将提交
f应用到master分支$ git checkout master $ git cherry-pick f -
代码库变为了
Master: a - b - c - d - fFeature: e - f - gmaster分支的末尾增加了一个提交f
git cherry-pick命令的参数,不一定是提交的哈希值,分支名也是可以的,表示转移该分支的最新提交.
一个提交
将feature分支的最近一次提交,转移到当前分支
$ git cherry-pick feature
多个提交
$ git cherry-pick <HashA> <HashB>
# 将 `A` 和 `B` 两个提交应用到当前分支。这会在当前分支生成两个对应的新提交
转移**一系列**的连续提交,可以使用下面的简便语法
$ git cherry-pick A..B
# 转移从 A 到 B 的所有提交。它们必须按照正确的顺序放置:提交 A 必须早于提交 B,否则命令将失败,但不会报错。
# 使用上面的命令,提交 A 将不会包含在 Cherry pick 中。如果要包含提交 A,可以使用下面的语法
$ git cherry-pick A^..B
代码冲突
如果操作过程中发生代码冲突,Cherry pick会停下来,让用户决定如何继续操作。
-
–continue
用户解决代码冲突后,第一步将修改的文件重新加入暂存区(git add .),第二步使用下面的命令,让Cherry pick过程继续执行。$ git cherry-pick --continue -
–abort
发生代码冲突后,放弃合并,回到操作前的样子 -
–quit
发生代码冲突后,退出Cherry pick,但是不回到操作前的样子。
转移到另一个代码库
Cherry pick 也支持转移另一个代码库的提交,方法是先将该库加为远程仓库。
-
添加了一个远程仓库
target。$ git remote add target git://gitUrl -
将远程代码抓取到本地。
$ git fetch target -
检查一下要从远程仓库转移的提交,获取它的哈希值。
$ git log target/master -
使用
git cherry-pick命令转移提交。$ git cherry-pick <commitHash>
多人协作
远程分支
当你从远程仓库克隆时,实际上 Git 自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
-
查看远程库的信息,用
git remote$ git remote origin# 用git remote -v显示更详细的信息 $ git remote -v origin # 显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
推送分支
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git 就会把该分支推送到远程库对应的远程分支上
$ git push origin master
# 要推送其他分支,比如dev,就改成
$ git push origin dev
并不是一定要把本地分支往远程推送,根据分支需求来决定是否需要推送到远程
master分支是主分支,因此要时刻与远程同步;dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;bug分支只用于在本地修复bug,就没必要推到远程了;feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
抓取分支
多人协作时,大家都会往master和dev分支上推送各自的修改。
例
-
当从远程库
clone时,默认情况下,只能看到本地的master分支。可以用git branch命令看看$ git branch -
要在
dev分支上开发,就必须创建远程origin的dev分支到本地,创建本地dev分支$ git checkout -b dev origin/dev -
在
dev上继续修改,然后,时不时地把dev分支push到远程$ git add env.txt $ git commit -m "add env" $ git push origin dev To <仓库地址> -
上面是一个人向,
origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送$ git add env.txt $ git commit -m "Update env.txt" $ git push origin dev推送失败,因为这个人的最新提交和你试图推送的提交有冲突
-
先用
git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送$ git pullgit pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接$ git branch --set-upstream-to=origin/dev dev再
pull:$ git pullgit pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push$ git commit -m "fix env conflict" $ git push origin dev
工作模式
多人协作的工作模式:
- 试图用
git push origin <branch-name>推送自己的修改; - 如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull试图合并; - 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用
git push origin <branch-name>推送就能成功!
如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
git rebase
多人在同一个分支上协作时,很容易出现冲突。即使没有冲突,后push的童鞋不得不先pull,在本地合并,然后才能push成功。
每次合并再push后,查看分支情况
$ git log --graph --pretty=oneline --abbrev-commit
会发现,分支看上去很乱。
针对上述问题,Git有一种称为**rebase**的操作,有人把它翻译成“变基”。
-
假设,在和远程分支同步后,我们对
hello.py这个文件做了两次提交。用git log命令看看$ git log --graph --pretty=oneline --abbrev-commit *注意到Git用(HEAD -> master)和(origin/master)标识出当前分支的HEAD和远程origin的位置分别是
582d922 add author和d1be385 init hello,本地分支比远程分支快两个提交。 -
尝试推送本地分支
$ git push origin master To <仓库地址>推送失败,说明有人先于你推送了远程分支,按照经验,先
pull:$ git pull再用git status看看状态
$ git status加上刚才合并的提交,现在我们本地分支比远程分支超前3个提交
用
git log看看$ git log --graph --pretty=oneline --abbrev-commit *提交历史分叉,现在把本地分支
pull到远程,会显的分支很乱 -
git rebase$ git rebase用
git log看看$ git log --graph --pretty=oneline --abbrev-commit *push把本地分支推送到远程$ git push origin mastergit log查看$ git log --graph --pretty=oneline --abbrev-commit *
原本分叉的提交现在变成一条直线了!这种神奇的操作是怎么实现的?
我们注意观察,发现Git把我们本地的提交“挪动”了位置,放到了
f005ed4 (origin/master) set exit=1之后,这样,整个提交历史就成了一条直线。rebase操作前后,最终的提交内容是一致的,但是,我们本地的commit修改内容已经变化了,它们的修改不再基于d1be385 init hello,而是基于f005ed4 (origin/master) set exit=1,但最后的提交7e61ed4内容是一致的。这就是
rebase操作的特点:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了。
-
rebase操作可以把本地未push的分叉提交历史整理成直线 -
rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比
Git标签
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git 的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起
创建标签
-
切换到需要打标签的分支上
$ git branch $ git checkout master -
git tag <name>就可以打一个新标签$ git tag v1.0 -
默认标签是打在最新提交的
commit上的。有时候,如果忘了打标签方法是找到历史提交的commit id,然后打上就可以$ git log --pretty=oneline --abbrev-commit找到对应的
commit id,敲入命令:$ git tag v0.9 <commit id> -
查看标签
$ git tag标签不是按时间顺序列出,而是按字母排序的。可以用
git show查看标签信息$ git show # 查看具体某个标签信息 $ gitshow <tagname> -
创建带有说明的标签,用
-a指定标签名,-m指定说明文字$ git tag -a v0.1 -m "version 0.1 released"
注意:标签总是和某个
commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
标签操作
-
如果标签打错了,也可以删除
$ git tag -d v0.1因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
-
推送某个标签到远程,使用命令
git push origin <tagname>$ git push origin v1.0一次性推送全部尚未推送到远程的本地标签
$ git push origin --tags -
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除
$ git tag -d <tagname>然后,从远程删除。删除命令也是
push,但是格式如下$ git push origin :refs/tags/<tagname> -
命令
git tag -a <tagname> -m 'messages'可以创建一个带附注的标签 -
命令
git tag -s <tagname> -m 'messages'可以创建一个带gpg签名的标签
总结
在日常工作中git少不了,所以掌握这门核心技术,是非常有必要的。
虽说,大模型(LLMs)不断飞速发展,他们展现的基础能力,能超过基础人员的认知,但是在逻辑、思维能力上LLms是达不到,因此,开发人员,更应该扎实基础,在此基础上,不断提高个人的认知以及构思能力。近期,在不断的重温基础知识,厚积才能薄发。
以上是我个人在学习过程中的记录所学,希望对正在一起学习的小伙伴有所帮助!!!
如果对你有帮助,希望你能一键三连【关注、点赞、收藏】!!!
参考链接
- https://www.git-tower.com/learn/?utm_source=Tower+Blog&utm_medium=cheat+sheet+pdf&utm_content=german+version&utm_campaign=Tower+website
- https://rogerdudler.github.io/git-guide/
- https://www.git-scm.com/
928

被折叠的 条评论
为什么被折叠?



