Git-常用命令学习
Git命令学习网站
Git的提交记录
Git 仓库中的提交记录保存的是项目目录下所有文件的快照 Git 希望提交记录尽可能地轻量,因此在每次进行提交时,它并不会盲目地复制整个目录,条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录 Git 还保存了提交的历史记录。这就是为什么大多数提交记录的上面都有父节点的原因 git commit :提交一个新的记录,提交完当前分支自动变为指向这个新的记录
Git的分支(Branch)
GIt分支就是简单地指向某个提交记录而已,所以创建再多的分支也不会造成储存或者内存上的开销,而且按照业务逻辑将项目工作分解到不同的分支比维护单一的臃肿的分支要简单得多 总而言之,使用一个分支其实就相当于是“基于这个提交记录以及它的所有父提交进行其它的工作”
分支的创建与切换
git branch 分支名 :创建一个指定名称的新分支,跟当前所在分支指向同一个提交记录(但两个分支不会同时移动,假设当前分支是main,然后创建了一个新的分支newBranch和main指向同一个记录,然后git commit提交了一个新的记录,只有main会移动指向这个新的记录,newBranch还是指向一开始和main一同指向的记录) git branch 分支名 指定提交记录的引用 :创建一个新分支,通过引用指向指定的提交记录 git chechout 分支名 :切换到指定名称分支上 git switch 分支名 :切换到指定名称分支上,该命令将逐渐取代git checkout命令 git checkout -b 新分支名 :创建一个新的分支同时切换到这个分支
分支的合并
合并分支即将一条分支合并到另一条上,例如在自己的分支上开发某个新功能,开发完成后再合并回主线
方式1:merge命令
git merge 分支名 :将指定分支合并到当前分支里。这一步会创建一个新的记录,这个记录包含了指定分支以及main分支上的所有修改,同时当前指向这个新的记录 例如,当前分支为main,除了main外还有一个不同的分支名为bugFix: 执行git merge bugFix后会变为: 然后切换到bugFix分支,再将main分支合并到bugFix分支上: 因为main继承自bugFix,所以执行git merge main时只是把bugFix移动到main所指向的分支即可
方式2:rebase命令
rebase命令实际上就是取出一系列的提交记录,复制后在另外一个地方逐个地放下去。将当前分支上的和指定分支上的提交记录不同的提交记录,复制后放到指定分支下 例如,当前分支为bugFix,另一个分支为main: 使用git rebase main 命令,将bugFix分支(当前分支)中的工作(当前分支上有而指定分支上没有的提交记录,这里是C3)移到main分支(指定分支)上: 移动之后看起来两个分支的功能好像是按顺序开发的,即提交序列是线性的。实际上是并行开发的。 最后切换到main上再执行git rebase bugFix 命令即可使main也更新指向到bugFix的位置,相当于就是,当前分支main上的所有提交记录,在指定分支bugFIx上都有,那就直接将当前分支main移到指定分支bugFix的位置
在提交树上操作
提交树即个人在项目开发中的所有提交记录
HEAD
HEAD是一个对当前检出记录的符号引用,也就是指向你正在其基础上进行工作的提交记录。不能把它当成一个分支来使用 HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的 HEAD 通常情况下是指向分支名的。在你提交时,改变了分支的状态,这一变化通过 HEAD 变得可见。分离HEAD就是让HEAD指向某个具体的提交记录(git checkout 指定记录的哈希值 可以使其指向指定记录)而不是分支名
相对引用
引用提交记录时需要其哈希值。git log 可以查看提交记录的哈希值。git中的哈希值通常很长(40位),不过git支持只提供能够唯一标识提交记录的前几个字符即可 即便如此,使用哈希值来指定提交记录的话还是比较麻烦的,所以git引入了相对引用。我们可以在某一个地方,使用^ 向上移动 1 个提交记录;使用~<num> 向上移动多个提交记录,如~3向上移动三个提交记录,也可以不加num,这样就跟^的作用一样。这两个符号直接用在引用名称(分支名或记录哈希值或HEAD)的后面 如git checkout main^相当于将HEAD切换到main的父提交(当前main指向的提交记录的上一个提交记录)。HEAD自身也可以作为相对引用的参照,通过HEAD^可以让HEAD一直往父提交移动 又如git checkout HEAD~4可以让HEAD一直往父提交移动四步
移动分支
借助相对引用以及git branch 的-f选项,可以方便地修改分支的位置 git branch -f main HEAD~3:将main分支强制指向HEAD的第三级父提交
撤销变更
reset
git reset通过把分支记录回退几个提交记录来实现撤销改动。回退后原来指向的记录就好像没有提交过。git reset commitId,回退到指定版本。如: 执行git reset HEAD~1后: 此时C2这个提交户口也就好像没有提交过一样。其实reset后,C2所做的变更还在,但是处于未加入暂存区状态
revert
reset方式对大家一起使用的远程分支是无效的,解决这个问题需要使用revert命令。git revert commitId,回退指定版本。如: 执行git revert HEAD命令后: 提交记录C2’引入了更改,而这些更改刚好是用来撤销C2这个提交的,也就是说C2’的状态与C1是相同的 revert后将记录推送到远程仓库就是可以与他人分享的 总而言之就是本地分支可以使用reset进行回退,但远程分支就只能使用revert进行回退
整理提交记录
cherry-pick
git cherry-pick commitId… : 将一个或多个提交记录复制到当前所在的位置(HEAD)下面(被选的提交记录不能是在当前所在位置的上游,即不能包含在当前所在位置的提交集中)。多个提交记录之间用空格分隔开即可 使用场景:某一个版本上出现了bug,为了debug做了一些操作,这些操作的相关代码也成为了新的一个或多个版本,最后终于调试成功得到了没有bug的版本,现在要将工作合并到主分支上,肯定不能把修改bug的分支上所有版本都合并过去,只需要合并那一个最终没有bug的版本,这时就可以使用cherry-pick将那个版本复制到main下面
交互式rebase
使用cherry-pick需要知道想复制的提交记录的哈希值,如果事先并不知道提交记录的哈希值,可以使用交互式rebase来实现记录复制 git rebase -i HEAD~num:打开一个交互式窗口,展示可以供选择进行复制且复制到HEAD下的提交记录,包括从HEAD~num到HEAD之间的提交记录,不包括HEAD~num,可以对这些记录进行排序,筛选及合并 使用场景:一开始在分支1(newImage)上进行了一次提交,然后基于这个提交创建了分支2(caption),又做了一次提交,此时要对分支1上的提交做一些修改 首先让HEAD指向的提交记录是C3,通过git rebase -i HEAD~2调整C2跟C3的顺序: 当前HEAD指向的就是要修改的C2提交记录,通过git commit --amend 命令进行修改,得到C2’’: 然后再使用一次git rebase -i HEAD~2命令,调整C3’跟C2’‘的顺序: 最后使用git branch -f main HEAD命令让main指向现在HEAD的位置就可以实现一开始的目标了,上图中的C3’‘其实就是一开始的C3,而C2’’'就是一开始的C2的修改版,所以整个操作结果就相当于在原基础上将C2修改了一样 这么做唯一的问题就是要进行两次排序,而这有可能造成由rebase而导致的冲突。解决这个问题可以使用git cherry-pick命令,还是上面那个例子,这里不再演示: 1.git branch -f caption newImage 2.git commit --amend 3.git cherry-pick C3 4.git branch -f main HEAD
Git Tags
分支很容易被移动,包括人为移动以及有新的提交记录提交时它也会移动指向。所以分支不能作为引用某个特定提交记录的标识。而GIt tag就是用来永久地将某个特定的提交命名为里程碑,然后可以像分支一样对其引用。当然这种永久也不是绝对的,因为标签可以被删除后重新在另外一个位置创建同名的标签。标签并不会随着新的提交而移动,也不能切换到某个标签上面进行修改或提交,它就像是提交树上的一个锚点,标识了某个特定的位置 git tag 标签名 commitId 建立一个指向指定提交记录commitId的标签。如果不指定提交记录,git会让这个标签指向HEAD所指向的位置
Git Describe
git describe <ref> 用来描述离指定最近的标签 ref可以是任何能被Git识别成提交记录的引用,如果没有指定的话,Git会以目前所检出的位置(HEAD) 该命令的输出结果是这样的:<tag>_<numCommits>_g<hash> tag表示的是离ref最近的标签,numCommits是表示这个ref与tag相差有多少个提交记录,hash表示的是你所给定的ref所表示的提交记录哈希值的前几位。当 ref 提交记录上有某个标签时,则只输出标签名称
选择父提交记录
操作符’^‘后面也可以加数字,不过跟’~‘不同,加数字不是用来指定向上返回几代,而是指定合并提交记录的某个父提交。前面提到,一个合并提交有两个父提交,所以遇到这样的节点时该选择哪条路径就不是很清晰了。Git默认选择合并提交的“第一个”父提交,在操作符’^‘后跟一个数字可以指定选择哪一个父提交 配合使用’~‘和’^'可以自由地在提交树上移动,而且两个操作符支持链式调用,相当于将多次~或^操作合并为一步,如git checkout HEAD~^2~2,相当于先HEAD~,再HEAD^2(选择第二个父提交),最后HEAD~2
关于远程仓库
关于远程分支
o(origin)/main表示它是一个远程分支,对应远程仓库上的一个同名分支,origin为远程仓库名,远程分支保持的是最后一次与远程仓库通信时,远程仓库上的分支的情况。此时在远程分支上进行提交操作(git checkout o/main;git commit;),这个远程分支并不会一起更新,而是进入了分离HEAD的状态,只有HEAD会随着提交而移动: 这个远程分支只有在远程仓库中相应的分支更新了以后才会更新(使用git push 命令后o/main就会指向C2)
远程仓库相关操作
关于远程仓库的操作归纳起来就是向远程仓库传输数据以及从远程仓库获取数据
获取数据与上传数据
git fetch 命令用于获取远程仓库上的分支的数据到本地仓库的对应的远程分支上。没有指定参数,会下载远程仓库上所有的提交记录到各个远程分支。包括从远程仓库下载本地仓库中缺失的提交记录以及更新远程分支指针(如o/main) 当使用git fetch获取远程仓库数据后,要合并到本地分支中。可以像合并本地分支那样来合并远程分支,使用cherry-picsk,rebase,merge等命令。 Git提供了一个专门的命令来完成获取数据以及合并分支两个操作:git pull 。git pull就相当于git fetch加git merge git push 负责将本地分支 (这个分支需是基于本地远程分支如o/main开发出来的,通过当前检出分支的属性来确定远程仓库以及要 push 的目的地) 上的变更上传到远程仓库,并在远程仓库上合并自己的新提交记录。git push不带任何参数时的行为与Git的一个名为 push.default的配置有关。它的默认值取决于当前使用的Git的版本。git push之后,本地的远程分支以及远程仓库中对应的分支都会更新,同步
git push指定参数
git push <remote> <place> 如git push origin main,这个命令就是:切到本地仓库中的“main”分支,获取所有的提交,再到远程仓库“origin”中找到“main”分支,将远程仓库中没有的提交记录都添加上去。我们通过“place”参数来告诉Git提交记录来自于 main, 要推送到远程仓库中的 main。它实际就是要同步的两个仓库的位置 因为我们通过指定参数告诉了Git所有它需要的信息, 所以git就忽略了我们所检出的分支(当前HEAD指在哪个分支上)的属性 上面的例子中本地的分支跟远程的分支都指定为了main,那么如果来源和去向分支的名称不同呢 要同时为源和目的地指定<place>的话,只需要用冒号’:'将二者连起来就可以了:git push origin <source>:<destination> 如果要推送到的目的分支不存在,Git会在远程仓库中根据提供的名称创建这个分支。 <source>不一定只能是本地的分支名,也可以是引用(如HEAD~2),只要是git能识别的位置即可,如: 执行git push origin foo^:main后得到: 如果source为空,如git push origin :foo,会删除远程仓库中的foo分支(和本地对应的远程分支)
git fetch指定参数
git fetch origin foo:Git会到远程仓库的foo分支上,然后获取所有本地不存在的提交,放到本地的o/foo(而不是本地的foo分支,如果有foo的话)上 类似于git push,通过指定<source>:<destination>可以做到从远程仓库的指定分支(source)获取提交放到本地指定分支(destination)上。同样source不一定只能是分支名,也可以是引用 如果source为空,如git fetch origin :bar,会在本地创建一个新分支bar
git pull指定参数
既然git pull相当于git fetch加git merge,所以为git指定参数可以理解为用同样的参数执行git fetch,然后再merge所抓取到的提交记录(merge到检出位置)
提交历史偏离
在上面说到的git push中,本地远程分支跟远程仓库中对应的分支的分支结构应是相同的,这样才能实现git pull后,进行本地开发(本地开发过程不影响本地的远程分支的指向,所以跟git pull的时候是一样的),最后git push到远程仓库分支上的目的。如果本地git pull之后,在本地开发还没完成还未push到远程时,远程仓库分支被修改: 远程仓库分支main在一开始pull的时候应该是指向C1记录的,因为本地的远程分支o/main指向C1,在本地开发C3时,远程仓库分支上多了一个C2,这时本地执行git push时,因为最新提交的C3基于远程分支中的C1。而远程仓库中该分支已经更新到C2了,所以Git拒绝了推送请求 所以应该先让本地的工作基于最新的远程分支(将远程上的C2更新到o/main上),包含远程仓库中最新变更 可以先fetch将远程仓库的分支上的内容更新到本地远程分支o/main上,然后再把本地main上的C3记录rebase到o/main上,最后再git push即可 也可以fetch后使用merge命令合并分支再push 更方便地,git pull是fetch和merge的简写,类似地,git pull --rebase就是 fetch和rebase的简写,使用git pull --rebase或git pull以及push两条命令即可
Remote Rejected
可能会遇到远程仓库main被锁定,不允许直接push到远程的main分支上,需要一些Pull Request流程来合并修改 解决方法就是:新建一个分支(feature), 推送到远程服务器. 然后reset自己的main分支和远程服务器保持一致, 否则下次pull并且他人的提交和你冲突的时候就会出现问题
remote tracking
当克隆仓库时, Git会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/main)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为main git checkout -b 跟踪分支名 远程分支名:创建一个新分支,跟踪指定远程分支 git branch -u 远程分支名 跟踪分支名:指定一个已存在的分支令其跟踪指定的远程分支,如果当前就在指定的跟踪分支上,还可以省略跟踪分支名
|