git rev-parse master

  可使用@{n}来引用git reflog中输出的记录,其中HEAD@{n}若为提交记录,则输出提交记录信息,若非提交记录,则输出所在的分支的最近一次提交记录。注意reflog引用日志只存在本地仓库,记录仓库内的操作信息,新克隆的仓库引用日志为空。

git show HEAD@{2}

  查看某个提交的父提交信息。

git show HEAD^

  某次提交为合并提交,则其存在多个父提交,^n表示当前提交的第n父提交。若某合并提交有两个父提交,其中第一父提交为合并时所在分支的提交,第二父提交为所合并分支的提交。

git show HEAD^2

  根据指定的次数获取对应的第一父提交,如下为第一父提交的第一父提交,与HEAD^^等价。

git show HEAD~2
       C —— D <-- dev
A —— B —— E —— F <-- master

  筛选出在一个分支中而不在另一个分支中的提交。如下命令选出在dev分支而不在master分支中的提交,即CD提交。

git log master..dev

  也可查看即将推送到远端的某些提交,如下查看当前分支推送到远端master分支的提交有哪些。

git log origin/master..HEAD

  也可加上^字符或者--not来指明不希望包含某个分支的提交,如下三个命令等价。

git log A..B
git log ^A B
git log B --not A

  查看AB包含的但是C不包含的提交。

git log A B ^C
git log A B --not C

  筛选被两个中的一个包含但又不包括两者同时包含的提交。如下查看masterdev中包含但是不包括两者共有提交,即EFCD

git log master...dev

  常用参数--left-right显示每个提交是处于哪一侧的分支。

git log --left-right master...dev

交互式暂存

  运行命令git add -i进入Git交互式终端模式,其中-i--interactive简写。

           staged     unstaged path
  1:    unchanged        +1/-1 TODO
  2:        +1/-1      nothing index.html
  3:    unchanged        +1/-1 readme.md
*** Commands ***
  1: status       2: update       3: revert       4: add untracked
  5: patch        6: diff         7: quit         8: help
What now>

  其中staged为已暂存列表,unstaged为未暂存列表,Commands为操作命令,What now后键入数字序号或命令首字母操作。

  • status:同git status一致,信息更简略
  • update:暂存文件,键入2u后输入文件对应的数字暂存文件(多个文件用,隔开),每个文件前面的*意味着选中的文件将会被暂存,>>提示符后不输入任何东西并回车表示执行此次命令
  • revert:取消暂存
  • add untracked:跟踪文件
  • patch:部分暂存文件,类似git add -p
  • diff:暂存区和最近一次提交的差异,类似git diff --cached
  • quit:退出交互式终端
  • help:命令帮助
  •   执行如下命令,部分暂存更改,其中-p--patch简写。

    git add -p
    

      其中每一个修改的文件称为一个区块,也可分隔成多个较小的区块,区块命令如下。

  • y:暂存此区块
  • n:不暂存此区块
  • q:退出,不暂存包括此区块在内的剩余的区块
  • a:暂存此区块与此文件后面所有的区块
  • d:不暂存此区块与此文件后面所有的区块
  • g:选择并跳转至一个区块
  • /:搜索正则表达示匹配的区块
  • j:跳转至下一个未处理的区块
  • J:跳转至下一个区块
  • k:跳转至上一个未处理的区块
  • K:跳转至上一个区块
  • s:将当前的区块分割成多个较小的区块
  • e:手动编辑当前的区块
  • ?:输出帮助
  •   储藏即将还不想提交的但是已修改的内容保存至堆栈中,后续可在某个分支上恢复出堆栈中的内容。不仅可以恢复到原分支,也可以恢复到其他任意指定的分支上。作用范围包括工作区和暂存区中的修改,即未提交的修改都会保存至堆栈中。

      将未提交(工作区和暂存区)的修改保存至堆栈中,不包括未跟踪的文件。

    git stash
    

      将未提交的修改和未跟踪的文件都保存至堆栈中,其中-u--include-untracked简写,也可执行git stash --all

    git stash -u
    

      将工作区的修改保存至堆栈,不包括未跟踪的文件,其中-k--keep-index简写。

    git stash -k
    

      保存至堆栈中并备注,其中message为备注信息。

    git stash save 'message'
    

      保存部分修改至堆栈中,其中-p--patch简写。

    git stash -p
    

      查看堆栈中的内容。

    git stash list
    

      运行如下命令,查看保存至堆栈中的某次修改的改动(每个修改的文件增改行统计和共计)。git stash show查看栈顶即最近一次保存至堆栈的修改的改动。

    git stash show stash@{3}
    

      查看某次修改的改动的详细差异。

    git stash show stash@{3} -p
    

      运行如下命令,应用某次改动到当前分支。git stash apply应用栈顶的改动。

    git stash apply stash@{3}
    

      已重新应用了文件的改动,但是之前暂存的修改未被重新暂存。--index选项重新应用暂存的修改。

    git stash apply --index
    

      移除堆栈上某次改动,git stash drop移除栈顶的改动。

    git stash drop stash@{3}
    

      应用堆栈上某次改动并移除。git stash drop应用并移除栈顶的改动。注意若执行git stash pop出现冲突,实际已经应用了改动,但是改动依然在堆栈列表内,手动移除即可。

    git stash pop stash@{3}
    

      清空堆栈列表。

    git stash clear
    

      运行如下命令,将堆栈中某次改动生成一个新分支,检出储藏时所在的提交,然后应用改动,应用成功后移除改动。git stash branch将栈顶改动生成一个新分支。

    git stash branch dev stash@{3}
    

      git clean用来从工作目录删除未被跟踪的文件。

      主要选项如下,可指定多项并简写。

  • -f--force简写,删除时必须指定
  • -n--dry-run简写,用于显示将被删除的文件或文件夹
  • -d:删除文件夹时必须指定
  • -x:包括.gitignore忽略的文件或文件夹
  • -X:仅为.gitignore忽略的文件或文件夹
  • -i:交互式清理
  •   查看将被删除的文件列表 。

    git clean -n
    

      查看将被删除的文件和文件夹。

    git clean -d -n
    

      删除未被跟踪的文件。

    git clean -f
    

      删除未被跟踪的文件和文件夹。

    git clean -f -d
    

      删除未被跟踪的文件和被.gitignore忽略的文件。

    git clean -f -x
    

      仅删除被.gitignore忽略的文件。

    git clean -f -X
    

      删除未被跟踪的文件和文件夹、.gitignore忽略的文件和文件夹,也可简写为git clean -fdx

    git clean -f -d -x
    

    交互式清理

      运行git clean -i -d进入交互模式。

    Would remove the following item:
      dist/ readme.md index.html
    *** Commands ***
        1: clean                2: filter by pattern    3: select by numbers
        4: ask each             5: quit                 6: help
    What now>
    

      Would remove the following item后为即将清理的文件和文件夹列表。

  • clean:清理列表内文件和文件夹
  • filter by pattern:排除清理列表部分文件或文件夹,全部排除清理列表为空自动退出交互模式
  • select by numbers:选择清理列表部分文件或文件夹,均未选择清理列表为空自动退出交互模式
  • ask each:询问方式删除列表文件或文件夹
  • quit:退出交互式模式
  • help:命令帮助
  •   从工作目录中查找一个字符串或者正则表达式。

      查找工作目录包含字符串A的行。

    git grep A
    

      查找工作目录包含字符串A的行,并展示行号。

    git grep -n A
    

      查找包含字符串A的文件。

    git grep --name-only A
    

      统计文件中出现字符串A的行数,-c--count简写。

    git grep -c A
    

      某一行满足多个条件,如下命令满足某一行包括AB,其中--or可省略。

    git grep -e A --or -e B
    

      某一行包括A并且包括B

    git grep -e A --and -e B
    

      某一行包括AB或者AC

    git grep -e A --and \( -e B -e C \)
    

    交互式变基

      用于变基时对提交历史记录进行复杂的修改,可用于调整提交顺序、改变提交中的提交信息或修改文件、压缩提交或拆分提交、也可用于移除提交等。

    D  d34...  <-- master HEAD
    C  c23...
    B  b12...
    A  <-- HEAD~3
    

      运行如下命令显示交互式变基界面,其中-i选项后为提交记录区间,HEAD~3HEAD范围的提交记录,左开又闭,即为BCD。区间终点可省略,默认为HEAD指向的提交记录。注意Git从上到下依次应用每一个提交的修改,越早的提交在越上面。

    git rebase -i HEAD~3 HEAD
    pick b12... B
    pick c23... C
    pick d34... D
    # Rebase ... onto ... (3 commands)
    

      部分选项参数如下,注意删除某一行提交即移除某个提交,全部删除变基将会终止。

  • pick:保留某个提交
  • reword:修改某个提交的提交信息
  • edit:修改某个提交
  • squash:将某个提交与上一个提交合并,可修改提交信息
  • fixup:将某个提交与上一个提交合并
  • drop:移除某个提交
  •   将pick修改为drop,保存并退出。如下将移除C的提交信息。

    pick b12... B
    drop c23... C
    pick d34... D
    

      调整编辑器内提交记录顺序,保存并退出。如下将提交顺序由BCD调整为DBC

    pick d34... D
    pick b12... B
    pick c23... C
    

    修改提交信息

      将pick修改为reword,保存并退出。如下将修改CD的提交信息。

    pick b12... B
    reword c23... C
    reword d34... D
    

      保存并退出后进入C的提交信息编辑界面,然后再进入D的提交信息编辑界面。运行git log --oneline查看提交历史。

    d89... D'
    c36... C'
    b12... B
    

      将多个提交压缩为一个提交,如下将CD压缩到B,并修改提交信息。

    pick b12... B
    squash c23... C
    squash d34... D
    

      保存并退出将修改提交信息。

    # This is a combination of 3 commits.
    # This is the 1st commit message:
    # This is the commit message #2:
    # This is the commit message #3:
    

      也可执行如下命令,跳过修改提交信息。

    pick b12... B
    fixup c23... C
    fixup d34... D
    

      拆分一个提交为多个提交,如下将拆分提交C

    pick b12... B
    edit c23... C
    pick d34... D
    

      保存并退出后,HEAD指向提交C,运行git reset HEAD^实际是撤销C的提交但是保留了修改且均为未暂存。然后再多次提交,最后执行git rebase --continue继续变基。

    git reset HEAD^
    git add readme.md
    git commit -m 'C1'
    git add index.html
    git commit -m 'C2'
    git rebase --continue
    

      运行git log --oneline查看提交历史。

    d96... D
    c35... C2
    c46... C1
    b12... B
    

      使用脚本的方式改写大量提交,可全局修改邮箱地址或从每一个提交中移除一个文件。

      filter-branch选项参数如下。

  • --tree-filter:在检出项目的每一个提交后运行指定的命令然后重新提交结果
  • --prune-empty:若修改后的提交为空则扔掉不要
  • -f--force简写,即忽略备份强制覆盖。第二次进行擦除时会出错,即Git上次已做了备份,若再次运行的话会先处理掉上次的备份
  • --all:擦除所有分支
  • --index-filter:与--tree-filter类似,--tree-filter将每个提交检出到临时目录,运行filter命令,并根据临时目录中的内容构建新的提交。而--index-filter则将每个提交复制到索引中,运行filter命令,并根据索引中的内容构建新的提交。即从索引构建提交比从目录构建提交要快
  •   擦除dev分支整个提交历史中的dist/index.html文件。误操作可运行git reflog查看历史提交校验和,再版本回退恢复。

    git filter-branch -f --prune-empty --index-filter 'git rm -f --cached --ignore-unmatch dist/index.html' dev
    

      批量修改当前分支提交历史中的作者和邮箱地址,如下将提交记录中的邮箱A@git.com修改为B@git.com,作者修改为B

    git filter-branch --commit-filter '
    if [ "$GIT_AUTHOR_EMAIL" = "A@git.com" ];
        GIT_AUTHOR_NAME="B";
        GIT_AUTHOR_EMAIL="B@git.com";
        git commit-tree "$@";
        git commit-tree "$@";
    

      HEAD是当前分支引用的指针,总是指向该分支上的最后一次提交。

      Index是预期的下一次提交,可引用为暂存区域。

      Working Directory即工作目录。

      git init创建一个Git仓库,其中的HEAD引用指向未创建的分支(master还不存在)。分支即指向提交的指针,初始化的仓库没有提交记录,默认也就不存在分支。

    ? <-- master <-- HEAD
    

      工作目录新建文件readme.md,暂为v1版本。

    ———— HEAD ———————— Index ———————— Working Directory
           ?             ?            readme.md (v1)
    

      git add获取工作目录中的内容,将其复制到Index中。

    ———— HEAD ———————— Index ———————— Working Directory
           ?         readme.md (v1)   readme.md (v1)
    

      git commitIndex中的内容保存为快照,然后创建指向快照的提交对象,更新master指向此次提交对象。

    v1 <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v1)   readme.md (v1)   readme.md (v1)
    

      修改工作目录中文件,定为v2版本,运行git status,将会看到Changes not staged for commit

    v1 <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working
    
    
    
    
        
     Directory
    readme.md (v1)   readme.md (v1)   readme.md (v2)
    

      暂存v2,运行git status,将会看到Changes to be committed

    v1 <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v1)   readme.md (v2)   readme.md (v2)
    

      提交此次修改,master指向v2版本。

    v1 —— v2 <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v2)   readme.md (v2)   readme.md (v2)
    

      重置即git reset版本回退,修改readme.md并提交,提交历史如下。

    v1 —— v2 —— v3 <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v3)   readme.md (v3)   readme.md (v3)
    

      第一步移动HEAD,即移动master指向v2HEAD再指向master。此过程可运行git reset --soft HEAD^实现,其实质是撤销了v3的提交,再次运行git commit可完成git commit --amend所做的事。

    v1 —— v2 <-- master <-- HEAD ———— HEAD ———————— Index ———————— Working Directory readme.md (v2) readme.md (v3) readme.md (v3)

      第二步更新Index,即更新暂存区域。此过程可运行git reset --mixed HEAD^实现,其中--mixed可省略,实质是撤销v3的提交,同时取消暂存所有修改。

    v1 —— v2 <-- master <-- HEAD ———— HEAD ———————— Index ———————— Working Directory readme.md (v2) readme.md (v2) readme.md (v3)

      第三步更新工作目录,即让工作目录与Index一致。此过程可运行git reset --hard HEAD^实现,强制将Index中的v2覆盖工作目录。

    v1 —— v2 <-- master <-- HEAD ———— HEAD ———————— Index ———————— Working Directory readme.md (v2) readme.md (v2) readme.md (v2)

      修改readme.md并暂存,提交历史如下。

    v1 <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v1)   readme.md (v2)   readme.md (v2)
    

      运行git reset readme.md(为git reset --mixed HEAD readme.md的简写形式),实质只是将readme.mdHEAD复制到Index

    v1 <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v1)   readme.md (v1)   readme.md (v2)
    

      也可以不从HEAD复制到Index,而是复制具体某次提交的文件对应版本,运行git reset 5f5292 readme.md,其中5f5292为某次提交的校验和。

    v1 (5f5292) —— v2 —— v3 <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v3)   readme.md (v1)   readme.md (v3)
    

      若一个项目最近有三次提交,第一次提交A新增readme.md,第二次提交B修改readme.md并新增index.txt,第三次提交C再次修改readme.md。由于BC两次提交都是修改同一功能,因此需要压缩。

          C readme.md (v3) index.txt (v1) <-- master <-- HEAD
       B readme.md (v2) index.txt (v1)
    A readme.md (v1)
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v3)   readme.md (v3)   readme.md (v3)
    index.txt (v1)   index.txt (v1)   index.txt (v1)
    

      运行git reset --soft HEAD^^HEAD移动到提交A上。

          C readme.md (v3) index.txt (v1)
       B readme.md (v2) index.txt (v1)
    A readme.md (v1) <-- master <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v1)   readme.md (v3)   readme.md (v3)
                     index.txt (v1)   index.txt (v1)
    

      运行git commitBC的修改压缩为一次新的提交D

    C readme.md (v3) index.txt (v1)
    B readme.md (v2) index.txt (v1)
    |   D readme.md (v3) index.txt (v1) <-- master <-- HEAD
    A readme.md (v1)
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v3)   readme.md (v3)   readme.md (v3)
    index.txt (v1)   index.txt (v1)   index.txt (v1)
    

      分支的提交历史如下,当前HEAD指向master分支。

       B readme.md (v2) <-- master <-- HEAD
    A readme.md (v1) <-- dev
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v2)   readme.md (v2)   readme.md (v2)
    

      git checkout dev移动HEAD指向dev分支,不同于git reset --hard HEAD,仅仅移动HEAD自身,且checkout会检查是否有未提交的修改,防止修改丢失。

       B readme.md (v2) <-- master
    A readme.md (v1) <-- dev <-- HEAD
    ———— HEAD ———————— Index ———————— Working Directory
    readme.md (v1)   readme.md (v1)   readme.md (v1)
    
  • --continue:某些情况下合并产生冲突,Git会暂停下来等待解决冲突。一种方式是git add将冲突文件标记为已解决,再次提交即可。另一种方式是标记后执行git merge --continue继续合并,若没有冲突产生,Git会自动创建一个合并提交
  • --abort:尝试恢复到合并前的状态。当工作目录中有未提交的修改,git merge --abort某些情况下无法重现合并前的状态。因此建议合并前保持干净的工作目录,可将部分修改通过git stash储藏,解决冲突后再释放出来
  • -Xignore-all-space:合并过来的分支和当前分支某一文件除了空格以外没有任何区别的时候,忽略合并过来的分支的那个文件。即若A合并B的修改,A修改为hello wor ld,B 修改为hello wo rld,两次修改等效,且忽略合并过来的修改B
  • -Xignore-space-change:忽略空格量的变化。若某行在非末尾的位置有空格而另外一个没有,按照冲突处理。即若A合并B的修改,A修改为hello*world(暂用*代替空格),B修改为hello**world,则两次修改等效,且忽略合并过来的修改BA修改为helloworldB修改为hello world,则两次修改冲突
  •   查看未合并的文件,运行如下命令。其中包括两者共同祖先的版本1、当前版本2HEAD)、合并过来的版本3MERGE_HEAD)。

    git ls-files -u
    100644 ac5336... 1	readme.md
    100644 36c569... 2	readme.md
    100644 e85456... 3	readme.md
    

      查看未合并版本的具体内容,运行如下命令,其中36c569为当前版本2的部分校验和,也可运行一个特别的语法git cat-file -p :2:readme.md

    git cat-file -p 36c569
    

      冲突文件修改后(不暂存),可运行如下命令查看修改差异。其中--base为查看修改后的版本与两者共同祖先的版本的差异,--theirs为查看修改后的版本与合并过来的版本的差异,--ours为查看修改后的版本和当前版本的差异。

    git diff [--base|--ours|--theirs]
    

      master分支修改了readme.md文件,dev分支也修改了readme.md文件,当前HEAD指向master分支,若合并dev分支的readme.md修改,将会产生大致如下的冲突。

    puts 'hi world' ======= puts 'hello git'

      此时并不知道保留哪一处修改,缺少更多的参照信息,运行如下命令可查看oursbasetheirs三个版本的差异。可通过配置git config --global merge.conflictstyle diff3来作为以后合并冲突的默认格式。

    git checkout --conflict=diff3 readme.md
    cat readme.md
    hi world
    ||||||| base
    hello world
    =======
    hello git
    >>>>>>> theirs
    

      运行如下命令,快速保留某一方的修改。其中--ours表示保留当前的修改,丢弃掉引入的修改。--theirs表示保留引入的修改,丢弃掉当前的修改。

    git checkout [--ours|--theirs] readme.md
    

      master分支和dev分支提交历史如下,当前HEAD指向master。其中BD提交修改了readme.md文件,提交C新增了index.txt,提交E新增了file.txt

    C index.txt (v1) <-- master <-- HEAD
    B readme.md (v3)
    |       E file.txt (v1) <-- dev
    |     /
    |   D readme.md (v2)
    A readme.md (v1)
    

      git merge dev合并产生冲突后,可运行如下命令,查看此次合并中包含的每一个分支的所有独立提交。

    git log --oneline --left-right HEAD...MERGE_HEAD
    < f127062 B
    < af9d363 C
    > e3eb226 D
    > c3ffff1 E
    

      添加--merge选项,只显示任何一边接触了合并冲突文件的提交。也可再添加-p选项查看所有冲突文件的区别。

    git log --oneline --left-right --merge
    < f127062 B
    > e3eb226 D
    

      运行如下命令。若有某些可以合并的修改,Git会直接合并,某些有冲突的修改,Git根据选项参数选择特定的修改。-Xours选项即产生冲突优先使用当前HEAD修改,-Xtheirs选项即优先使用dev分支修改,其余可合并的修改直接合并。

    git merge [-Xours|-Xtheirs] dev
    

    重用合并记录

      重用合并记录(reuse recorded resolution)即让Git记住解决一个块冲突的方法,下一次看到相同的冲突时自动解决它。

      配置如下选项启用rerere功能,也可不配置,在.git目录下新建rr-cache文件夹即可。

    git config --local rerere.enabled true
    

      各分支的readme.md修改内容如下。

        C readme.md (hello git) <-- master <-- HEAD
    A readme.md (hello world) —— B readme.md (hi world) <-- dev
    

      master分支下合并dev分支readme.md的修改。其中Recorded preimage表示Git已经开始跟踪此次合并了。

    git merge dev
    Auto-merging readme.md
    CONFLICT (content): Merge conflict in readme.md
    Recorded preimage for 'readme.md'
    Automatic merge failed; ...
    

      查看readme.md文件。

    cat readme.md
    hello git
    =======
    hi world
    

      查看.git/rr-cache/0ff6a9/preimage下记录的合并冲突前的版本,其中0ff6a9为此次冲突的校验和。

    hello git ======= hi world

      处理readme.md,将其标记为已解决并提交。其中Recorded resolution表示记录了此次冲突的解决方法。

    git add readme.md
    git commit -m 'D'
    Recorded resolution for 'readme.md'.
    [master ...] D
    

      冲突解决后提交历史如下。

       B readme.md (hi world)  <-- dev
     /                        \
    A readme.md (hello world)   D readme.md (hi git) <-- master <-- HEAD
     \                        /
       C readme.md (hello git)
    

      查看.git/rr-cache/0ff6a9/postimage下记录的合并冲突后的版本。本质上当Git看到一个readme.md文件的一个块冲突中有hi world在一边、hello git在另一边,它会将其解决为hi git

    hi git
    

      撤销合并提交D,然后再次合并dev的修改。其中Resolved ... using previous resolution表示使用了之前的合并记录。

    git reset --hard HEAD^
    git merge dev
    Auto-merging readme.md
    CONFLICT (content): Merge conflict in readme.md
    Resolved 'readme.md' using previous resolution.
    Automatic merge failed; ...
    

      查看使用了合并记录后的readme.md

    cat readme.md
    hi git
    

      执行git merge --abort撤销本次合并,回到合并前的状态。再来看看将dev的修改变基到master的情况。

    git switch dev
    git rebase master
    Resolved 'readme.md' using previous resolution.
    

      执行git add将文件标记为已解决,执行git rebase --continue继续变基,此次变基记录记为B'master成为dev分支的直接上游。

    B' <-- dev <-- HEAD
    C <-- master
    

      Git自动解决冲突,但是可能你已经忘记冲突时readme.md的状态了,运行如下命令,恢复至冲突时的readme.me状态。

    git checkout --conflict=merge readme.md
    cat readme.md
    hello git
    =======
    hi world
    >>>>>>> theirs
    

      分支提交历史如下。

             E —— F <-- dev
    A —— B —— C <-- master
    

      某种情况下要合并master分支的修改,来测试dev分支的修改是否影响了master分支的部分功能。

             E —— F —— G <-- dev
           /         /
    A —— B ———————— C <-- master
    

      可能多次从master分支合并至dev分支进行测试,最终master分支合并了dev分支的修改。查看master分支提交历史,可能看见很多的合并记录,历史树看起来并不直观。

             E —— F —— G —— H —— K —— L <-- dev
           /         /          /      \
    A —— B ———————— C ———————— J —— M —— N <-- master
    

      其实dev分支每次合并master分支完成测试后,可以丢弃掉那次合并记录,因为rerere已经记录了冲突的解决方法,不必担心以后再次合并,最终dev分支完成开发合并至master,提交历史树如下。

             E —— F —— H —— L <-- dev
           /                 \
    A —— B —— C —— J —— M —— N <-- master
    

      提交包括常规提交、合并提交,常规提交只有一个父提交对象,而合并提交有两个或者多个的父提交对象。git reset --hard可取消某次提交的修改,但是对于已经推送至远程仓库的提交,会造成很大的问题。另一种解决方式就是还原提交,即生成一个新的提交,此提交将会撤销指定的某次提交的修改。

      若分支提交历史如下,其中HEAD指向master

    A —— B (24e888) —— C <-- master <-- HEAD
    

      执行如下命令取消B的修改,也可执行git revert HEAD^

    git revert 24e888
    

      运行后Git会打开Vim编辑器,可修改此次新提交的提交信息。

    Revert "B"
    This reverts commit 24e888....
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    # On branch master
    

      最终提交历史如下。

    A —— B —— C —— ^B <-- master <-- HEAD
    

      各分支的提交历史大致如下,HEAD指向master分支,其中D为合并提交,其余均为常规提交。

    A —— B —— C —— D (110c0d6) —— G <-- master <-- HEAD
          \       /
           E —— F <-- dev
    

      某些情况下发现dev分支合并进来的修改存在重大缺陷,需要丢弃掉dev分支的修改,即EF两次提交的修改。若运行git resert 110c0d6撤销提交D,但是Git不知道撤销哪一个分支的修改,此时需告诉Git保留哪一个分支的修改,而另一个分支的修改将被撤销。运行如下命令创建还原提交^D,其中-m表示此次取消的是一次合并提交,1表示保留提交D的第一父提交C的修改并撤销EF的修改。

    git revert -m 1 110c0d6
    A —— B —— C —— D (110c0d6) —— G —— ^D <-- master <-- HEAD
          \       /
           E —— F <-- dev
    

      当dev分支的重大缺陷修复后,可再次合并进master。可能直觉上觉得EFH的修改均合并进了master分支,但是注意由于提交^D撤销了EF的修改,所以master并不包含EF的修改,即只有H的修改合并进了master

    A —— B —— C —— D —— G —— ^D —— I <-- master <-- HEAD
          \       /               /
           E —— F —————————————— H <-- dev
    

      解决上述情况的办法也很容易,用撤销解决撤销,即先撤销^D的修改。其中d6d7365为提交^D的部分校验和,也可执行git revert HEAD

    git revert d6d7365
    A —— B —— C —— D —— G —— ^D (d6d7365) <-- master <-- HEAD
          \       /
           E —— F ——— H <-- dev
    

      再执行git merge合并dev的修改。

    A —— B —— C —— D —— G —— ^D —— ^^D —— I <-- master <-- HEAD
          \       /                      /
           E —— F ————————————————————— H <-- dev
    

      查看某一文件每一行的最后一次修改的提交。输出的每一行中第一个字段是最后一次修改此行的提交的部分校验和,^开头表示的是此文件第一次加入项目时的提交。括号中的字段分别为作者、提交时间(含时区)、行号。最后为某一行的内容。

      查看文件第二行到第五行。其中-L表示范围,2,5表示第二行到第五行,都是闭区间,不指定-L参数和范围则查看文件所有行。

    git blame -L 2,5 readme.md
    ^4832fe2 (xx 2021-01-12 10:31:28 +0800 2)   hello
    9f6560e4 (xx 2021-01-13 10:32:29 +0800 3)   world
    cd564aa5 (xx 2021-01-14 10:33:30 +0800 4)   and
    7f3a6645 (xx 2021-01-15 10:34:31 +0800 5)   git
    

      范围也可指定行的个数,+表示往下,-表示往上。如下表示从第二行往下三行,则输出行号为234的行的提交信息。

    git blame -L 2,+3 readme.md
    

      bisect命令会对提交历史进行二分查找来帮助尽快找到是哪一个提交引入了问题。

      提交历史如下,提交C101收到了bug反馈,但是在提交C1并未存在此bug,可以确定的是在提交C1C101之间的提交引入了bug

    C1 (d564aa) —— C2 ··· C50 ··· C100 —— C101 <-- master <-- HEAD
    

      运行如下命令,选择C1C101的提交历史进行二分查找排查,代码库会切换到范围正当中的那一次提交C51,其中d564aa为提交C1的部分校验和。

    git bisect start HEAD d564aa
    

      提交C51下复现bug,并不存在,说明在C52C101之间,good表示本次提交C51没有问题。

    git bisect good
    

      Git自动切换到C52C101的中点提交C76bad表示本次提交C76有问题。

    git bisect bad
    

      不断重复此过程,最终查找到出问题的那次提交,并打印出那次提交的详细信息。

    857293... is the first bad commit
    commit 857293...
    Author: ...
    Date:   ...
    

      执行如下命令,退出查错,回到最近一次的代码提交。

    git bisect reset
    

      Git可将分支内容打包成一个二进制文件,用于邮件或其他方式传输。

      打包master分支所有提交历史,其中repo.bundle为打包生成的二进制文件名。

    git bundle create repo.bundle HEAD master
    

      克隆打包生成的二进制文件,其中repos为自定义的仓库名,也可不指定,如下则默认为repo

    git clone repo.bundle repos
    

      打包区间范围的提交记录,其中HEAD^^..HEAD左开右闭区间,即DE两次提交记录。

    git bundle create repo.bundle HEAD^^..HEAD master
    A —— B —— C —— D —— E <--master <-- HEAD
    

      检查文件是否是合法的Git包,是否拥有共同的祖先从而导入。其中The bundle requires this ref表示此包父提交对象校验和为99884a

    git bundle verify repo.bundle
    The bundle requires this ref:
    99884a...
    

      查看包可导入的分支。

    git bundle list-heads repo.bundle
    

      导入包中master分支的提交到本地dev分支。

    git fetch repo.bundle master:dev
    

      Git使用HTTP协议访问远程仓库进行操作时,每一个连接都是需要用户名和密码的。

      倘若每次推送或者拉取都输入用户名和密码,显得非常繁琐,Git提供了一个凭据系统来解决此种问题,部分选项如下。

  • 默认:凭据都不缓存,每一次连接都会询问用户名和密码
  • cache:将凭据存放在内存中一段时间,密码不会被存储在磁盘中,并且15分钟后从内存中清除。注意此选项不适用于windows系统,因为此选项是通过unix套接字进行通信
  • store:凭据明文存放在磁盘中永不过期。默认路径为C:/Users/{username}/.git-credentials
  • manager:凭据管理至windows系统中的凭据管理器,可在控制面板中的用户账户的凭据管理器中查看
  •   运行如下命令配置上述选项。

    git config --global credential.helper [cache|store|manager]
    

      一般安装Git会默认使用manager方式,其中Enable Git Credential Manager即开启manager方式

      如下窗口输入用户名和密码会被凭据管理器记录。

      子模块即一个Git仓库作为另一个Git仓库的子目录,两个仓库相互独立,同时一个仓库又依赖另一个仓库。

      仓库添加子模块,默认子模块会放到与仓库同名的目录中,即主项目中会生成子目录subrepo

    git submodule add https://github.com/username/subrepo.git
    

      也可在命令结尾指定子目录名称或路径,如下主项目中会生成子目录subrepos

    git submodule add https://github.com/username/subrepo.git subrepos
    

      注意子模块默认克隆仓库的master分支,运行如下命令克隆具体分支,其中-b--branch简写,dev为远程仓库subrepo的分支。

    git submodule add -b dev https://github.com/username/subrepo.git
    

      主项目下运行git status查看状态,注意首次添加子模块,会生成.gitmodules文件且均为暂存状态,同时.gitmodules也会被Git跟踪并管理。

    git status
    Changes to be committed:
            new file:   .gitmodules
            new file:   subrepo
    

      其中