前言

Git是一个分布式版本管理系统,是为了更好地管理Linux内核开发而创立的。

Git可以在任何时间点,把文档的状态作为更新记录保存起来。因此可以把编辑过的文档复原到以前的状态,也可以显示编辑前后的内容差异。

而且,编辑旧文件后,试图覆盖较新的文件的时候(即上传文件到服务器时),系统会发出警告,因此可以避免在无意中覆盖了他人的编辑内容。

背景

老王是一个“朴实”、“憨厚”的普通程序员。一天早上,他拿着资料和自己的办公用品来到一家小型企业上班,而这天是他成为新员工的第一天。


此时的 Git 最新版本为 2.22.0,老王用的版本为:

$ git --version
git version 2.20.1.windows.1

新上任的第一天,老王干劲十足,在自己的办公桌上只一顿操作,就收拾的干干净净,而且还在配给的电脑上搭好了必须的开发环境,一看就知道是个老司机:blush:。

中午开完会后,公司决定采用 Git 来作为新项目的版本控制,由于其具有开源、分布式、灵活性, 优越性等特点;采用 Github 作为远程存储库(听说码云也不错)。

安装教程见参考资料

公司创建的 Github 远程存储库

温馨提示:

创建存储库时可以针对公司主营的项目,创建一个存储库模板,之后的其它项目都可以根据这个模板来创建,进行规范化管理。

下面设置存储库为 private 时,对外不可见(适合商业化):

create_repository.png

可先初始化一个 .gitignore 文件,其中设置提交忽略的文件或文件夹,如:

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

如果这个存储库之后被其他公司看中,则可以进行合法转让,也可进行迁移。

同步(可先跳过)

git remote

公司全体成员经过慎重考虑后,决定将此开发团队的组长的一些样板代码作为初始代码模板,其他成员依照此模板进行开发

此时的远程存储库是空的,所以为了其他成员能够使用自己的模板代码,此开发团队的组长将自己的代码提交到远程存储库上。

$ git remote add origin https://github.com/livejq/Test.git #创建与远程存储库的新连接,<name> 是 URL的快捷方式,等同于书签

$ git push -u origin master # 提交至远程仓库主分支

git remote命令允许您创建,查看和删除(remove/rm)与其他存储库的连接。远程连接更像是书签,而不是直接链接到其他存储库。它们不是提供对另一个存储库的实时访问,而是作为方便的名称,可用于引用不太方便的URL。

注意:书签/name 和 URL 是多对一的关系

拓展:
git remote:列出本地书签
git remote -v:列出本地书签和对应的URL
git remote remove:删除书签
git remote rename:重命名书签
git remote get-url –push:列出远程推送的URL
git remote get-url –all:列出远程的所有URL(包括push、fetch)
git remote show:提供有关远程配置的详细输出


$ git remote show origin
* remote origin
  Fetch URL: https://github.com/livejq/Test.git
  Push  URL: https://github.com/livejq/Test.git
  HEAD branch: master
  Remote branches:
    dev    tracked
    master tracked
  Local branches configured for 'git pull':
    dev    merges with remote dev
    master merges with remote master
  Local refs configured for 'git push':
    dev    pushes to dev    (local out of date)
    master pushes to master (fast-forwardable)

git fetch

git fetch命令将commit,文件和索引从远程存储库下载到本地存储库中(多人合作时,常常会有新的提交或合并到主分支,这时可以将它们下载到自己的版本控制中来,以了解中央历史记录的进展情况)。它的好处是对本身的存储库并没有任何影响,可以通过 git checkout 自由选择比自己更新的版本。

版本控制中的.git目录

  • .git/objects:存储本地和远程的所有提交(git log)

  • .git/refs/heads:本地分支引用列表(git branch)

  • .git/refs/remotes:以前缀加分支名的形式区别本地分支(git branch -r 查看远程分支)

  • .git/config:配置文件,主要信息如:

[remote“<name>”]
        url = <url>
        pushurl = <pushurl>
        push = <refspec>
        fetch = <refspec>

如果未在命令行上提供refspec,则默认情况下将使用此远程的refspec(可直接在配置文件中编辑)

fetch详细说明:

  • git fetch:获取所有分支+下载所有必需的提交和文件

  • git fetch:默认使用config中对应的refspec,获取指定的分支和内容

  • git fetch –all:获取远程存储库中所有已注册的name

  • git fetch –dry-run:测试操作,结果不生效

上面只是字面上的理解,并未实际操作过:flushed:…


这是老王做的一个噩梦。在梦中,老王通过 fetch 不断地去上游/远程存储库中查找更新的版本信息,而每次都发现有更新,然后老王合并到自己的分支中来,自己到最后却什么都没干,然后就被炒鱿鱼了:ghost:

虚拟中…


$ git fetch origin

a1e8fb5..45e66a4 master -> origin/master
a1e8fb5..9e8ab1c dev -> origin/dev
* [new branch] 抢饭碗的人 -> origin/抢饭碗的人

$ git log --oneline dev origin/master #查看版本信息(origin/master作为过滤器)

$ git checkout dev
$ git merge origin/master

老王在睡梦中惊醒,出了一身冷汗…,心里算是松了口气。但他知道,这个结果是有可能发生的,暗自奋起…。

git push

将本地存储库内容上载到远程存储库,为了防止覆盖提交,Git在导致目标存储库中的非快进合并时不会让您推送(解决办法:先通过 git pull下载和合并分歧生成新的本地提交,然后才能push)。(笔者推测:flushed:,在推送前,Git会比较本地历史跟远程历史的提交版本,本地历史>远程历史才能推送成功,也就是说要有新的),也可直接 push –force 忽略比较,直接覆盖整个中央存储库历史(慎重使用!!!);push –all 将本地所有分支推送到远程存储库对应的分支。

git_push_before.jpg

git_push_after.jpg

git pull

git pull <remote>
相当于执行
git fetch <remote>
git merge origin/<current-branch>

git pull命令首先运行git fetch,这将下载从指定的远程存储库的内容。然后git merge执行以将远程内容引用和头部合并到新的本地合并提交中。

许多开发人员更喜欢重新定位而不是合并,因为它就像是在说“我希望将我的变化置于其他人所做的事情之上”。

解决办法:

要想防止执行默认的 git merge,可以通过设置全局变量:

git config --global branch.autosetuprebase always

这使得用 git rebase(后面讲到) 代替 git merge

Tips:
git pull –no-commit:提取远程内容但不创建新的合并提交
git pull –rebase:复制了远程提交并将它们附加到本地origin/提交历史记录中。
git pull –verbose:在拉动期间提供详细输出,显示正在下载的内容和合并详细信息

  • git pull –rebase

git_pull_rebase.jpg

  • git merge

git_merge.jpg

简单回顾

  • remote 是建立远程连接

  • fetch ->(对应于) push:

    获取导入提交到本地分支 -> 推动导出提交到远程分支

  • pull 和 fetch 区别:它们都用于下载远程内容。有一个重要的安全区别看,git fetch可以被认为是“安全”的选择,git pull 可以认为是不安全的。git fetch将下载远程内容,而不是更改本地存储库的状态;而 git pull 将下载远程内容并立即尝试更改本地状态以匹配该内容。这可能会无意中导致本地存储库进入冲突状态。

设置共同开发成员

  • Collaborators

在自己的仓库中点击 Settings/Collaborators, 然后在输入框中输入成员的 Github 账号或邮箱地址(前提为对方设置为允许通过邮箱搜索),所有开发人员即可共同基于此仓库进行开发(push,merge,rebase,checkout branch xx)

  • Pull Requests

这个主要是针对开源项目使用的,当一位作者创建了一个仓库并写出了让人十分赞叹的东西时,你可能对此十分感兴趣并想为此做出一些贡献使它变得更好。所以你可以在此存储库中点击右上角的 fork 来拷贝到自己的仓库中去并可任意修改。若有天你做出个令自己得意的东西时,你可以在这个存储库中 new pull request 来主动发送个拉取,请求作者收录自己的这个小东西,让更多的人收益。

第一个存储库 Repository

吃饱饭后,休憩片刻,老王便开始了工作。他信心满满,十分惬意地享受着这个过程,因为 Git 是他的拿手好戏。

创建一个目录 Test ,然后右击它选择Git bash here:


$ git init
Initialized empty Git repository in C:/Users/mayn/Desktop/Test/.git/
 #初始化一个用于版本控制Test项目的数据库

$ git config --global user.name "你的昵称"
$ git config --global user.email "你的邮箱"
 #由于分布式,多人协作等,这是识别你的标志

$ git clone 远程仓库名(后缀为.git,默认为origin数据库来跟踪上游/中央/远程存储库,默认master分支)

使用克隆存储库 git clone 时,它会自动创建一个名为 origin 的远程连接,指向克隆的远程存储库。

git_clone.png

老王操作完成后,点击 Test 目录,发现有从远程存储库中克隆下来的初始开发文件,还有一个熟悉的 .git 目录,老王为此欣喜若狂。

接着,老王充分了解了组长交给自己的任务之后,开始着手准备开发资料,并完成初步的框架模型,不一会儿,一个下午就过去了。黄昏时分,夕阳从窗外照射进来,老王开始准备结束这一天的工作。

而此时老王心中浮现出一幅经典的流程图(就像武侠小说中剑客练剑时心中出现的一招一式那样):

Git_tree.jpg

依然是对 Test 目录的命令行操作:


$ git status # 显示红色的文件为 untracked 文件

$ git add . # 将未被跟踪的文件加上索引(变为staged的可提交状态)来监听其是否被操作(CRUD), 点号表示所有文件和目录,如果只是一个文件也可直接键入文件名。

$ git commit -m "完成项目的初始化框架" # 这是提交到本地存储库,状态变为 unmodified,不可修改状态,来作为本项目的一个版本(用数字和字母组成的唯一编号)(以后如果有必要,可以恢复到当前状态),-m 中输入本次提交的摘要

$ git log # 输出提交记录
Author: livejq <1325286933@qq.com>
Date:   Mon Jul 15 18:05:18 2019 +0800

    老王的第一天

$ git push -u origin master # 将此版本推送到远程存储库上

老王的第一天就这样结束了。(乌托邦主义:T,希望我出来的时候也可以这样,没有996)


修改记录 log&diff

Tips:HEAD -> master 是本地项目当前存储的最新版本(commit 后的),而 origin/master 对应于远程存储库中 git 数据库的跟踪版本

第二天清早,老王被叫到组长的办公室。走廊上,老王只听见自己的脚步声,思绪万千。原来,老王写的东西有些注释不是很符合公司的要求,所以组长只是想跟他提一下而已。:flushed:

回到办公桌上,老王整理下思绪,埋头苦干了起来,他首先修改了主程序的注释,然后 bash 查看:

$ git diff

+/**
+ *  组长说的注释,呐~
+ */
 @SpringBootApplication
 public class MealtimeApplication {

-    // 测试修改文件
     public static void main(String[] args) {
         SpringApplication.run(MealtimeApplication.class, args);
     }

+号表示新增内容,-号表示删除内容;git diff 展示的是 unstaged 状态的内容与 staged 状态的内容之间的差异(特点:包括了删除和增加的记录)。

$ git add **/MealtimeApplication.java

$ git diff --cached
+/**
+ *  组长说的注释,呐~
+ */
 @SpringBootApplication
 public class MealtimeApplication {

        SpringApplication.run(MealtimeApplication.class, args);
 }

**/表示所有目录下;git diff –cached 展示 staged 状态下所修改的内容(常用上面那个,当 add 之后可以用这个查看修改前后的差异)(特点:针对 add 之后的内容比较,而且删除的文件不会显示出来了)

$ git diff HEAD
+/**
+ *  组长说的注释,呐~
+ */
 @SpringBootApplication
 public class MealtimeApplication {

+    /**
+     * 检验 diff HEAD
+     */
     public static void main(String[] args) {
         SpringApplication.run(MealtimeApplication.class, args);
     }

这个是将当前的变化(包括 staged 和 modified 状态的内容)和 HEAD 头指向的本地存储库的版本进行比较,显示它们的差异。

$ git commit -m "老王的第二天"
[master 7fcf418] 老王的第二天
 1 file changed, 3 insertions(+)

$ git push -u origin master
Enumerating objects: 17, done.
Counting objects: 100% (17/17), done.
Delta compression using up to 6 threads
Compressing objects: 100% (8/8), done.
...
...

提交完之后,老王松了口气,还真是有惊无险呐~~:satisfied:

回到从前 reset

修订已 commit 的版本

黄昏尽头,断肠人在天涯(前一句好像…),老王正准备收拾东西回家吃晚饭(真潇洒~~)。这时,他忽然想到,有个算法可以换一种方式写,效率更高,同事明天可能会用到,所以,老王撒手就干,优化完一个类文件后:

$ git log --oneline
7fcf418 (HEAD -> master, origin/master) 老王的第二天
83e6bda 老王的第一天
90c61a5 老王的第一天

$ git add **/ProductServiceImpl.java
$ git commit --amend --no-edit
[master 82048ac] 老王的第二天
 Date: Tue Jul 16 22:25:33 2019 +0800
 2 files changed, 114 insertions(+), 111 deletions(-)
 rewrite src/main/java/com/livejq/service/impl/ProductServiceImpl.java (67%)

$ git log --oneline
82048ac (HEAD -> master) 老王的第二天
83e6bda 老王的第一天
90c61a5 老王的第一天

## 由于修订了当前版本中的内容,而要想推送到远程仓库,则必须大于远程仓库的版本(就是比它要新),所以这种修订当前已同步的版本(跟远程仓库的哈希值/编号一样),得先向其发送拉取请求,然后将其作为本地较旧的版本(如自动创建的 7fcf418),然后将其与当前修订过的版本进行一次合并,生成一个新的版本号,HEAD 移至自动创建的新版本中,之后才能进行 push 推送操作
## 多次修订同一个版本和推送会触发 OpenSSL,要求输入用户名和密码确认
$ git pull https://github.com/livejq/Test.git
From https://github.com/livejq/Test
 * branch            HEAD       -> FETCH_HEAD
Merge made by the 'recursive' strategy.

$ git log --oneline
c754205 (HEAD -> master) Merge https://github.com/livejq/Test
82048ac 老王的第二天
7fcf418 (origin/master) 老王的第二天
83e6bda 老王的第一天
90c61a5 老王的第一天

$ git push -u origin master
...

git commit –amend –no-edit 不编辑摘要,只是对上一个提交进行修订,执行并入操作(修改提交而不更改其提交消息)。

Attention:这个操作只是合并到上次提交的 commit 版本中,并没有和远程仓库同步。

回到 add 之前

老王发现有一个文件还得继续完善,暂时不需要提交,所以:

$ git reset xxx.java

回到 commit 之前(整个存储库版本)

commit_head.jpg

老王搞了真么多奇奇怪怪的东西,把自己也弄糊涂了。所以,他干脆直接回到上一次的 commit 版本(无论当前干了啥~~)。

$ git reset --hard HEAD

拓展一:
git reset –hard HEAD^ (回到不同于上一次版本的第一个版本)

git reset –hard id版本编号 (回到指定id编号的版本)

以上是回退版本,要是想再次回到先前新的版本,则可以:

git reflog 来显示先前的淹没了的版本,根据版本号直接将 HEAD 指向它即可(版本号不用输全,与其它不同时即可定位:blush:)

拓展二:
git reset –soft <commit/编号>:即使将当前的HEAD重置为,还能保留所做的更改内容
git reset –mixed <commit/编号>:缺省参数,将已经add的索引给清除掉,并输出这些变为unstaged文件的信息
git reset –hard <commit/编号>:上面有说,它将丢弃当前添加的索引,并且丢弃版本之后的版本信息(其实只是不显示,可以用reflog回溯)

开始->关机,老王满意地走出了公司的大门。

回到从前 checkout (针对单个文件)

$ git checkout 版本编号 -- 目录路径/文件名.xx

例如:

$ git log --oneline
e519e8b (HEAD -> master) 老王外传-checkout single file
c754205 (origin/master) Merge https://github.com/livejq/Test
82048ac 老王的第二天
7fcf418 老王的第二天
83e6bda 老王的第一天
90c61a5 老王的第一天

$ git checkout 7fcf -- ./pom.xml

操作完后,当前目录下的 pom.xml 文件就恢复到了先前的xx版本状态

分支 branch

老王等一伙人苦干了几天,终于完成了一个基本的功能实现。经反复测试,已确认可以上线。为了能有效的优化和扩展相关功能,决定建立开发版,层层迭代。为此,得区分 主分支 master(稳定版) 和 开发分支 dev (开发版)。

$ git branch dev # 创建 dev 分支

$ git branch # 输出所有分支

  dev
* master

$ git checkout dev # 将 HEAD 指向 dev 开发分支

Switched to branch 'dev'
M       pom.xml

Tips: 也可直接 git checkout -b dev 来直接达到创建和切换分支的作用

git_branch.jpg

老王做了些修改后直接:

$ git commit --patch # 交互式提交更改,有选项

diff --git a/pom.xml b/pom.xml
index f4c3979..c3e112f 100644
--- a/pom.xml
+++ b/pom.xml
...
...
Stage this hunk [y,n,q,a,d,e,?]? y
<stdin>:24: trailing whitespace.

<stdin>:25: trailing whitespace.

<stdin>:26: trailing whitespace.

warning: 3 lines add whitespace errors. 

[dev f094b6a] 老王在开发版 dev 中的修改
 1 file changed, 8 insertions(+), 16 deletions(-)

输入选项后,其打开默认的编辑器,里边可输入message作为提交的摘要。

Tips:
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
e - manually edit the current hunk

分支冲突 merge 和 rebase

当两个单独的分支对文件中的同一行进行编辑,或者文件已在一个分支中删除但在另一个分支中编辑时,就会发生冲突。在团队环境中工作时,很可能会发生冲突

冲突的产生 merge

这天,老王在分析代码中关键点的性能问题时,无意中发现了个BUG,他看似十分的镇静,内心却无比激动。他反复测试了几次,证实了这的确是那种躲在阴暗角落里的BUG。修改完后,他有点飞上天了:no_mouth:


$ git branch
* dev
  master

$ git log --oneline --graph
* d76e6a6 (HEAD -> dev) 老王在dev中修改完的Bug
* 9a21b06 (origin/master) 老王在开发版 dev 中的修改*2
* f094b6a 老王在开发版 dev 中的修改
* e519e8b 老王外传-checkout single file
*   c754205 Merge https://github.com/livejq/Test
|\
| * 7fcf418 老王的第二天
* | 82048ac 老王的第二天
|/
* 83e6bda 老王的第一天
* 90c61a5 老王的第一天

而在几天之前,老王的组长已经发现了这个问题,并得到了有效的解决。他在合并老王那个模块的功能时,发生了冲突:

$ git branch
  dev
* master

$ git merge dev # 这里的合并策略根据合并内容自动调用(常用recursive)
Auto-merging src/main/java/com/livejq/service/ProductService.java
CONFLICT (content): Merge conflict in src/main/java/com/livejq/service/ProductService.java
Removing src/main/java/com/livejq/VO/ResultVO.java
Automatic merge failed; fix conflicts and then commit the result.

Tips:
git checkout master
git merge dev
上述两个命令等同于:
git merge master dev

冲突的解决 merge

解决合并冲突的最直接方法是编辑冲突的文件

常用工具有:


mayn@PC-15767232209 MINGW64 ~/Desktop/Test (`master|MERGING`)

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (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)

Changes to be committed:

        deleted:    src/main/java/com/livejq/VO/ResultVO.java

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

        both modified:   src/main/java/com/livejq/service/ProductService.java


$ git log --merge
commit d76e6a635efaeb75afe9ab5cb152f53c0808e0ed (dev)
Author: livejq <1325286933@qq.com>
Date:   Wed Jul 17 16:46:23 2019 +0800

    老王在dev中修改完的Bug

commit ff3acc0b32a02d1ad7bacb819ed67df47640c521 (HEAD -> master)
Author: livejq <1325286933@qq.com>
Date:   Wed Jul 17 17:21:14 2019 +0800

    主分支增加的新功能


$ git diff
diff --cc src/main/java/com/livejq/service/ProductService.java
index 894b0cc,f49225e..0000000
--- a/src/main/java/com/livejq/service/ProductService.java
+++ b/src/main/java/com/livejq/service/ProductService.java
@@@ -36,5 -36,6 +36,10 @@@ public interface ProductService

      //下架
  //    ProductInfo offSale(String productId);
++<<<<<<< HEAD
 +      void decreaseStock(List<CartDTO> cartDTOList);
++=======
+
+       ProductInfo offSale(String productId);
++>>>>>>> dev
  }

$ cat src/main/java/com/livejq/service/ProductService.java
package com.livejq.service;

import com.livejq.dao.ProductInfo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;

/**
 * 描述:
 * 商品服务
 * Created by livejq
 * 2019-06-07 22:28
 **/
public interface ProductService {
    ProductInfo findOne(String productId);

    /**
     * 查询所有在架商品列表
     * @return
     */
    List<ProductInfo> findUpAll();

    Page<ProductInfo> findAll(Pageable pageable);

    ProductInfo save(ProductInfo productInfo);

    //加库存
//    void increaseStock(List<CartDTO> cartDTOList);

    //减库存
//    void decreaseStock(List<CartDTO> cartDTOList);

    //上架
//    ProductInfo onSale(String productId);

    //下架
//    ProductInfo offSale(String productId);
<<<<<<< HEAD
        void decreaseStock(List<CartDTO> cartDTOList);
=======

        ProductInfo offSale(String productId);
>>>>>>> dev
}

这些新线视为“冲突分隔线”。=======是冲突的“中心”,中心和<<<<<<< HEAD之间的所有内容都是HEAD所指向的当前分支中的内容,下面的为将要合并的分支中的内容。

编辑完冲突的文件后保存,然后:


$ git commit -am "手动编辑冲突文件,提交合并内容"# -a 暂存已修改和删除的文件,但对新增的文件没有任何影响
[master c04a0a1] 手动编辑冲突文件,提交合并内容

mayn@PC-15767232209 MINGW64 ~/Desktop/Test (`master`)

$ git log --oneline
c04a0a1 (HEAD -> master) 手动编辑冲突文件,提交合并内容
d76e6a6 (dev) 老王在dev中修改完的Bug
ff3acc0 主分支增加的新功能
9a21b06 (origin/master) 老王在开发版 dev 中的修改*2
f094b6a 老王在开发版 dev 中的修改
e519e8b 老王外传-checkout single file
c754205 Merge https://github.com/livejq/Test
82048ac 老王的第二天
7fcf418 老王的第二天
83e6bda 老王的第一天
90c61a5 老王的第一天

what-is-a-merge.gif

Tips:
git merge –abort:git merge使用该–abort选项执行将退出合并过程并将分支返回到合并开始之前的状态。

老王托我问问,还有别的解决冲突问题的方法吗?没办法,人有时候就是有点倔:unamused:

分支冲突 rebase(暂不演示):

这一期,老王出差去了,所以只得休刊一个星期(动漫迷是知道的:yum:)

基本操作、解决办法和 merge 相同,只是操作对象不同而已。

如果你在自己的分支上操作,想要看看主分支的程序应用到这里来看看自己所实现的功能能不能正常在项目中运行,则可以使用 rebase 将远程仓库程序加在自己的程序之上。

相关命令:

  • git rebase:合并命令

如:git rebase master或者git rebase master topic,效果如下:


            A---B---C topic
         /
    D---E---F---G master

变为:


                     A'--B'--C' topic
                 /
    D---E---F---G master

整个topic分支移动到分支的顶端master,从而有效地整合了所有新的提交master。但是,重新引用不是使用合并提交(merge),而是通过为原始分支中的每个提交(如:这里的F、G)创建全新的提交来重写项目历史记录。

  • git rebase –continue:变基时,若解决完冲突文件并add添加索引后,则可直接使用此命令继续完成变基任务。

  • git rebase –abort:若变基失败,可以使用此命令中止变基并回复到变基前的历史

  • git rebase –onto:变基多个分支

例如:

                            H---I---J topicB
                           /
                  E---F---G  topicA
                 /
    A---B---C---D  master

git rebase –onto master topicA topicB

执行完后,则:


                 H'--I'--J'  topicB
                /
                | E---F---G  topicA
                |/
    A---B---C---D  master
  • git rebase -i:interactive,交互式变基,这将打开一个文本编辑器,列出即将移动的所有提交,这使得对分支的提交历史的完全控制,其中的相关命令已显示在编辑器中,不在赘述。

merge 和 rebase 的区别

黄金法则 git rebase 是永远不要在公共分支机构使用它

简单区别1:merge通常由公共分支(如master,合并其它分支的小功能模块)发起,而rebase由一些小分支(如这里的dev,整合主程序中的一些提交)发起。

简单区别2:merge 先将两个中的差异分离出,然后在当前分支中创建一个新的提交进行合并;而 rebase 直接将中的提交加在当前分支上。

变基(rebase)的主要好处是可以获得更清晰的项目历史。缺点:重写项目历史记录可能会对您的协作工作流程造成灾难性后果;rebase 会丢失合并提交提供的上下文 - 您无法看到上游更改何时合并到功能中。

临时修改 stash

老王一出差回来,就接到了老板的临时变更任务,说是想到了一个更好的点子,得先搞出这个来(:innocent:,希望我还能看到明天的日出)。没办法,老王只得放下手上的东西:

$ git status -s 
 M src/main/java/com/livejq/utils/ResultVOUtil.java # 修改了的文件

$ git stash
Saved working directory and index state WIP on dev: d76e6a6 老王在dev中修改完的Bug

$ git status
On branch dev
Your branch and 'origin/dev' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

nothing to commit, working tree clean

# 存储完刚才修改的文件后,转到老板安排的任务
# 完成后

$ git status -s
 M pom.xml

$ git commit -am "老王完成了老板吩咐的**的任务"
[boss aa9fcaa] 老王完成了老板吩咐的**的任务
 1 file changed, 10 insertions(+)

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

$ git merge boss --no-ff -m "合并老板的小东西"
Merge made by the 'recursive' strategy.
 pom.xml | 10 ++++++++++
 1 file changed, 10 insertions(+)


$ git log --oneline --graph
*   5c0e8da (HEAD -> master) 合并老板的小东西
|\
| * aa9fcaa (boss) 老王完成了老板吩咐的**的任务
* |   c04a0a1 (origin/master) 手动编辑冲突文件,提交合并内容
|\ \
| |/
| * d76e6a6 (dev) 老王在dev中修改完的Bug
* | ff3acc0 主分支增加的新功能
|/
* 9a21b06 老王在开发版 dev 中的修改*2
* f094b6a 老王在开发版 dev 中的修改
* e519e8b 老王外传-checkout single file
*   c754205 Merge https://github.com/livejq/Test
|\
| * 7fcf418 老王的第二天
* | 82048ac 老王的第二天
|/
* 83e6bda 老王的第一天
* 90c61a5 老王的第一天

完成任务后的老王马不停蹄地接着回去干自己的事情了:disappointed:


$ git checkout dev
Switched to branch 'dev'
Your branch and 'origin/dev' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

$ git stash list # 列出暂存的任务
stash@{0}: WIP on dev: d76e6a6 老王在dev中修改完的Bug

$ git stash pop # 取出修改的文件信息
On branch dev
Your branch and 'origin/dev' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

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:   src/main/java/com/livejq/utils/ResultVOUtil.java

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (1ef62d12c8e24914539958b3dd3530a7ddc77874)

$ git status -s # 和刚接到老板任务时的状态一样
 M src/main/java/com/livejq/utils/ResultVOUtil.java

main_git_func.jpg

好了,老王的故事就讲到这儿,日后有机会再帮你们打听打听关于他的消息😎

未完,待续…

参考资料

  1. Git 下载地址
  2. Git 官方教程
  3. Backlog Git Tutorial
  4. 猴子都能懂的 Git 教程
  5. atlassian Bitbucket
  6. 浅谈Github多人合作开发
  7. git使用小记—比较