End

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_copygit 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

参考 stackoverflow 上的答案

------------------------------------------------ 问题复现 ------------------------------------------------

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

posted @ 2020-07-30 17:19  白乾涛  阅读(1235)  评论(0编辑  收藏  举报