Git 版本控制-3 命令解释 问题记录
目录
Git 疑难问题分析记录
Git 基本工作流程
Git 的三个区域:
- Working Tree(workspace):当前的工作区域
- Index/Stage:暂存区域,使用 git add xx,就可以将 xx 添加到 Stage 里面
- Repository:提交的历史,即使用 git commit 提交后的结果
1、刚开始本地的 working tree、index 与 repository(HEAD) 里面的內容都是一致的
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
2、当 git 管理的文件夹里面的内容出现改变后,此时 working tree 的內容就会跟 index 及 repository(HEAD)的不一致,而 Git 知道是哪些文件(Tracked File)被改动过,直接将文件状态设置为 modified (Unstaged files)。
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
3、当执行 git add 后,会将这些改变的文件內容加入 index 中 (Staged files),所以此时 working tree 跟 index 的內容是一致的,但他们与 repository(HEAD) 內容不一致。
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
4、接着执行 git commit 后,將 Git 索引中所有改变的文件內容提交至 Repository 中,建立出新的 commit 节点(HEAD)后, working tree 、 index 与 repository(HEAD) 区域的内容又会保持一致。
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
5、在 commit 完成后,如果发现错误,可以撤回提交并再次修改并提交,最终通过 git push 将本地的修改推送到远程的 git 服务器,此时本地和 origin 也保持一致了。
git reset 解释
reset 的本质:移动 HEAD 以及它所指向的 branch
git-reset - Reset current HEAD to the specified state
1、reset --hard
会在重置 HEAD 的同时,重置 workspace、stage、Repository 里的所有内容,也就是说,会重置以下所有内容:
- workspace 中修改但未 add 的内容
- stage 中修改且已 add 的内容
- Repository 中已经 commit 的内容
2、reset --soft
会保留 workspace 和 stage 原先的内容,并把重置 HEAD 所带来的新的差异放进 stage 中:
- workspace 中修改但未 add 的内容会保留
- stage 中修改且已 add 的内容会保留
- Repository 中已经 commit 的内容会被重置,且重置的内容被放进 stage 中
3、reset
如果不加参数,那么默认使用 --mixed
参数,它的行为是:把所有差异都混合(mixed)放入 workspace 中。
实质上,reset 虽然可以用来撤销 commit,但它的实质行为并不是撤销,而是移动 HEAD,而
reset --hard HEAD^
之所以起到了撤销 commit 的效果,是因为它把 HEAD 和它所指向的 branch 一起移动到了当前 commit 的父 commit 上,Git 的历史只能往回看,不能向未来看,所以把 HEAD 和 branch 往回移动,就能起到撤回 commit 的效果。
所以同理,
reset --hard
不仅可以撤销提交,还可以用来把 HEAD 和 branch 移动到其他的任何地方。
git revert 解释
git revert 是用于“反做”某一个版本,以达到撤销该版本的修改的目的。比如,我们 commit 了三个版本(版本一、版本二、 版本三),突然发现版本二不行,想要撤销版本二,但又不想影响撤销版本三的提交,就可以用 git revert 命令来反做版本二,生成新的版本四
,这个版本四里会保留版本三的东西,但撤销了版本二的东西。
注意:revert 是回滚某个 commit ,不是回滚 到 某个 commit
例如,原先的 commit 是这样的
c2d5669 [2 seconds ago] baiqiantao 3
df1167a [16 seconds ago] baiqiantao 2
a6263fe [27 seconds ago] baiqiantao 1
git revert df1167a
修改后的 commit 是这样的
724ab81 [13 seconds ago] baiqiantao Revert "2"
c2d5669 [27 seconds ago] baiqiantao 3
df1167a [41 seconds ago] baiqiantao 2
a6263fe [52 seconds ago] baiqiantao 1
git fetch 解释
从远程主机 clone
Git 的 clone 命令会为你自动将远程主机命名为 origin
,拉取它的所有数据,创建一个指向它的 master
分支的指针,并且在本地将其命名为 origin/master
。同时 Git 也会给你一个与 origin 的 master 分支在指向同一个地方的本地 master
分支,这样你就有工作的基础。
本地有 commit,远程也有别人的 push
远程库有人 push 了提交 C0 和 C1:
本地提交了 D0 和 D1,只要你不与 origin 服务器连接(fetch、pull),你的 origin/master
指针就不会移动。
通过 git fetch 同步
如果要同步远程库到你的工作,运行 git fetch origin
命令。这个命令查找 origin
是哪一个服务器,从中抓取本地没有的数据,并且更新本地数据库
,移动 origin/master
指针指向新的、更新后的位置。
要特别注意的一点是 fetch 抓取到新的远程跟踪分支时,本地的工作区
workspace
不会自动生成一份可编辑的副本,抓取结果是直接送到版本库Repository
中!这是和 pull 的本质区别。
打个比方,在远程库 origin 新建了一个分支 dev,git fetch 后本地不会生成一个新的分支 dev(可用 git branch 查看),只有一个不可以修改的 origin/dev 指针。
在 origin/master 上继续工作
如果想要在 origin/master
分支上工作,可以新建分支 test
并将其建立在远程跟踪分支之上:
git checkout -b test origin/master
这会给你新建一个用于工作的本地分支 test
,并且起点位于 origin/master
。
合并
如果想把拉取的结果合并到本地分支,需要手动合并。使用如下命令:
git chekout master
git merge origin/master
然而,看到上面的合并结果会想到命令 git pull 。在大多数情况下它的含义是一个 git fetch 紧接着一个 git merge 命令。即 git pull 是 git fetch 和 git merge 的两步的和。
但是由于 git pull 的使用经常令人困惑,所以通常单独显式地使用 fetch 与 merge 命令会更好一些。
git pull --rebase 解释
git pull = git fetch + git merge FETCH_HEAD
git pull --rebase = git fetch + git rebase FETCH_HEAD
现在我们有这样的两个分支:
D---E test
/
A---B---C---F--- master
在 master 执行git merge test
,然后会得到如下结果:
D--------E
/ \
A---B---C---F----G--- test, master(merge 操作会生成一个新的节点,之前的提交分开显示)
在 master 执行git rebase test
,然后得到如下结果:
A---B---D---E---[C]---[F]--- test, master(rebase 操作不会生成新的节点,是将两个分支融合成一个线性的提交)
rebase 可以生成更好的的提交树,这样可以线性的看到每一次提交,并且没有增加提交节点。
- merge 操作遇到冲突的时候,当前 merge 不能继续进行下去。手动修改冲突内容后,add 修改,commit 就可以了。
- rebase 操作遇到冲突的时候,会中断 rebase,同时会提示去解决冲突。解决冲突后,将修改 add 后执行
git rebase –continue
继续操作,或者git rebase –skip
忽略冲突。
git rebase -i 的几个功能
合并多个提交:【s】
基本步骤
- 执行
git rebase -i HEAD~影响的最少commit数
或git rebase -i 前一个commitId
- 将
需要合并的那条commit
前面的pick
改为s
,保存后退出 - 会提示你修改 commit 日志,修改完保存后退出
------------------------------------------------ 操作细节 ------------------------------------------------
在使用 Git 作为版本控制的时候,我们可能会由于各种各样的原因提交了许多临时的 commit
,而这些 commit 拼接起来才是完整的任务。那么我们为了避免太多的 commit 而造成版本控制的混乱,通常我们推荐将这些 commit 合并成一个。
首先你要知道自己想合并的是哪几个提交,可以使用 git log 命令来查看提交历史,假如最近 4 条历史如下:
commit 8b5cbda494a68582164048159e605e731f357444 (HEAD -> master)
commit a472755058e78a33595390f87d03d855fc25da84
commit 6ab13045d47157842961fae70baa7ef25e1856b1
commit 00b8fe51079ac0ba1d5a87e9c0b51c9e851d233f (origin/master, origin/HEAD)
历史记录是按照时间排序的,时间近的排在前面。
如果想要合并第 2 和第 3 条记录,有两个方法,这两个方法效果是一致的:
- 从 HEAD 版本开始往过去数 3 个版本
- 指名要合并的版本
之前
的版本号
git rebase -i HEAD~3
git rebase -i 00b8fe51079ac0ba1d5a87e9c0b51c9e851d233f
请注意 00b8fe51079ac0ba1d5a87e9c0b51c9e851d233f 这个版本是不参与合并的,可以把它当做一个坐标
执行了 rebase 命令之后,会弹出一个窗口,内容如下:
pick 6ab1304 11111111
pick a472755 222222222
pick 8b5cbda 3333333
# Rebase 00b8fe5..8b5cbda onto 00b8fe5 (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword(改写) = use commit, but edit the commit message
# e, edit = use commit, but stop for amending(修正)
# s, squash(塞入) = use commit, but meld into(融入) previous commit
# f, fixup(修正) = like "squash", but discard(丢弃) this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
# Note that empty commits are commented out(被注释掉的)
可以看到,里面有详细的命令提示。我们将需要合并的那条commit
前面的pick
改为s
,之后保存并关闭文本编辑窗口即可。改完之后文本内容如下:
pick 6ab1304 11111111
s a472755 222222222
pick 8b5cbda 3333333
然后保存退出。如果没有冲突,或者冲突已经解决,则会出现如下的编辑窗口:
# This is a combination of 2 commits.
# This is the 1st commit message:
11111111
# This is the commit message #2:
222222222
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit(空消息会终止提交).
#
# Date: Sun Jul 7 21:14:02 2019 +0800
#
# interactive(互动) rebase in progress; onto 00b8fe5
# Last commands done (2 commands done):
# pick 6ab1304 11111111
# squash a472755 222222222
# Next command to do (1 remaining command):
# pick 8b5cbda 3333333
# You are currently rebasing branch 'master' on '00b8fe5'.
#
# Changes to be committed:
# modified: build.gradle
如果不做任何修改的话,默认是保留两次 commit 的日志,中间有一个空白行,你可以随意修改合并后的日志。
保存并退出后再次输入 git log 查看 commit 历史信息,你会发现这两个 commit 已经合并了。
commit 9adb987d31f11f8d38f8d817096d2d21a7390a1d (HEAD -> master)
commit 052ea3ea0b30c375e79ad9e891c5e5202768b11b
commit 00b8fe51079ac0ba1d5a87e9c0b51c9e851d233f (origin/master, origin/HEAD)
虽然我们只是合并了第 2 条和第 3 条的commit,但是第 1 条的 commitId 也变了。
如果有冲突,需要修改。修改的时候要注意,保留最新的历史,不然我们的修改就丢弃了。
修改以后要记得敲下面的命令:
git add .
git rebase --continue
如果你想放弃这次压缩的话,执行以下命令:
git rebase --abort
删除多个提交:【d】
和上面的操作基本一致,基本步骤:
- 执行
git rebase -i HEAD~影响的最少commit数
或git rebase -i 前一个commitId
- 将
需要删除的那几条commit
前面的pick
改为d
,保存后退出 - 会提示你修改 commit 日志,修改完保存后退出
------------------------------------------------ 操作细节 ------------------------------------------------
注意,这个操作感觉出现冲突的概率比较大,例如我三次 commit 都是在同一个文件的第一行插入一行新的文字:
//添加3
//添加2
//添加1
执行rebase后就会提示冲突了:
git rebase -i 9adb987d31f11f8d38f8d817096d2d21a7390a1d
Auto-merging build.gradle
CONFLICT (content): Merge conflict in build.gradle
error: could not apply 831be8e... 333
Resolve all conflicts manually手动, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 831be8e... 333
解决冲突后执行以下操作:
git add .
git rebase --continue
可以发现,相应 commit 都还原了,包括修改的文件以及提交的 commit 记录。
修改多个提交:【r】
和上面的操作基本一致,基本步骤:
- 执行
git rebase -i HEAD~影响的最少commit数
或git rebase -i 前一个commitId
- 将
需要修改的那几条commit
前面的pick
改为r
,保存后退出 - 每个改为 reword 的 commit 都会提示你修改 commit 日志,修改保存后退出
注意,修改后会生成新的 commitId
使用 git branch -d 删除分支的条件
参考:git branch -d 和 git branch -D 的区别
结论:在使用
-d
删除分支时,该分支必须完全和它的上游分支 merge 完成
,如果没有上游分支,必须要和HEAD
完全 merge。
------------------------------------------------ 案例一 ------------------------------------------------
- 1、现有一个本地分支
v0.1
,我做一些修改后 commit (没有 commit 或 stash 是不能切换到其他分支的) - 2、然后切到其他分支(因为不能删除当前分支)后尝试删除
v0.1
,可以发现删除失败
git branch -d v0.1
error: The branch 'v0.1' is not fully merged.
If you are sure you want to delete it, run 'git branch -D v0.1'.
- 3、然后切到
v0.1
上,将修改推送到远端:git push origin v0.1
- 4、然后再切到其他分支后尝试删除
v0.1
,可以发现删除成功了,但是有warning
信息
git branch -d v0.1
warning: deleting branch 'v0.1' that has been merged to refs/remotes/origin/v0.1', but not yet merged to HEAD.
Deleted branch v0.1 (was 4a79623).
上面的步骤进行一些优化:
- 5、假如我们后面删除前是切到了
master
分支 - 6、将
v0.1
上的修改 merge 过来git merge v0.1
,然后再删除v0.1
试试,可以发现删除成功且没有警告
------------------------------------------------ 案例二 ------------------------------------------------
- 1、首先我基于当前分支
v0.1
创建了一个分支v0.1_copy
:git branch v0.1_copy
(此分支并没有上游分支) - 2、然后切到
v0.1_copy
上,并进行一些修改,然后 commit - 3、然后切到
v0.1
后删除v0.1_copy
试试,可以发现删除失败
git branch -d v0.1_copy
error: The branch 'v0.1_copy' is not fully merged.
If you are sure you want to delete it, run 'git branch -D v0.1_copy'.
- 4、然后切到
v0.1
上,并将v0.1_copy
上的修改 merge 过来git merge v0.1_copy
- 5、然后再删除
v0.1_copy
试试,可以发现删除成功且没有警告
git branch -d v0.1_copy
Deleted branch v0.1_copy (was 03cb1c6).
------------------------------------------------ 总结 ------------------------------------------------
简单来说就是这么一个设计思想:
- 如果一个分支有和远端分支关联(有上游分支),那么在删除此分支前需要将此分支上的commit push到远端,不然在分支被删除时在其上所做的 commit 也一并被删除了,设计者认为这样的操作风险极大
- 如果一个分支没有和远端分支关联(没有上游分支),由于在此分支上的 commit 不能 push 到远端,那么为了防止 commit 也一并被删除了,设计者要求必须要和
HEAD
完全merge才能删除
总之一句话,删除分支不能导致有任何已做的 commit 无法追踪,如果你想这么做,必须使用 -D 达到你的目的
解决 push 时提示 Everything up-to-date
------------------------------------------------ 问题复现 ------------------------------------------------
1、现在有一个本地分支 v0.1,且其和远端分支 v0.1 关联,此时你在本地 v0.1 做的任何修改都可以通过git push origin v0.1
推到远端,这很正常
2、然后你通过git branch v0.1_backup
创建了一个备份分支,然后 checkout 到此分支后做了一些修改,当你 commit 后 push 到远端的 v0.1 时问题就出现了
git push origin v0.1
Everything up-to-date
其实这里的 "Everything up-to-date(最新)", 的含义是:
"all the branches you've told me how to push are up to date".
也就是说远端的v0.1是最新的
并且此时其实并没有push到远端的
git status
On branch v0.1_backup
Your branch is ahead of 'origin/v0.1' by 1 commit.
原因是很简单的,分支 v0.1_backup 并没有和远端分支关联
$ git branch -vv
master de18ed6 [origin/master] 修改1
v0.1 03cb1c6 [origin/v0.1] 修改2
* v0.1_backup 88eb433 修改3
3、现在你通过git branch -u origin/v0.1 v0.1_backup
可以将其和远端的 v0.1 关联
$ git branch -vv
master de18ed6 [origin/master] 修改1
v0.1 03cb1c6 [origin/v0.1] 修改2
* v0.1_backup 88eb433 [origin/v0.1: ahead 1] 修改3
4、然后再 push 到远端的 v0.1 会发现,问题依旧
------------------------------------------------ 解决方式 ------------------------------------------------
解决方式有三个:
- 1、改名,将本地分支名 v0.1_backup 改为和远程分支名一样的 v0.1 就可以了。神马,同名的分支已存在?那你为什么不在已存在的那个分支上操作呢?
- 2、merge,比如上面说的和远程分支同名的本地分支 v0.1 已存在时,可以先将 v0.1_backup 的修改 merge 到 v0.1 上,然后再在 v0.1 上 push 就可以了。虽然麻烦,但是这是标准操作(个人感觉)。
- 3、使用下面的命令也可以
git push origin v0.1_backup:v0.1
2020-07-30
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/13405119.html