美文网首页
Git撤销与合并

Git撤销与合并

作者: overflowedstack | 来源:发表于2020-03-22 20:47 被阅读0次

    1. git init
    创建一个空的git repo,也就是创建一个.git的子目录,这个目录包含了几乎所有git存储和操作的东西。新初始化的.git目录的典型结构如下:

    .git $ ls -l
    total 24
    -rw-r--r--   1 a123  staff   23  3 22 20:57 HEAD
    -rw-r--r--   1 a123  staff  137  3 22 20:57 config
    -rw-r--r--   1 a123  staff   73  3 22 20:57 description
    drwxr-xr-x  13 a123  staff  416  3 22 20:57 hooks
    drwxr-xr-x   3 a123  staff   96  3 22 20:57 info
    drwxr-xr-x   4 a123  staff  128  3 22 20:57 objects
    drwxr-xr-x   4 a123  staff  128  3 22 20:57 refs
    

    description文件仅供git web程序使用,平常无需关心。
    config文件包含项目特有的配置选项。
    info目录包含一个全局性排除文件,用以放置那些不希望被记录在.gitignore文件中的忽略模式。
    hooks目录包含客户端或服务端的钩子脚本。
    HEAD文件指向目前被检出的分支。
    index文件(尚待创建)保存暂存区信息。
    objects目录存储所有数据内容。
    refs目录存储指向数据的提交对象的指针。

    git的默认分支名字是master,git init时默认创建它。

    myproject $ cat .git/HEAD
    ref: refs/heads/master
    
    myproject $ git status
    位于分支 master
    
    尚无提交
    
    无文件要提交(创建/拷贝文件并使用 "git add" 建立跟踪
    

    2. git的三种状态,以及工作区(Working directory),暂存区(Index),HEAD
    Git 有三种状态,你的文件可能处于其中之一:已修改(modified)、已暂存(staged)和已提交(committed)

    基于刚才init的git project,做一些改动。

    myproject $ touch file1.txt
    myproject $ git status
    位于分支 master
    
    尚无提交
    
    未跟踪的文件:
      (使用 "git add <文件>..." 以包含要提交的内容)
    
        file1.txt
    
    提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
    myproject $ ls .git/
    HEAD        description info        refs
    config      hooks       objects
    
    

    会看到在git add之后,.git下面多了一个index文件。

    myproject $ git add file1.txt 
    myproject $ ls .git/
    HEAD        description index       objects
    config      hooks       info        refs
    

    这时候,所做的改动就处于已暂存状态,体现在index文件中。
    可以利用以下命令查看git缓存了的内容。

    myproject $ git ls-files --stage
    100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0   file1.txt
    

    同时,.git/objects下面多了一个子文件夹,并生成了一个新文件。这个新文件就对应了刚才所做的改动。这就是git存储内容的方式--一个文件对应一条内容,以该内容加上特定头部信息一起的SHA-1校验和作为文件名。校验和的前两个字符用于命名子目录,余下的38个字符则作为文件名。后面会详叙。

    myproject $ ls .git/objects
    e6  info    pack
    myproject $ ls .git/objects/e6
    9de29bb2d1d6434b8b29ae775ad8c2e48c5391
    

    可以通过cat-file命令从git那里查看存储的内容。
    git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

    myproject $ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
    

    由于file1.txt的内容为空,所以这里显示为空。

    这时候可以往file1.txt里添加一些内容,并git add。可以看到.git/objects又多了一个object。

    myproject $ vim file1.txt 
    myproject $ git add file1.txt 
    
    myproject $ find .git/objects
    .git/objects
    .git/objects/pack
    .git/objects/info
    .git/objects/5e
    .git/objects/5e/1187c8883693e5657056087bb5f105337fa0d6
    .git/objects/e6
    .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
    

    查看这个新的对象的内容以及类型。会发现它是一个blob对象。

    myproject $ git cat-file -p 5e1187c8883693e5657056087bb5f105337fa0d6
    this is line1.
    myproject $ git cat-file -t 5e1187c8883693e5657056087bb5f105337fa0d6
    blob
    

    接下来commit这个change。
    myProject $ git commit -m "first commit"

    myproject $ git commit -m "first commit"
    
    myproject $ git log
    commit ec1ddc2cd64f33c13bd376e695987e88a0d35957 (HEAD -> master)
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:11:57 2020 +0800
    
        first commit
    

    查看这个commit 对象的类型以及内容,commit的tree对象所指向的内容, 我们会发现,这个tree指向的是一个blob,而这个blob的内容,就是我们刚刚做过改动的文件。

    myproject $ git cat-file -t ec1ddc2cd64f33c13bd376e695987e88a0d35957
    commit
    myproject $ git cat-file -p ec1ddc2cd64f33c13bd376e695987e88a0d35957
    tree 4a0475f1371a1df3a7c1ee5d772e782abd072271
    author stack <a123@123deMacBook-Pro.local> 1584882717 +0800
    committer stack <a123@123deMacBook-Pro.local> 1584882717 +0800
    
    first commit
    
    myproject $ git cat-file -p 4a0475f1371a1df3a7c1ee5d772e782abd072271
    100644 blob 5e1187c8883693e5657056087bb5f105337fa0d6    file1.txt
    myproject $ git cat-file -t 4a0475f1371a1df3a7c1ee5d772e782abd072271
    tree
    
    myproject $ git cat-file -p 5e1187c8883693e5657056087bb5f105337fa0d6
    this is line1.
    

    同时,我们查看一下暂存区的内容:

    myproject $ git ls-files --stage
    100644 5e1187c8883693e5657056087bb5f105337fa0d6 0   file1.txt
    

    会发现,暂存区指向的也是同样的blob对象。
    至此,一个commit就提交了,工作区,暂存区,以及head又指向了同样的内容。
    它们更新内容的顺序为,工作区->暂存区->head

    3. git reset
    将做过的change撤销掉,就像没有发生过一样。
    git reset 应用的顺序为 head->暂存区->工作区。

    (1) git reset --soft
    当前,git的状态如下。

    myproject $ git log
    commit 2e62d3728e289f0c7270ac728b00f036e1c1edff (HEAD -> master)
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:23:35 2020 +0800
    
        third commit
    
    commit ee31138368e8994f702902e21edd7d3f46c1a815
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:23:13 2020 +0800
    
        second commit
    
    commit ec1ddc2cd64f33c13bd376e695987e88a0d35957
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:11:57 2020 +0800
    
        first commit
    
    

    head指向的内容为:
    (head是当前分支引用的指针,总是指向该分支上的最后一次提交。)

    myproject $ git cat-file -p HEAD
    tree cc90f92f82e86fa02c6dbdf0fe7f6b1b85abdd6c
    parent ee31138368e8994f702902e21edd7d3f46c1a815
    author stack <a123@123deMacBook-Pro.local> 1584883415 +0800
    committer stack <a123@123deMacBook-Pro.local> 1584883415 +0800
    
    third commit
    myproject $ git ls-tree -r HEAD
    100644 blob 3234a5d219806f3c2b72e2ca4d84c75a636cfcd1    file1.txt
    

    index指向的内容为:
    (索引是你的预期的下一个提交)

    myproject $ git ls-files --stage
    100644 3234a5d219806f3c2b72e2ca4d84c75a636cfcd1 0   file1.txt
    

    我们来进行一次reset。(移动HEAD, --soft)

    myproject $ git reset --soft HEAD~
    myproject $ git ls-tree -r HEAD
    100644 blob 9381eaa3762a236ddfb7ace54dd45e508ff55943    file1.txt
    myproject $ git ls-files --stage
    100644 3234a5d219806f3c2b72e2ca4d84c75a636cfcd1 0   file1.txt
    
    myproject $ git log
    commit ee31138368e8994f702902e21edd7d3f46c1a815 (HEAD -> master)
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:23:13 2020 +0800
    
        second commit
    
    commit ec1ddc2cd64f33c13bd376e695987e88a0d35957
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:11:57 2020 +0800
    
        first commit
    

    --soft将仅仅移动HEAD的指向,而并不会移动index以及工作区。
    HEAD指的是HEAD的父节点。HEAD是父节点的父节点,也可以写成HEAD2.
    所以这个命令本质上是撤销了上一次git commit命令。

    (2) git reset --mixed
    接下来,再通过reset来更新索引。(--mixed,默认行为)

    myproject $ git ls-tree -r HEAD
    100644 blob 9381eaa3762a236ddfb7ace54dd45e508ff55943    file1.txt
    myproject $ git ls-files --stage
    100644 3234a5d219806f3c2b72e2ca4d84c75a636cfcd1 0   file1.txt
    
    myproject $ git reset --mixed HEAD~
    重置后取消暂存的变更:
    M   file1.txt
    
    myproject $ git ls-tree -r HEAD
    100644 blob 5e1187c8883693e5657056087bb5f105337fa0d6    file1.txt
    myproject $ git ls-files --stage
    100644 5e1187c8883693e5657056087bb5f105337fa0d6 0   file1.txt
    

    (3) git reset --hard
    reset更新工作目录(--hard)
    git reset --hard HEAD~
    --hard标记是reset命令的危险用法,它也是git会真正销毁数据的几个操作之一。

    如果这个commit已经被推送到远端,可以用这个命令使远端也回退到相应的版本。
    git push origin <branch> --force

    4. git revert
    将做过的change撤销掉,通过“反做”某一个版本,用一个新的commit来消除做过的change。
    当前git的状态:

    myproject $ git log
    commit c216d922fd1cc851502bd5a1ed7e92200cae75be (HEAD -> master)
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:38:59 2020 +0800
    
        third commit
    
    commit 0bb69921280911ec4a704ce6efcfd3ab1b11425a
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:38:32 2020 +0800
    
        second commit
    
    commit af58830f47a7c260dcef186250429e33dcd43715
    Author: stack <a123@123deMacBook-Pro.local>
    Date:   Sun Mar 22 21:37:56 2020 +0800
    
        first commit
    

    revert其中一个commit:

    myproject $ git revert -n 0bb69921280911ec4a704ce6efcfd3ab1b11425a
    myproject $ git status
    位于分支 master
    您在执行反转提交 0bb6992 的操作。
      (所有冲突已解决:运行 "git revert --continue")
      (使用 "git revert --abort" 以取消反转提交操作)
    
    要提交的变更:
      (使用 "git reset HEAD <文件>..." 以取消暂存)
    
        删除:     file2.txt
    
    myproject $ git commit -m "revert second commit"
    

    再来看,多了一个commit,也就是用来revert的commit:

    yproject $ git log --oneline
    0bd4d1c (HEAD -> master) revert second commit
    c216d92 third commit
    0bb6992 second commit
    af58830 first commit
    

    而若是想要revert某个版本,但是在这个版本后又做过change,则在revert的过程中可能出现冲突,则需要解决冲突之后再提交。

    5. git merge 与git rebase
    先来讲讲git merge。
    当前master 和 dev branch:

    myproject $ git log --graph --all --oneline
    * 3c3801f (HEAD -> dev) change file2 on dev
    | * 79b700e (master) change file1 on master
    |/  
    * 50b905c first commit
    

    接下来打算将dev的工作并入master分支。

    myproject $ git checkout master
    切换到分支 'master'
    myproject $ git merge dev
    Merge made by the 'recursive' strategy.
     file2.txt | 1 +
     1 file changed, 1 insertion(+)
    myproject $ git status
    位于分支 master
    无文件要提交,干净的工作区
    myproject $ git log --graph --all --oneline
    *   413dabe (HEAD -> master) Merge branch 'dev'
    |\  
    | * 3c3801f (dev) change file2 on dev
    * | 79b700e change file1 on master
    |/  
    * 50b905c first commit
    

    另外,还想将master的工作也并入dev。
    git merge之后,会发现dev branch指向了与master相同的commit:

    myproject $ git checkout dev
    切换到分支 'dev'
    myproject $ git merge master
    更新 3c3801f..413dabe
    Fast-forward
     file1.txt | 1 +
     1 file changed, 1 insertion(+)
    
    myproject $ git log --graph --all --oneline
    *   413dabe (HEAD -> dev, master) Merge branch 'dev'
    |\  
    | * 3c3801f change file2 on dev
    * | 79b700e change file1 on master
    |/  
    * 50b905c first commit
    

    所以,git merge是把两个分支的最新快照,以及两者最近的共同祖先进行三方合并,合并的结果是生成一个新的快照。

    接下来,用git rebase来合并分支。
    当前的git状态

    myproject $ git log --graph --all --oneline -n 3
    * 5441022 (dev) change file2 again on dev
    | * b727be3 (HEAD -> master) change file1 again on master
    |/  
    *   413dabe Merge branch 'dev'
    |\ 
    

    此时,采用git rebase,将dev的工作并入到master。

    myproject $ git branch
      dev
    * master
    myproject $ git rebase dev
    首先,回退头指针以便在其上重放您的工作...
    应用:change file1 again on master
    
    myproject $ git log --graph --all --oneline -n 3
    * 4dd73d6 (HEAD -> master) change file1 again on master
    * 5441022 (dev) change file2 again on dev
    *   413dabe Merge branch 'dev'
    |\
    

    当在master branch上执行git rebase dev的时候,实际发生的事情是,找到master和dev两个分支的最近共同祖先,对比当前分支(master分支)相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将master分支指向目标基底(dev的head指向的commit),最后以此将之前另存为临时文件的修改依序应用。

    可以看到,rebase使得提交历史更加整洁。尽管实际的开发工作是并行在不同branch上进行的,但是它们看上去就像是串行的一样,提交历史是一条直线没有分叉。

    因此,变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。这两种方式,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同。

    相关文章

      网友评论

          本文标题:Git撤销与合并

          本文链接:https://www.haomeiwen.com/subject/jfdsshtx.html