上图展示了git的整体架构

  • 工作区(working directory),简言之就是你工作的区域。对于git而言,就是的本地工作目录。工作区的内容会包含提交到暂存区和版本库(当前提交点)的内容,同时也包含自己的修改内容。
  • 暂存区(stage area, 又称为索引区index),是git中一个非常重要的概念。是我们把修改提交版本库前的一个过渡阶段。查看GIT自带帮助手册的时候,通常以index来表示暂存区。在工作目录下有一个.git的目录,里面有个index文件,存储着关于暂存区的内容。git add命令将工作区内容添加到暂存区。
  • 本地仓库(local repository),版本控制系统的仓库,存在于本地。当执行git commit命令后,会将暂存区内容提交到仓库之中。在工作区下面有.git的目录,这个目录下的内容不属于工作区,里面便是仓库的数据信息,暂存区相关内容也在其中。这里也可以使用merge或rebase将远程仓库副本合并到本地仓库。图中的只有merge,注意这里也可以使用rebase。
  • 远程版本库(remote repository),与本地仓库概念基本一致,不同之处在于一个存在远程,可用于远程协作,一个却是存在于本地。通过push/pull可实现本地与远程的交互;
  • 远程仓库副本,可以理解为存在于本地的远程仓库缓存。如需更新,可通过git fetch/pull命令获取远程仓库内容。使用fech获取时,并未合并到本地仓库,此时可使用git merge实现远程仓库副本与本地仓库的合并。git pull 根据配置的不同,可为git fetch + git merge 或 git fetch + git rebase。rebase和merge的区别可以自己去网上找些资料了解下。

一、解决冲突

  场景如下:人生不如意之事十之八九,合并分支往往也不是一帆风顺的
  • ` git switch -c feature1
    //创建新的feature1分支,并切换到feature1分支`
  • git add readme.txt
    ` git commit -m "AND simple"
    //分支中完成代码修改并提交 `
  • git switch master
    git add readme.txt
    ` git commit -m "& simple"
    //切回到master分支,但在master分支上对readme.txt文件也做了同行位置的代码提交`

现在master分支和feature1分支各自都分别有新的提交,变成了这样:

这种情况下,Git无法执行快速合并,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:

$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

果然冲突了!Git告诉我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")

我们可以直接cat readme.txt查看readme.txt的内容, Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1

我们直接可以vim readme.txt修改内容后:wq直接并完成保存。或者直接在vscode编辑器里(借用插件快速修改)完成修改后保存。
再提交:

$ git add readme.txt 
$ git commit -m "conflict fixed"
[master cf810e4] conflict fixed

现在,master分支和feature1分支变成了下图所示:

最后,删除feature1分支:

$ git branch -d feature1
Deleted branch feature1 (was 14096d0).

git log --graph命令可以看到分支合并图。

注意:

在切换分支前,要留意你的工作目录和暂存区里那些还没有被提交的修改, 它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,暂存(stashing)修补提交(commit amending)), 会在bug分支处理模块中看到关于stash命令的介绍。


二、分支管理策略

  通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
  如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
  查看所有包含未合并工作的分支,可以运行 git branch --no-merged

场景如下:
请注意--no-ff参数,表示禁用Fast forward。因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去:

$ git merge --no-ff -m "merge with no-ff" dev    //master主支上合并dev分支
Merge made by the 'recursive' strategy.
 readme.txt | 1 +
 1 file changed, 1 insertion(+)

合并后,我们用git log --graph看看分支历史:

$ git log --graph
*   e1e9c68 (HEAD -> master) merge with no-ff
|\  
| * f52c633 (dev) add merge
|/  
*   cf810e4 conflict fixed
...

可以看到,不使用Fast forward模式,merge后就像这样:
image.png

分支策略

在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:

Git分支十分强大,在团队开发中应该充分应用。
合并分支时:

  • 加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。
  • 加上--squash,使用squash方式合并,把多次分支commit历史压缩为一次。例如:git merge --squash -m 'merge with squash' dev


三、版本回退

  首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,上一个版本就是HEAD~,上上一个版本就是HEAD~~,当然往上100个版本写100个~比较容易数不过来,所以写成HEAD~100

  场景如下:
  • 现在,我们要把当前版本回退到上一个版本,就可以使用git reset命令:git reset --hard HEAD~
  • 现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的commit id怎么办?
    在Git中,总是有后悔药可以吃的。当你用 git reset --hard HEAD~回退到上一版本时,再想恢复到最新版本,就必须找到最新版本的commit id。Git提供了一个命令git reflog用来记录你的每一次命令:
$ git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file
  • 找到最新版本的commit id1094adb...,于是就可以指定回到未来的某个版本:git reset --hard 1094a
模块小结:
  • HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令:git reset --hard commit_id
  • 穿梭前,用git log --graph可以查看提交历史,以便确定要回退到哪个版本。
  • 要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。
git resetgit revert都是属于重新恢复工作区以及远程提交的方式,但这两种操作有着截然不同的结果:

1、git reset 是将之前的提交记录全部抹去,将 HEAD 指向自己重置的提交记录,对应的提交记录都不复存在;
2、git revert 操作是将选择的某一次提交记录 重做,若之后又有提交,提交记录还存在,只是将指定提交的代码给清除掉。


四、管理修改

场景如下:

操作过程:第一次修改 -> git add -> 第二次修改 -> git commit

  • Git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
  • git diff 是工作区(work dict)和暂存区(stage)的比较。(注意:跟最新的暂存区对比,也就是上一个提交commit前的暂存区 ;并不是跟比如有空暂存区这种情况对比,新人潜意识就会拿工作区和commit后最新的那个提交版本去对比,以为那个就是最新的暂存区。)
  • git diff HEAD -- readme.txt 是查看工作区和版本库里面最新版本的区别。
$ git diff HEAD -- readme.txt 
diff --git a/readme.txt b/readme.txt
index 76d770f..a9c5755 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
 Git is a distributed version control system.
 Git is free software distributed under the GPL.
 Git has a mutable index called stage.
-Git tracks changes.
+Git tracks changes of files.
  • 那怎么提交第二次修改呢?你可以继续git addgit commit,也可以别着急提交第一次修改,先git add第二次修改,再git commit,就相当于把两次修改合并后一块提交了:
  • 第一次修改 -> git add -> 第二次修改 -> git add -> git commit

五、撤销修改

  • 场景一:当你改乱了工作区域某个文件的内容,想直接丢弃工作区的修改时,直接用命令:git checkout -- 文件名。(注:git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令)
    git checkout . 本地所有修改的。没有的提交的,都返回到原来的状态
  • 场景二:当你不但改乱了工作区某个文件的内容,还添加到了暂存区,想丢弃修改需要分两步走。
    ① 命令一:git reset HEAD 文件名 / 直接git reset
    ② 命令二: 接上场景一操作
  • 场景三: 但你提交了不合适的修改到了版本库,想要撤销本次提交,参考版本回退模块的内容,命令:git reset -- hard commit_id

五、bug分支处理

场景一如下:

当前正在dev分支上进行的工作还没有提交,并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge

现在,用gst查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug再合并到开发或者发布版本。
首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:

$ git checkout master   //第一步切回master分支
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
  (use "git push" to publish your local commits)

$ git switch -c issue-101  //创建issue-101修改版本分支
Switched to a new branch 'issue-101'

现在修复bug,然后提交。修复完成后,切换到master分支,并完成合并,最后删除issue-101分支:

$ git switch master  //切回主分支master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
  (use "git push" to publish your local commits)

$ git merge --no-ff -m "merged bug fix 101" issue-101  //在主分支master上合并
Merge made by the 'recursive' strategy.
 readme.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

太棒了,bug修复只花了5分钟!现在,是时候接着回到dev分支干活了!但dev工作区是干净的,刚才的工作现场存到哪去了?工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
①用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除。
②用git stash pop,恢复的同时把stash内容也删了。
再用git stash list查看,就看不到任何stash内容了。

补充:
1、如果是有新建的文件,需要git stash -u把整个放到临时区
2、如果git stash pop有冲突的时候处理完冲突还需要手动删除stash里面的缓存,用git stash drop stash@{0}

场景二如下:

master分支上修复了bug后,我们要想一想,dev分支是早期从master分支分出来的,所以,这个bug其实在当前dev分支上也存在。那怎么在dev分支上修复同样的bug?有木有更简单的方法?
同样的bug,要在dev上修复,我们只需要把4c805e2 fix bug 101这个提交所做的修改“复制”到dev分支,并不是把整个master分支merge过来。Git专门提供了一个cherry-pick命令,让我们能复制一个特定的提交到当前分支:

$ git branch
* dev
  master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
 1 file changed, 1 insertion(+), 1 deletion(-)

Git自动给dev分支做了一次提交,注意这次提交的commit1d4b803,它并不同于master4c805e2,因为这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick,我们就不需要在dev分支上手动再把修bug的过程重复一遍。

模块小结:
  • 修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
  • 当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场;
  • master分支上修复的bug,想要合并到当前dev分支,可以用git cherry-pick <commit>命令,把bug提交的修改“复制”到当前分支,避免重复劳动。

六、Feature功能开发分支

feature分支bug分支是类似的,先合并再删除。

场景如下:

因经费不足,新功能必须取消!虽然白干了,但是这个包含机密资料的分支还是必须就地销毁:

$ git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.

Git友情提醒,feature-vulcan分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D参数。

$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 287773e).

如果要丢弃一个没有被合并过的分支,可以通过git branch -D <分支名>强行删除。


七、多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin

$ git remote  //要查看远程库的信息
origin
$ git remote -v  //显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
origin  git@github.com:michaelliao/learngit.git (fetch)
origin  git@github.com:michaelliao/learngit.git (push)
$ git remote -v   //如果你的项目关联的远程仓库不止一个,该命令会将它们全部列出。 
bakkdoor  https://github.com/bakkdoor/grit (fetch)
bakkdoor  https://github.com/bakkdoor/grit (push)
cho45     https://github.com/cho45/grit (fetch)
cho45     https://github.com/cho45/grit (push)
defunkt   https://github.com/defunkt/grit (fetch)
defunkt   https://github.com/defunkt/grit (push)
koke      git://github.com/koke/grit.git (fetch)
koke      git://github.com/koke/grit.git (push)
origin    git@github.com:mojombo/grit.git (fetch)
origin    git@github.com:mojombo/grit.git (push)
  • git push origin master //Git就会把该分支推送到远程库对应的远程分支上
  • git push origin dev //如果要推送其他分支,比如dev
  • 哪些分支需要推送,哪些不需要:
    master分支是主分支,因此要时刻与远程同步;
    dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
    bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
    feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
  • 删除远端仓库的分支: git push --delete origin 分支
  • git pull时冲突的几种解决方式,写的蛮实用
  • GitHub 的 Pull Request 是指什么意思?
  • 直接指定clone某个分支: git clone -b 分支名 git地址

如果你是 Git 的重度使用者,那么还可以通过 git remote show 看到更多的信息:这个命令列出了当你在特定的分支上执行 git push 会自动地推送到哪一个远程分支。 它也同样地列出了哪些远程分支不在你的本地,哪些远程分支已经从服务器上移除了, 还有当你执行 git pull 时哪些本地分支可以与它跟踪的远程分支自动合并。

$ git remote show origin
* remote origin
  URL: https://github.com/my-org/complex-project
  Fetch URL: https://github.com/my-org/complex-project
  Push  URL: https://github.com/my-org/complex-project
  HEAD branch: master
  Remote branches:
    master                           tracked
    dev-branch                       tracked
    markdown-strip                   tracked
    issue-43                         new (next fetch will store in remotes/origin)
    issue-45                         new (next fetch will store in remotes/origin)
    refs/remotes/origin/issue-11     stale (use 'git remote prune' to remove)
  Local branches configured for 'git pull':
    dev-branch merges with remote dev-branch
    master     merges with remote master
  Local refs configured for 'git push':
    dev-branch                     pushes to dev-branch                     (up to date)
    markdown-strip                 pushes to markdown-strip                 (up to date)
    master                         pushes to master                         (up to date)
抓取分支:

Git是分布式管理的,克隆是整个仓库都被克隆下来了,当然就包括所有分支了。git clone git@github.xxx.com:xxxxx.git来克隆别人github的版本库时是克隆了全部分支,但是别人一开始只能看到你的master分支,需要创建远程origin的对应分支到本地,才能看到对应的分支。

  • 你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:
$ git branch
* master
  • 现在,你的小伙伴要在dev分支上开发,就必须创建远程origindev分支到本地,于是他用这个命令创建本地dev分支git switch -c dev origin/dev
  • 现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:
$ git add env.txt

$ git commit -m "add env"
[dev 7a5e5dd] add env
 1 file changed, 1 insertion(+)
 create mode 100644 env.txt

$ git push origin dev
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 308 bytes | 308.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
   f52c633..7a5e5dd  dev -> dev
  • 你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:
$ git push origin dev   //你尝试向dev远程分支提交代码,但你不知道你的小伙伴已经向dev分支提交过一遍了
To github.com:michaelliao/learngit.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
  • 不幸的是推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:
$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.

    git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=origin/<branch> dev
  • 更不幸的是git pull也失败了,提示no tracking information,原因是没有指定本地dev分支远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接,然后再pull:
$ git branch --set-upstream-to=origin/dev dev   //先建立链接
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
$ git pull   //再pull
Auto-merging env.txt
CONFLICT (add/add): Merge conflict in env.txt
Automatic merge failed; fix conflicts and then commit the result.
  • 这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和解决冲突模块完全一样。解决后,提交,再push:
$ git commit -m "fix env conflict"
[dev 57c53ab] fix env conflict

$ git push origin dev
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
   7a5e5dd..57c53ab  dev -> dev
  • 当然上面提示了,如果你本地dev分支远程origin/dev分支的有链接,可以直接git pull <remote远程库名> <branch分支名>来完成 git pull 操作。
  • 如果想要查看设置的所有跟踪分支,可以使用 git branch -vv 命令。 这会将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。
$ git branch -vv
  iss53     7e424c3 [origin/iss53: ahead 2] forgot the brackets
  master    1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
  testing   5ea463a trying something new
小结
  • 查看远程库信息,使用git remote -v
  • 本地新建的分支如果不推送到远程,对其他人就是不可见的;
  • 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
  • 在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;
  • 建立本地分支和远程分支的关联,使用git branch --set-upstream-to=<remote>/<branch-name> <branch-name>
  • 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

八、Git 分支 - 变基

回顾一下经典的快进合并:

分支 master想要合并的分支 hotfix 所指向的提交 C4 是你所在的提交 C2 的直接后继, 因此 Git 会直接将指针向前移动。换句话说,当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。

回顾一下经典的简单三方合并:

master 分支所在提交并不是 iss53 分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的公共祖先(C2),做一个简单的三方合并。

一次典型合并中所用到的三个快照和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。

注意:

当merge合并时使用--no-ff参数禁用fast forward时,和merge合并时出现三方合并这种情况时,我们都会发现 Merge made by the 'recursive' strategy (通过“递归”策略进行合并):

git merge --no-ff -m'merge iss' iss    //使用--no-ff参数禁用fast forward
Merge made by the 'recursive' strategy.
 readme.txt | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
$ git checkout master
Switched to branch 'master'
$ git merge iss53    //三方合并情况时
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)
下面进入变基正题:

图一:这是一个三方合并,通过合并操作来整合分叉的历史
其实,还有一种方法:你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。

操作第一步:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master) 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。此时逻辑图如下:
将 C4 中的修改变基到 C3 上,此时C4'和C4并不是同一个快照了

操作第二步:
$ git checkout master
$ git merge experiment

现在回到 master 分支,进行一次快进合并。此时,C4' 指向的快照就和图一中 C5 指向的快照一模一样了。 这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。 此时逻辑图如下:
master 分支的快进合并

变基的风险:
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。
  • 如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。
  • 如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 git pull --rebase 命令,这样尽管不能避免伤痛,但能有所缓解。
总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。

九、单人开发时变基正确操作

能会由于各种各样的原因提交了许多临时的 commit,而这些 commit 拼接起来才是完整的任务。那么我们为了避免太多的 commit 而造成版本控制的混乱,通常我们推荐将这些 commit 合并成一个。

  • 对上2次提交合并: git rebase -i HEAD~2
  • 接着我们就进入到 vi 的编辑模式

    • image.png
    • pick 的意思是要会执行这个 commit
      squash 的意思是这个 commit 会被合并到前一个commit
      我们将 c4e858b5 这个 commit 前方的命令改成 squashs,然后输入:wq以保存并退出。(如果是两条以上的commit变基合并,需要把除了最上面的保留为pick外,其余的都改成squash)
    • image.png
    • 然后我们会看到 commit message 的编辑界面:
    • image.png
    • 其中非注释部分就是两次的 commit message, 你要做的就是将这两个修改成新的 commit message。
    • image.png
    • 输入:wq保存并推出, 再次输入git log查看会发现这两个 commit 已经合并了。
    • image.png
  • 万一有冲突处理不了 git rebase --abort撤销

十、Git打标签

git tag <tagname>用于新建一个标签,默认为HEAD,也可以指定一个commit id
$ git tag v1.0
$ git tag v0.9 f52c633
git tag可以查看所有标签:
$ git tag
v0.9
v1.0
git tag -a <tagname> -m "blablabla..."可以指定标签信息:
$ git tag -a v0.1 -m "version 0.1 released" 1094adb
git show <tagname>查看标签信息:
$ git show v0.1
tag v0.1
Tagger: Michael Liao <askxuefeng@gmail.com>
Date:   Fri May 18 22:48:43 2018 +0800

version 0.1 released

commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (tag: v0.1)
Author: Michael Liao <askxuefeng@gmail.com>
Date:   Fri May 18 21:06:15 2018 +0800

    append GPL

diff --git a/readme.txt b/readme.txt
...
git tag -d <tagname>可以删除一个本地标签:
$ git tag -d v0.1
Deleted tag 'v0.1' (was f15b0dd)
git push origin <tagname>可以推送一个本地标签:
$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
 * [new tag]         v1.0 -> v1.0
git push origin --tags可以推送全部未推送过的本地标签:
$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
 * [new tag]         v0.9 -> v0.9
git push origin :refs/tags/<tagname>可以删除一个远程标签:
$ git tag -d v0.9   //如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除
Deleted tag 'v0.9' (was f52c633)
$ git push origin :refs/tags/v0.9  //然后,从远程删除
To github.com:michaelliao/learngit.git
 - [deleted]      ![image.png](/img/bVcSxd7)

十一、git pull --rebase

这个指令通过变基的原理,让拉进来的分支提交放在我们的提交之前,然后去做对比;这种对比方式就是pull进来的分支的各个commit都尝试放到我们当前分支的最新提交之前,做比较合并。当提交的commit不止一条时就需要一遍遍的比较处理变基,直至处理完,会比较麻烦,而且每次处理完一个变基都需要commit后再git rebase --continue继续下一条变基处理!所以一般在1-2个commit确实需要把对方的的提交放到我们的提交合并之前,才会使用这个指令
image.png

github中对于git的使用文档还是比较清楚的https://docs.github.com/en/get-started/using-git/resolving-merge-conflicts-after-a-git-rebase

十二、如果某些文件已经被纳入了版本管理中,就算是在.gitignore中已经声明了忽略路径也是不起作用的解决办法

git rm -r --cached .
git add .
git commit -m 'update .gitignore'
git push -u origin master

十三、gitlab远端remote删除分支,但是本地remote查看中还是存在解决办法

问题:
我们在gitlab分支面板删除了一些已经合并的分支,然后我们在vscode中fetch过后发现删除的remote分支还是显示,然后我们想去 git push -d origin xxx分支 会发现报以下错误。我们去 git branch -a 查看远程和本地所有分支,发现还会显示已删除的本地与远程有对应关系的那条分支
image.png
解决办法:git remote prune origin 删除远程没有,本地与远程有对应关系的那条分支
git branch -a查看就会发现:本地与远程有对应关系的那条分支已经被清理了。

十四、修改最近一次commit的信息

使用命令:git commit --amend 进入命令模式,修改好commit信息后:wq保存我们编辑的信息,强推到远端版本库。


Macrohoo
28 声望2 粉丝

half is wisdom!🤔