Git(简单)使用指南

一. 前言

picv

Git, 分布式版本管理系统(Distributed Version Control System)的一种.

突然有新的想法, 准备使用新的思路来写代码, 但是担心写不去, 需要在写之前预留一个副本. 避免写不下去时可以返回初始的状态.

多个需求或者多个方案, 代码存在多个分支, 方便维护.

多人同时对一项目作业, 不同部分的协作.

简而言之, 可以归结为以下三大功能:

  • 版本控制
  • 协同工作
  • 代码管理.

二. Git安装

Git download

由于选项较多, 下面的链接提供较完整的解释, 新的版本git还在增加新的功能中...

Windows下相关安装教程

三. 基本流程

pS7w1lq.png
pSIfPOA.png

文件的状态:

In the following table, these three classes are shown in separate sections, and these characters are used for X and Y fields for the first two sections that show tracked paths:

  • ' ' = unmodified (未修改)
  • M = modified (已修改)
  • T = file type changed (regular file, symbolic link or submodule)
  • A = added (已添加到缓存区)
  • D = deleted (已删除)
  • R = renamed (已重命名)
  • C = copied (if config option status.renames is set to "copies")
  • U = updated but unmerged (更新但未合并)

(当文件状态发生改变, 会使用上述标记用于标识)

X          Y     Meaning
-------------------------------------------------
	 [AMD]   not updated
M        [ MTD]  updated in index
T        [ MTD]  type changed in index
A        [ MTD]  added to index
D                deleted from index
R        [ MTD]  renamed in index
C        [ MTD]  copied in index
[MTARC]          index and work tree matches
[ MTARC]    M    work tree changed since index
[ MTARC]    T    type changed in work tree since index
[ MTARC]    D    deleted in work tree
	    R    renamed in work tree
	    C    copied in work tree
-------------------------------------------------
D           D    unmerged, both deleted
A           U    unmerged, added by us
U           D    unmerged, deleted by them
U           A    unmerged, added by them
D           U    unmerged, deleted by us
A           A    unmerged, both added
U           U    unmerged, both modified
-------------------------------------------------
?           ?    untracked
!           !    ignored
-------------------------------------------------

本地的三个阶段分区:

  • workspace 工作区, 即当前正在作业的阶段
  • index(stage), 缓存区, 等待commit的暂存阶段
  • local repository, 最终的提交commit的版本库
flow
# 高频命令

`add`, + filename, => 表示需要追踪的文件

`commit` + (注释) => 提交

`fetch` 将远端仓库的数据同步到本地仓库, 可以使用diff比较代码和工作区的差异, 然后在同步到工作区

`pull` 将远端仓库的数据直接覆盖本地仓库

`diff` 比较文件的差异

`clone` 将项目复制到本地

`rebase` 优化commit

`checkout` 切换分支/恢复状态

四. 基本命令

# 帮助信息
git help

# 具体获得某项帮助
# 将打开浏览器加载本地的帮助文件
git help add

# 查看配置
git config --list --show-origin

# 设置用户名
git config --global user.name "John Doe"
git config --global user.email johndoe@example.com

# 查看某个项的配置信息
git config user.name
# 变更默认的编辑器
# 安装时有选择
# 还是vim来得更为方便
git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"

git config --global core.editor vim

core.editor="C:\Users\Lian\AppData\Local\Programs\Microsoft VS Code\bin\code" --wait
# 变更默认分支名称

# github

git config --global init.defaultBranch main

git的默认终端-MingW64支持部分Linux命令, 假如对Linux命令熟悉, 相应的操作会比较得心应手.

# 创建文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ touch python_test.py

# 查看文件目录
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ ls
python_test.py

# 查看文件内容
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ cat python_test.py
import time

def main():
    print(time.time())

# 创建文件夹
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ mkdir test_pack

# 查看更详细的文件目录信息
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ ls -a
./  ../  .git/  python_test.py  test_pack/

# 切换目录
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ cd test_pack

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test/test_pack (master)
$

# 查看所有的文件信息
Lian@DESKTOP-F6VO5U4 MINGW64 ~/desktop/git_test (master)
$ ls -R
.:
a.txt  d/  readme.txt  test_a/  test_b/  test_c/

./d:
'New Text Document.txt'

./test_a:
b.txt

./test_b:
c.txt  d.txt  e.py

./test_c:
1.txt  2.txt  3.py
# 假如不不使用git的终端
# 相应的powershell的命令

# 创建文本的命令
echo 'hello world' > readme.txt

# 查看文档的内容
@Lian ➜ ~\Desktop ( base 3.9.12)  get-content readme.txt
hello world
# 也支持cat命令
@Lian ➜ ~\Desktop ( base 3.9.12)  cat "bb-b.txt"
bb-b, abcn`

# 查看文件列表
ls

# 创建文件夹
mkdir test_folder

# 删除文件
rm test_folder/a.txt

# 删除文件夹
rm test_folder

4.1 vim in powershell

简单的文本修改还是vim更为便捷.

pSbFqMV.png

由于多数的终端操作是在powershell下执行, 为git单独开一个窗体执行, 相对麻烦, 这里将vim也转移到powershell.

powershell中同样可以使用vim

这里使用的powershellpowershell7.x, 相关的介绍见Windows Terminal配置和美化手册此文

找到git的安装路径

powershell的配置文件profile.ps1, 添加以下内容, 保存即可在powershell中调用vim

# vim in ps
# 这里的路径根据自己的安装位置修改
$SCRIPTPATH = "C:\Program Files\Git\usr\share\vim"
# vim的执行路径
$VIMPATH    = "C:\Program Files\Git\usr\bin\vim.exe"

Set-Alias vi   $VIMPATH
Set-Alias vim  $VIMPATH

# for editing your PowerShell profile
Function Edit-Profile
{
    vim $profile
}

# for editing your Vim settings
Function Edit-Vimrc
{
    vim $home\_vimrc
}
pSHY7ZQ.png

五. 工作区

这是在使用编辑器直接操作文件的区域, 即可见的区域.

# 直接初始化
@Lian ➜ ~\Desktop ( base 3.9.12)  git init git_test
Initialized empty Git repository in C:/Users/Lian/Desktop/git_test/.git/

# 初始化仓库, 先手动创建文件夹, 在当前文件路径下
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test
$ git init
Initialized empty Git repository in C:/Users/Lian/Desktop/git_test/.git/
# clone remote仓库项目
git clone https://github.com/baa-god/sql_node.git

@Lian ➜ ~ ( base 3.9.12) 1.963s cd sql_node

@Lian ➜ ~\sql_node ( base 3.9.12) git(master)  ls

    Directory: C:\Users\Lian\sql_node

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         2/13/2023     10:43                mysql
-a----         2/13/2023     10:43             32 README.md

@Lian ➜ ~\sql_node ( base 3.9.12) git(master)

六. 暂存区

暂存区, 一个处理文件的暂存区, 同时也是一个缓冲区, 当经过思考后, 认为代码完善没问题了, 然后才执行提交commit.

# git-rm - Remove files from the working tree and from the index

# 查看缓存区文件 , 默认参数 -c
git ls-files

# 删除缓存区文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git rm --cached a.txt
rm 'a.txt'

# 查看暂存区文件内容
@Lian ➜ ~\..\test-a ( base 3.9.12) git(6361c5a)  git ls-files -s
100644 5b236f9e8a494f33ea069dd6c9a4bf67daffec00 0       a.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(6361c5a)  git cat-file -p 5b23
a-0
# 添加单个文件到追踪
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git add python_test.py

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
# 尚未commit
No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   python_test.py

# 添加多个文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/desktop/git_test (master)
$ git add test_b/d.txt test_b/e.py # 空格间隔即可
warning: in the working copy of 'test_b/d.txt', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'test_b/e.py', LF will be replaced by CRLF the next time Git touches it

需要注意的是, 新建的文件并不会主动添加到追踪.

# 添加特定的文件类型
Lian@DESKTOP-F6VO5U4 MINGW64 ~/desktop/git_test (master)
$ git add test_c/*.txt
warning: in the working copy of 'test_c/1.txt', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'test_c/2.txt', LF will be replaced by CRLF the next time Git touches it
# 添加整个文件夹
# 文件夹
# 将整个文件夹添加到缓存区
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git add test_folder/
# 将整个目录都添加
# 包括子目录
Lian@DESKTOP-F6VO5U4 MINGW64 ~/desktop/git_test (master)
$ git add .
warning: in the working copy of 'a.txt', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'test_a/b.txt', LF will be replaced by CRLF the next time Git touches it

# 等价
git add --all
# 查看哪些文件会被添加(不会实际执行)
# 用于忽视文件是否全部纳入或者是意外纳入
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git add -n .
add '.gitignore'
add 'test_b/c.txt'
add 'test_b/d.txt'
add 'test_b/e.py'
add 'test_c/1.txt'
add 'test_c/2.txt'
add 'test_c/3.py'

6.1 忽略文件

在提交的文件, 部分内容需要排除掉.

sourcetree中支持全局范围的忽略文件.

# 创建忽略文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/desktop/git_test (master)
$ vi .gitignore

# 指定忽略的内容
Lian@DESKTOP-F6VO5U4 MINGW64 ~/desktop/git_test (master)
$ cat .gitignore
readme.txt # 忽略readme.txt
/d # d 文件夹

Lian@DESKTOP-F6VO5U4 MINGW64 ~/desktop/git_test (master)
$ git add --all
warning: in the working copy of '.gitignore', LF will be replaced by CRLF the next time Git touches it

Lian@DESKTOP-F6VO5U4 MINGW64 ~/desktop/git_test (master)
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   .gitignore
        new file:   a.txt
        new file:   test_a/b.txt
        new file:   test_b/c.txt
        new file:   test_b/d.txt
        new file:   test_b/e.py
        new file:   test_c/1.txt
        new file:   test_c/2.txt
        new file:   test_c/3.py

.gitignore忽略规则简单说明
# 表示此为注释,将被Git忽略
*.a 表示忽略所有 .a 结尾的文件
!lib.a 表示但lib.a除外
build/ 表示忽略 build/目录下的所有文件过滤整个build文件夹;
doc/*.txt 表示会忽略doc/notes.txt但不包括 doc/server/arch.txt
bin/: 表示忽略当前路径下的bin文件夹该文件夹下的所有内容都会被忽略不忽略 bin 文件
/bin: 表示忽略根目录下的bin文件
/*.c: 表示忽略cat.c不忽略 build/cat.c
debug/*.obj: 表示忽略debug/io.obj不忽略 debug/common/io.obj和tools/debug/io.obj
**/foo: 表示忽略/foo,a/foo,a/b/foo等
a/**/b: 表示忽略a/b, a/x/b,a/x/y/b等
!/bin/run.sh 表示不忽略bin目录下的run.sh文件
*.log: 表示忽略所有 .log 文件
config.php: 表示忽略当前路径的 config.php 文件
/mtk/ 表示过滤整个文件夹
*.zip 表示过滤所有.zip文件
/mtk/do.c 表示过滤某个具体文件
被过滤掉的文件就不会出现在git仓库中(gitlab或github)了当然本地库中还有只是push的时候不会上传.
需要注意的是gitignore还可以指定要将哪些文件添加到版本管理中如下:
!*.zip
!/mtk/one.txt
唯一的区别就是规则开头多了一个感叹号Git会将满足这类规则的文件添加到版本管理中.为什么要有两种规则呢?
想象一个场景:假如我们只需要管理/mtk/目录中的one.txt文件这个目录中的其他文件都不需要管理那么.gitignore规则应写为::
/mtk/*
!/mtk/one.txt
假设我们只有过滤规则而没有添加规则那么我们就需要把/mtk/目录下除了one.txt以外的所有文件都写出来!
注意上面的/mtk/*不能写为/mtk/否则父目录被前面的规则排除掉了one.txt文件虽然加了!过滤规则也不会生效!
----------------------------------------------------------------------------------
还有一些规则如下:
fd1/*
说明:忽略目录 fd1 下的全部内容;注意不管是根目录下的 /fd1/ 目录还是某个子目录 /child/fd1/ 目录都会被忽略;
/fd1/*
说明:忽略根目录下的 /fd1/ 目录的全部内容;
/*
!.gitignore
!/fw/
/fw/*
!/fw/bin/
!/fw/sf/
说明:忽略全部内容但是不忽略 .gitignore 文件根目录下的 /fw/bin/ 和 /fw/sf/ 目录;注意要先对bin/的父目录使用!规则使其不被排除.

6.2 临时存储

使用下面的分支产生的问题来演示.

@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc) 12.95s git switch master
error: Your local changes to the following files would be overwritten by checkout:
        bb-b.txt
Please commit your changes or stash them before you switch branches.
Aborting

# 建议使用git stash save执行
# 而不是直接使用 git stash
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git stash save 'file bb-b not finish'
warning: in the working copy of 'bb-b.txt', LF will be replaced by CRLF the next time Git touches it
Saved working directory and index state On cc: file bb-b not finish

# 查看临时存储的信息
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc) 143ms git stash list
stash@{0}: On cc: file bb-b not finish # 这样就可以看到这些分支到底保存的是什么工作状态

# 就可以切换到其他的分支去
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc) 146ms git switch master
Switched to branch 'master'

# 假如不是再原分支上恢复操作引发的问题
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master) 146ms git stash pop
CONFLICT (modify/delete): bb-b.txt deleted in Updated upstream and modified in Stashed changes.  Version Stashed changes of bb-b.txt left in tree.
On branch master
Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add/rm <file>..." as appropriate to mark resolution)
        deleted by us:   bb-b.txt

no changes added to commit (use "git add" and/or "git commit -a")
The stash entry is kept in case you need it again.

# 再master进行操作后
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git switch cc
error: you need to resolve your current index first
bb-b.txt: needs merge

# 出现了问题
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git status
On branch master
Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add/rm <file>..." as appropriate to mark resolution)
        deleted by us:   bb-b.txt

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

# 继续出现问题
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git restore --staged bb-b.txt
error: path 'bb-b.txt' is unmerged

# 直接将master进行重置
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git reset --hard # 谨慎操作这个命令
HEAD is now at 242d6a3 init
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git status
On branch master
nothing to commit, working tree clean
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  ls

    Directory: C:\Users\Lian\Desktop\test-a

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           2/16/2023    17:11              9 file2.txt

# 查看文件内容
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  get-content file2.txt
change3

# 可以正常切换
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git switch cc
Switched to branch 'cc'
# 恢复
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git stash pop
On branch cc
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   bb-b.txt

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (a4ed010356b8d8b1bd54a51079aa03d9866fc9d4)
# 删除stash
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git stash drop
No stash entries found.
# 已经清空
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git stash list

注意: stash产生的文件不会被推送到远端服务器.

七. 本地仓库

# commit -a # 不需要添加暂存区 => 将所有追踪的文件提交

# 执行commit
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git commit
[master (root-commit) c2f673c] inital change
 1 file changed, 7 insertions(+)
 create mode 100644 python_test.py
# 将打开默认窗口填写调教信息

# 带有信息提交
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git commit -m 'the second'
[master 32317e3] the second
 1 file changed, 1 insertion(+)
 create mode 100644 test_a/b.txt

# 修改最后一次提交的commit信息
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git commit --amend -m 'this second change'
[master d15128b] this second change
 Date: Tue Feb 14 14:42:35 2023 +0800
 1 file changed, 1 insertion(+)
 create mode 100644 test_a/b.txt
# 修改各个阶段的信息还是相对麻烦的

# 前面已经提交, 但是发现
# 漏掉文件, 重新添加
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git commit --amend --no-edit
[master ffa5a99] this second change
 Date: Tue Feb 14 14:42:35 2023 +0800
 2 files changed, 2 insertions(+)
 create mode 100644 abc.txt
 create mode 100644 test_a/b.txt

# 提交了三次的信息, 只显示两次的commit
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git log
commit ffa5a9900f72ae15203026562e67b4ca613f8045 (HEAD -> master)
Author: Lian <Lian_Hwang@126.com>
Date:   Tue Feb 14 14:42:35 2023 +0800

    this second change

commit 77c755fbb61bfd285a57e9ce5956d58159f7dd04
Author: Lian <Lian_Hwang@126.com>
Date:   Tue Feb 14 14:41:04 2023 +0800

    the first

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git log --oneline
ffa5a99 (HEAD -> master) this second change
77c755f the first

# 修改历史的commit
git rebase -i ffa5a99

git rebase -i head~1
# 相对麻烦, 试用图形软件修改更为方便

7.1 标签

提供一种更为直观的版本管理方式, 可以将之视作代码进入稳定状态的标记.

# 为最新的commit添加tag
# 注意tag名称的规范
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git tag beta1.0

# 为特定的节点标记tag
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git tag aplha1.0 c941c94

# 查看tag
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git tag
aplha1.0
beta1.0

# 看到变化
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$  git log --oneline
0088566 (HEAD -> master, tag: beta1.0) merge with test_branch
a624df5 test_branch change two
e9e8e78 master change test file
1834b3c test branch change content
466ea24 master create a test_file
9ac57ed merge new_branch
d8a4cf6 master change readme
914b547 2
c941c94 (tag: aplha1.0) 1

版本, 这里需要注意, 不应该简单将之视作仅仅是标签这么简单, 而是应该将之视作相对稳定版本代码的标记, 这样, 在回滚时, 得到的是一份相对完备, 整洁的代码, 而不是频繁的对代码更新就进行标记.

八. 查看变化

# 查看当前所处的status
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        python_test.py

nothing added to commit but untracked files present (use "git add" to track)

# 日志
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test/test_pack (master)
$ git log
fatal: your current branch 'master' does not have any commits yet

# 查看日志
$ git log
commit c2f673c6ff60981a33f119cf31a402b353a63ea1 (HEAD -> master)
Author: Lian <Lian_Hwang@126.com>
Date:   Mon Feb 13 10:57:16 2023 +0800

    inital change # 注释内容

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git log --oneline
7a010fb (HEAD -> master) bbc2
3e9e014 test diff
ffa5a99 this second change
77c755f the first

# -p参数查看每次提交的变化 -2, 表示最近两次的提交
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git log -p -2
commit 0088566cf4a958bac6df70a1e35aefac106543b0 (HEAD -> master, tag: beta1.0)
Merge: e9e8e78 a624df5
Author: Lian <Lian_Hwang@126.com>
Date:   Wed Feb 15 11:04:37 2023 +0800

    merge with test_branch

commit a624df527f95bc413d77c6643f20cad7c4d094b7
Author: Lian <Lian_Hwang@126.com>
Date:   Wed Feb 15 10:56:58 2023 +0800

    test_branch change two

diff --git a/test.txt b/test.txt
index 1d3dbbd..b96fc64 100644
--- a/test.txt
+++ b/test.txt
@@ -1,2 +1,3 @@
 master create
 test_branch change content
+test_branch change content too

# --stat, 简略信息
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git log -2 --stat
commit 0088566cf4a958bac6df70a1e35aefac106543b0 (HEAD -> master, tag: beta1.0)
Merge: e9e8e78 a624df5
Author: Lian <Lian_Hwang@126.com>
Date:   Wed Feb 15 11:04:37 2023 +0800

    merge with test_branch

commit a624df527f95bc413d77c6643f20cad7c4d094b7
Author: Lian <Lian_Hwang@126.com>
Date:   Wed Feb 15 10:56:58 2023 +0800

    test_branch change two

 test.txt | 1 +
 1 file changed, 1 insertion(+)

选项 说明
-p 按补丁格式显示每个更新之间的差异.
--stat 显示每次更新的文件修改统计信息.
--shortstat 只显示 --stat, 中最后的行数修改添加移除统计.
--name-only 仅在提交信息后显示已修改的文件清单.
--name-status 显示新增修改删除的文件清单.
--abbrev-commit 仅显示 SHA-1 的前几个字符而非所有的 40 个字符.
--relative-date 使用较短的相对时间显示(比如"2 weeks ago").
--graph 显示 ASCII 图形表示的分支合并历史.
--pretty 使用其他格式显示历史提交信息.可用的选项包括 onelineshortfullfuller 和format(后跟指定格式).

8.1 比较文件差异

# 假如commit这个添加到stage的文件后
# 这个命令将返回空值
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git diff --staged
diff --git a/bbc.txt b/bbc.txt
new file mode 100644
index 0000000..2dc53ed
--- /dev/null
+++ b/bbc.txt
@@ -0,0 +1 @@
+hello, bbc

# 缓存区和工作区的文件比较
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git diff bbc.txt
warning: in the working copy of 'bbc.txt', LF will be replaced by CRLF the next time Git touches it
diff --git a/bbc.txt b/bbc.txt
index f71589a..2381e80 100644
--- a/bbc.txt
+++ b/bbc.txt
@@ -1,2 +1,3 @@
 hello, bbc!
 new line
+3 lines # 新增内容

# 提交的内容和工作区的文件比较
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git diff head readme.txt
warning: in the working copy of 'readme.txt', LF will be replaced by CRLF the next time Git touches it
diff --git a/readme.txt b/readme.txt
index c021280..a06d008 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1 +1,3 @@
-hellx
\ No newline at end of file
+hellx
+new line 2
+new line 3

# 缓存区和仓库(commit)的文件的差异
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git diff --cached bbc.txt
diff --git a/bbc.txt b/bbc.txt
index c41b049..f71589a 100644
--- a/bbc.txt
+++ b/bbc.txt
@@ -1 +1,2 @@
 hello, bbc!
+new line # 新增内容
# 等价
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git diff --staged bbc.txt
diff --git a/bbc.txt b/bbc.txt
index c41b049..f71589a 100644
--- a/bbc.txt
+++ b/bbc.txt
@@ -1 +1,2 @@
 hello, bbc!
+new line

# 查看日志-格式化
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git log --oneline
7a010fb (HEAD -> master) bbc2
3e9e014 test diff
ffa5a99 this second change
77c755f the first

## 对比两次提交的内容的差异
$ git diff 7a010fb 3e9e014
diff --git a/bbc.txt b/bbc.txt
index 2381e80..c41b049 100644
--- a/bbc.txt
+++ b/bbc.txt
@@ -1,3 +1 @@
 hello, bbc!
-new line
-3 lines

九. 分支

关于分支的处理, 有两个关键字可以实现

  • switch
  • checkout

二者在切换分支上的功能上, 非常接近.

previous command new command
git checkout <branch> git switch <branch>
git checkout N/A (use git status)
git checkout -b <new_branch> [<start_point>] git switch -c <new-branch> [<start-point>]
git checkout -B <new_branch> [<start_point>] git switch -C <new-branch> [<start-point>]
git checkout --orphan <new_branch> git switch --orphan <new-branch>
git checkout --orphan <new_branch> <start_point> N/A (use git switch <start-point> then git switch --orphan <new-branch>)
git checkout [--detach] <commit> git switch --detach <commit>
git checkout --detach [<branch>] git switch --detach [<branch>]
git checkout [--] <pathspec>… git restore [--] <pathspec>…
git checkout --pathspec-from-file=<file> git restore --pathspec-from-file=<file>
git checkout <tree-ish> [--] <pathspec>… git restore -s <tree> [--] <pathspec>…
git checkout <tree-ish> --pathspec-from-file=<file> git restore -s <tree> --pathspec-from-file=<file>
git checkout -p [<tree-ish>] [--] [<pathspec>…] git restore -p [-s <tree>] [--] [<pathspec>…]
# 创建分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch test_branch

# 查看分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch
* master
  test_branch

# 变更当前的分支名称为main, github 默认使用main, 从之前的master
git branch -M main

# 直接生成转移分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git checkout -b new_branch
Switched to a new branch 'new_branch'

# 分支, 相当于在当前的commit的主分支上, 复制一份到新的分支上
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ ls
abc.txt  readme.txt

# 有两个节点
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git log --oneline
914b547 (HEAD -> master) 2
c941c94 1

# 根据其中的节点创建新的分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git checkout -b new_branch c941c94
Switched to a new branch 'new_branch'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ ls
readme.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ cat readme.txt
hello
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)

# 根据另一个节点创建新的分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ git switch -c n_branch 914b547
Switched to a new branch 'n_branch'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (n_branch)
$ ls
readme.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (n_branch)
$ cat readme.txt
hello
new line 1
# 在n_branch 下修改了readme.txt (三个分支都有这个文件)
# 在没有提交, 没有添加文件到缓存区的情况下, 转移到其他的分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (n_branch)
$ git switch new_branch
error: Your local changes to the following files would be overwritten by checkout:
        readme.txt
Please commit your changes or stash them before you switch branches.
Aborting
# commit之后, 可以正常切换
# 每次切换分区, 这个readme.txt文件在本地磁盘展示的内容都会随着分区转换而转换

# 在new_branch中新建一个文件a.txt, 这个文件没有添加到追踪
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ vi a.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ ls
a.txt  readme.txt

# 不影响切换, 其他的分区也出现了这个文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ git switch n_branch
Switched to branch 'n_branch'

# 在这n_branch将new_branch创建的a.txt文件commit
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (n_branch)
$ git commit a.txt -m 'n_branch add a'
warning: in the working copy of 'a.txt', LF will be replaced by CRLF the next time Git touches it
[n_branch 9e260bc] n_branch add a
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt

# 该文件的所属权归属于n_branch
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (n_branch)
$ ls
a.txt  readme.txt

# 其他分区将不可见a.txt
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ ls
readme.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (new_branch)
$ git switch master
Switched to branch 'master'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ ls
readme.txt

# 可以看到文件的所属权和commit密切相关
# 合并分支
# 对主分支的readme.txt修改
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ cat readme.txt
hi, master
new line 1
# 尝试将new_branch的readme合并过来
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git merge new_branch
Already up to date.
# 提示早已经更新

# 这里创建new_branch 没有再更新过, 但是master进行过新的commit, 虽然master的数据和new_branch的数据已经不一样

# 修改new_branch的readme.txt之后
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git diff new_branch
diff --git a/readme.txt b/readme.txt
index 30f6dea..f61c0bb 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1 +1,2 @@
-hello, this is new branch
+hi, master
+new line 1

# 可以合并, 并如预期看到冲突的出现
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git merge new_branch
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

9.1 冲突

pSTz201.png

由于是修改同一行的数据, 所以这种错误, 需要手动修正

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git merge new_branch
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

# 切换到new_branch, 可以看到提示代码合并中
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master|MERGING)
$ vi readme.txt

<<<<<<< HEAD
hi, master
new line 1
=======
hello, this is new branch
>>>>>>> new_branch

# 修改完数据后, 重新返回master
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master|MERGING)
$ git switch master
fatal: cannot switch branch while merging
Consider "git merge --quit" or "git worktree add".

# 虽然没有合并数据但是, 修改的内容已经出现再master上的readme上
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master|MERGING)
$ git merge --quit # 下面的合并异常和这一步有关?

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ cat readme.txt
<<<<<<< HEAD
hi, master
new line 1
no problems # 这个
hello, this is new branch # 这个
>>>>>>> new_branch

# 再次切换分支还是出现错误
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git switch new_branch
error: you need to resolve your current index first
readme.txt: needs merge

# 相当于合并数据
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git add readme.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git commit readme.txt -m 'merge new_branch'
[master 9ac57ed] merge new_branch
 1 file changed, 4 insertions(+)

# 但是需要注意-这里执行删除操作时出现的问题
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch -d new_branch
error: The branch 'new_branch' is not fully merged.
If you are sure you want to delete it, run 'git branch -D new_branch'.

# 相关的操作没有显示已经合并
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch --merged
* master
pSTz7Xd.png

合并new_branch分支到master之后的的仓库的情况

  • master是主分支

  • new_branchn_branch是基于master的两次commit的节点上分支出来的

# 新建分区测试

# master新建文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ vi test.txt

# 文件内容
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ cat test.txt
master create

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git add test.txt
warning: in the working copy of 'test.txt', LF will be replaced by CRLF the next time Git touches it

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git commit test.txt -m 'master create a test_file'
warning: in the working copy of 'test.txt', LF will be replaced by CRLF the next time Git touches it
[master 466ea24] master create a test_file
 1 file changed, 1 insertion(+)
 create mode 100644 test.txt

# 直接创建一个test_branch分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git switch -c test_branch
Switched to a new branch 'test_branch'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (test_branch)
$ vi test.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (test_branch)
$ git add test.txt
warning: in the working copy of 'test.txt', LF will be replaced by CRLF the next time Git touches it

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (test_branch)
$ git commit test.txt
warning: in the working copy of 'test.txt', LF will be replaced by CRLF the next time Git touches it
[test_branch 1834b3c] test branch change content
 1 file changed, 1 insertion(+)

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (test_branch)
$ git switch master
Switched to branch 'master'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ cat test.txt
master create

# 再test_branch的test.txt的下一行修改内容
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git diff test_branch
diff --git a/test.txt b/test.txt
index 1d3dbbd..c743a65 100644
--- a/test.txt
+++ b/test.txt
@@ -1,2 +1 @@
 master create
-test_branch change content

# 合并, 成功
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git merge test_branch
Updating 466ea24..1834b3c
Fast-forward
 test.txt | 1 +
 1 file changed, 1 insertion(+)

# 合并的状态
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch --merged
* master
  test_branch # 可以看到合并成功的分支

# 重新修改master的test.txt
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ vi test.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git add test.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git commit test.txt -m 'master change test file'
[master e9e8e78] master change test file
 1 file changed, 1 insertion(+)

# 同样修改分支的text.txt
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (test_branch)
$ vi test.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (test_branch)
$ git add test.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (test_branch)
$ git commit test.txt -m 'test_branch change two'
[test_branch a624df5] test_branch change two
 1 file changed, 1 insertion(+)

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (test_branch)
$ git switch master
Switched to branch 'master'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
nothing to commit, working tree clean

# 重新查看合并分支的变化情况
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch --merged
* master
# 修改内容后test_branch分支重新变为未合并

# 如预期发生冲突
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git merge test_branch
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Automatic merge failed; fix conflicts and then commit the result.

# 直接进入文件修改
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master|MERGING)
$ vi test.txt

# 重新合并
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master|MERGING)
$ git merge test_branch
error: Merging is not possible because you have unmerged files.
hint: Fix them up in the work tree, and then use 'git add/rm <file>'
hint: as appropriate to mark resolution and make a commit.
fatal: Exiting because of an unresolved conflict.

# 添加文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master|MERGING)
$ git add test.txt

# 注意这里的操作
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master|MERGING)
$ git commit test.txt -m 'merge with test_branch'
fatal: cannot do a partial commit during a merge.

# 合并
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master|MERGING)
$ git commit -m 'merge with test_branch'
[master 0088566] merge with test_branch

# 可以正常看到合并的分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch --merged
* master
  test_branch
pS79vh6.png
# 可以正常合并分支
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch -d test_branch
Deleted branch test_branch (was a624df5).
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git log --oneline --graph
*   0088566 (HEAD -> master) merge with test_branch
|\
| * a624df5 test_branch change two
* | e9e8e78 master change test file
|/
* 1834b3c test branch change content
* 466ea24 master create a test_file
* 9ac57ed merge new_branch
* d8a4cf6 master change readme
* 914b547 2
* c941c94 1

9.2 关于Already up-to-date

The message "Already up-to-date" means that all the changes from the branch you’re trying to merge have already been merged to the branch you’re currently on. More specifically it means that the branch you’re trying to merge is a parent of your current branch.

注意: 可以看到图形界面的好处, 可以看到各个分支的变化, 更好判断合并的异常.

9.3 关于切换分支受限

分支管理是个相对复杂的行为, 除了考虑到代码的更新习惯等, 同时需要考虑, 撤销, 恢复等操作.

pSHIgGn.png

需要注意这个问题的存在, 其逻辑是?

# 基于master当前commit创建两个分支ab, bb
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git switch -c ab
Switched to a new branch 'ab'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (ab)
$ git switch -c bb
Switched to a new branch 'bb'

# 初始状态, 这三个分支处于merged状态
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch --merged
  ab
  bb
* master

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git branch
  ab
  bb
* master
  n_branch
  new_branch
# 分支创建文件ab.txt
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (ab)
$ vim ab.txt

# 添加到缓存区
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (ab)
$ git add ab.txt
warning: in the working copy of 'ab.txt', LF will be replaced by CRLF the next time Git touches it

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (ab)
$ git status
On branch ab
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   ab.txt

# 切换到bb
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (ab)
$ git switch bb
Switched to branch 'bb'
A       ab.txt

# 修改ab.txt
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (bb)
$ vim ab.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (bb)
$ git status
On branch bb
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   ab.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   ab.txt

# 切换到master
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (bb)
$ git switch master
Switched to branch 'master'
A       ab.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   ab.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   ab.txt

# 看到文件的两次修改
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ cat ab.txt
ab create ab
bb change ab

# -a, 强制提交所有的track file(不需要add)
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git commit -a -m 'ab-c-bb-master-commit'
warning: in the working copy of 'ab.txt', LF will be replaced by CRLF the next time Git touches it
[master fa07344] ab-c-bb-master-commit
 1 file changed, 2 insertions(+)
 create mode 100644 ab.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ ls
ab.txt  readme.txt  test.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
nothing to commit, working tree clean

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git switch ab
Switched to branch 'ab'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (ab)
$ git status
On branch ab
nothing to commit, working tree clean

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (ab)
$ ls
readme.txt  test.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (ab)
$ git switch bb
Switched to branch 'bb'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (bb)
$ git status
On branch bb
nothing to commit, working tree clean

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (bb)
$ ls
readme.txt  test.txt

上面的操作并没有触发Please commit your changes or stash them before you switch branches.这个提示, 在一连串的操作中.

commit之后, 相关的修改在哪个分支commit的, 最终相关的内容归属于当前分支, 如同新建的文件的归属.

这种操作有点奇怪, 为什么git没有全部阻止某个分支在对文件修改没有commit, 却可以自由切换到其他的分支, 而是在部分情形才会阻止切换.

# 例如这里创建的
# ab
@Lian ➜ ~\..\git test-a ( base 3.9.12) git(ab)  git add ab.txt
warning: in the working copy of 'ab.txt', LF will be replaced by CRLF the next time Git touches it
@Lian ➜ ~\..\git test-a ( base 3.9.12) git(ab)  git switch bb
Switched to branch 'bb'
A       ab.txt

# 在bb上可以修改, 可以commit, 可以删除
@Lian ➜ ~\..\git test-a ( base 3.9.12) git(bb)  git status
On branch bb
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   ab.txt
# 复刻出现阻止的情况
@Lian ➜ ~\Desktop ( base 3.9.12)  cd test-a
@Lian ➜ ~\..\test-a ( base 3.9.12)  git init
Initialized empty Git repository in C:/Users/Lian/Desktop/test-a/.git/

# 在master创建file2.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  echo change3 > file2.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git add .
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git commit -am "init"
[master (root-commit) 242d6a3] init
 1 file changed, 1 insertion(+)
 create mode 100644 file2.txt

# 创建分支
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git switch -c cc
Switched to a new branch 'cc'
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  cat file2.txt
change3
# 修改这个file2.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  echo new_ssh > file2.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git add .
# 添加commit
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git commit -am "update"
[cc 30a2f00] update
 1 file changed, 1 insertion(+), 1 deletion(-)
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc) 103ms git status
On branch cc
nothing to commit, working tree clean
# 在次修改(关键)
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  echo bbc > file2.txt
# 切换出现异常
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git switch master
error: Your local changes to the following files would be overwritten by checkout:
        file2.txt
Please commit your changes or stash them before you switch branches.
Aborting

# 撤销掉这个更新
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git restore file2.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git status
On branch cc
nothing to commit, working tree clean
# 新建一个文件bb-b.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  vim bb-b.txt
# 切换master正常
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc) 6.808s git switch master
Switched to branch 'master'
# 回到cc分支
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master)  git switch cc
Switched to branch 'cc'
# 添加对bb-b.txt的commit
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git add .
warning: in the working copy of 'bb-b.txt', LF will be replaced by CRLF the next time Git touches it
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git commit -am "update2"
[cc 4fa1860] update2
 1 file changed, 1 insertion(+)
 create mode 100644 bb-b.txt
# 再次修改这个bb-b.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  vim bb-b.txt
# 再次切换, 就促发这个问题
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc) 12.95s git switch master
error: Your local changes to the following files would be overwritten by checkout:
        bb-b.txt
Please commit your changes or stash them before you switch branches.
Aborting

可以得出这个错误的逻辑, 当分支的文件已经提交过commit, 修改这些已经被标记的文件, 而且不进行commit就执行切换分支的操作将导致这个错误的出现. 实际上git的逻辑是文件一定需要和版本库产生联系了, git才执行真正意义上对文件进行各种监控的行为. 否则git只能通过文件的状态来提醒用户, 切换分支需要注意, git认为尚未和版本库产生联系, 这些文件还是相对自由的, 分支尚不具备约束这些文件的能力.


十. 撤销/恢复

相关操作针对的情景非常多, 审慎操作.

这里的注意, 相当多的文档介绍, 由于理解表达以及翻译上的差异, 没有区分开二者的区别.

撤销, 其操作上强调的是, 撤销某种行为. 例如add之后, 撤销, 将是就这个add执行的操作撤销掉, 就是删掉add添加到缓存区的操作, 对于工作区的文件无影响.

恢复, 这是两个步骤的组合, 撤销之前的操作(如修改文档, 删除文件); 恢复, 即将文件恢复到一定的状态阶段(也可以说成是撤销某某操作). 简而言之, 在执行恢复的操作, 也带有撤销的行为, 但是和撤销不一样的是, 恢复通常带有文件的变迁(文件丢失, 可以找回).

  • reset, reset current HEAD to the specified state
  • restore, restore working tree files
  • checkout, restore working tree files
  • revert, revert some existing commits
  • git-revert is about making a new commit that reverts the changes made by other commits.
  • git-restore is about restoring files in the working tree from either the index or another commit.
    This command does not update your branch.
    The command can also be used to restore files in the index from another commit.
  • git-reset is about updating your branch, moving the tip in order to add or remove commits from the branch. This operation changes the commit history.
    git reset can also be used to restore the index, overlapping with git restore.
Command Scope Common use cases
git reset Commit-level Discard commits in a private branch or throw away uncommitted changes
git reset File-level Unstage a file
git checkout Commit-level Switch between branches or inspect old snapshots
git checkout File-level Discard changes in the working directory
git revert Commit-level Undo commits in a public branch
git revert File-level (N/A)

按照需要恢复的内容:

  • 恢复文件
  • 恢复文件夹(下的文件)
  • 恢复分支

按照恢复的影响范围:

  • 覆盖式恢复(如整体全部恢复)
  • 阶段覆盖(如覆盖工作区, 但是不影响缓存区)
  • 指定范围的恢复(如指定恢复某个问及那)

按照从那里恢复:

  • 缓存区

    需要注意缓存区的操作, 如果文件还没有提交过, 撤销(reset/restore)操作会导致缓存区的文件丢失. 假如你本地磁盘进行修改或者删除操作, 缓存区的文件已经不存在, 之后的操作如恢复将不复存在.

  • 版本仓库

由于很多文档关于恢复这一点上的介绍相当模糊, 如恢复, 到底是恢复哪个阶段? 恢复哪些内容? 对哪些文件产生影响?(这涉及到是否会丢失文件)等等这些问题大多含糊不清.

10.1 revert

revert相对而言, 是个非常清晰的function, 没有一堆乱七八糟的解析

Revert some existing commits

撤销现有的commit

# 以此为例
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git log
commit 429695f1950c21be7b9696f29b337e1227a2dc61 (HEAD -> master)
Author: Lian <Lian_Hwang@126.com>
Date:   Wed Feb 15 14:38:08 2023 +0800

    4

commit 796c2252c24d9550074f44905b67b8102c560aa5
Author: Lian <Lian_Hwang@126.com>
Date:   Wed Feb 15 14:37:36 2023 +0800

    3

commit 8a2b4011f0aa4851adaa6d1dbc7516cd84582d09
Author: Lian <Lian_Hwang@126.com>
Date:   Wed Feb 15 14:37:19 2023 +0800

    2

commit 4c4b583f95d95db524215cd41720a6cc2597f0c7
Author: Lian <Lian_Hwang@126.com>
Date:   Wed Feb 15 14:36:59 2023 +0800

    1

# revert
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git revert head
[master 2747218] Revert "test_r_head"
 1 file changed, 1 insertion(+), 2 deletions(-)

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git log --oneline
2747218 (HEAD -> master) Revert "test_r_head"
429695f 4
796c225 3
8a2b401 2
4c4b583 1

# git revert head 退回当前节点的上一个节点
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git diff 796c225 2747218

# 添加一个a.txt untract的文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git revert 796c225
[master 5dcf325] Revert "test-3"
 1 file changed, 1 insertion(+), 2 deletions(-)

# 指定节点 revert 返回, a文件不受影响

# 这里, 将a.txt添加缓存区
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git add a.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   a.txt

# 将会出现错误, 提示返回的内容将执行覆盖操作
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git revert 429695f
error: your local changes would be overwritten by revert.
hint: commit your changes or stash them to proceed.
fatal: revert failed

不对当前的commit产生影响, 开一个基于指定的commit新的commit.

从文档和测试来看, revert应该是只作用于commit, 而不具有针对单个文件的操作. 假如当前的工作区存在尚未commit的文件, 会停止, 这一点很好, 不担心操作导致的意外.

10.2 checkout

注意工作区文件的丢失.

restore working tree files

Updates files in the working tree to match the version in the index or the specified tree. If no pathspec was given, git checkout will also update HEAD to set the specified branch as the current branch.

注意这里的作用范围, 将可能影响到当前分支, 也可能影响到其他的分支.

pathspec:

From the Git Glossary:

[A pathspec is a pattern] used to limit paths in Git commands.

Pathspecs are used on the command line of "git ls-files", "git ls-tree", "git add", "git grep", "git diff", "git checkout", and many other commands to limit the scope of operations to some subset of the tree or worktree.

As an example, the command git add :/**.ts will recursively add to the index all of the files that end with .ts starting at the root of the repository (respecting the various ways of ignoring files).

简而言之, 指定(路径)的范围

[github - What's a in the git command? - Stack Overflow](https://stackoverflow.com/questions/27711924/whats-a-pathspec-in-the-git-command#:~:text=[A pathspec is a pattern] used to limit,to some subset of the tree or worktree.)

# 先将上面的revert产生的两个commit删除掉

# 由于有文件还没commit
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git rebase 429695f
error: cannot rebase: Your index contains uncommitted changes.
error: Please commit or stash them.

`429695f`, commit_id, 可以操作之前的commit

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   a.txt

# 撤销掉这个缓存区的文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git restore --staged a.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        a.txt

nothing added to commit but untracked files present (use "git add" to track)

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git log --oneline
5dcf325 (HEAD -> master) Revert "test-3"
2747218 Revert "test_r_head"
429695f 4
796c225 3
8a2b401 2
4c4b583 1

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git rebase -i 429695f
Successfully rebased and updated refs/heads/master.
# 弹出的窗口, 删除掉429695f之前的两个revert的节点, 改pick => drop即可
# 当前head指向429695f => 恢复到之前的提交状态

上面的内容和下面无关, 之前的思路中断了.

# 当存在有文件尚未commit, checkout被阻止
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master) 128ms git checkout 6361c5a4061f3410c7eab79f21dd5a3493902de1
error: Your local changes to the following files would be overwritten by checkout:
        a.txt
Please commit your changes or stash them before you switch branches.
Aborting

# 提交commit后执行
@Lian ➜ ~\..\test-a ( base 3.9.12) git(master) 118ms git checkout 6361c5a4061f3410c7eab79f21dd5a3493902de1
Note: switching to '6361c5a4061f3410c7eab79f21dd5a3493902de1'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 6361c5a add a0
@Lian ➜ ~\..\test-a ( base 3.9.12) git(6361c5a)  cat a.txt
a-0

# 缓存区的文件
# 注意  git(6361c5a) 这里的变化吗不再显示分区的位置, 而是显示节点的id
@Lian ➜ ~\..\test-a ( base 3.9.12) git(6361c5a)  git ls-files -s
100644 5b236f9e8a494f33ea069dd6c9a4bf67daffec00 0       a.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(6361c5a)  git cat-file -p 5b23
a-0
pSbE9u6.png

注意图上的HEAD的位置

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

简而言之, 进入游离的指针状态

@Lian ➜ ~\..\test-a ( base 3.9.12) git(b67d952)  git switch master
error: Your local changes to the following files would be overwritten by checkout:
        a.txt
Please commit your changes or stash them before you switch branches.
Aborting
@Lian ➜ ~\..\test-a ( base 3.9.12) git(b67d952)  git status
HEAD detached from 6361c5a
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   a.txt

# 查看指针的位置
@Lian ➜ ~\..\test-a ( base 3.9.12) git(b67d952)  cat .git/HEAD
b67d9525e5dce122439fd2a8dc0795dbbe9140d3

# 切换到新建的分支
@Lian ➜ ~\..\test-a ( base 3.9.12) git(b67d952) 131ms git switch -c detached-branch
Switched to a new branch 'detached-branch'
@Lian ➜ ~\..\test-a ( base 3.9.12) git(detached-branch)  ls

    Directory: C:\Users\Lian\Desktop\test-a

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           2/16/2023    18:14              3 a.txt

@Lian ➜ ~\..\test-a ( base 3.9.12) git(detached-branch)  cat a.txt
a-0
@Lian ➜ ~\..\test-a ( base 3.9.12) git(detached-branch)  git status
On branch detached-branch
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   a.txt

@Lian ➜ ~\..\test-a ( base 3.9.12) git(detached-branch)  git ls-files -s
100644 5b236f9e8a494f33ea069dd6c9a4bf67daffec00 0       a.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(detached-branch)  git cat-file -p 5b236
a-0

@Lian ➜ ~\..\test-a ( base 3.9.12) git(detached-branch)  git commit -am "detech"
[detached-branch 2355d51] detech
 1 file changed, 1 insertion(+), 1 deletion(-)
@Lian ➜ ~\..\test-a ( base 3.9.12) git(detached-branch)  git branch -v
  cc              1b385f1 add a3
* detached-branch 2355d51 detech
  master          fe904af add a4
pSbZB9J.png
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git log --oneline
1b385f1 (HEAD -> cc) add a3
03986f8 add a1
6361c5a add a0
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc) 129ms git checkout a.txt 03986f8
error: pathspec '03986f8' did not match any file(s) known to git
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git checkout 03986f8 -- a.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  cat a.txt
a-1
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git status
On branch cc
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   a.txt

10.3 restore

Restore working tree files

# 文件首次添加到缓存区
(use "git restore --staged <file>..." to unstage)

# 文件已经添加到缓存区
# 再次执行工作区的修改, 这个命令可以重置工作区执行的修改
(use "git restore <file>..." to discard changes in working directory)
        modified:   bb-b.txt
# 沿用临时存储的哪个示例

@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git ls-files
bb-b.txt
file2.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  get-content bb-b.txt
bb-b, abcn`

# 从缓存区删除
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git rm --cached bb-b.txt
rm 'bb-b.txt'

@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git status
On branch cc
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    bb-b.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        bb-b.txt

@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git restore --staged bb-b.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git status
On branch cc
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   bb-b.txt

@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git add bb-b.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git commit -am "add bb-b"
[cc 7ee0ab8] add bb-b
 1 file changed, 1 insertion(+), 1 deletion(-)
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  echo bbc-ccd > bb-b.txt

no changes added to commit (use "git add" and/or "git commit -a")
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  get-content bb-b.txt
bb-b, abcn`

@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  cat bb-b.txt
bbc-ccd
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  git restore bb-b.txt
@Lian ➜ ~\..\test-a ( base 3.9.12) git(cc)  cat bb-b.txt
bb-b, abcn`
# 将添加到缓存区的a.txt 撤销掉
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git restore --staged a.txt

# 添加文件夹到缓存区
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git add ./test

# 然后删掉文件夹
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   test/ab.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    test/ab.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        a.txt

# 这里就看到和restore的差异
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git checkout ./test # checkout恢复文件, 但是不影响缓存区的文件
Updated 1 path from the index

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   test/ab.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        a.txt

# `restore`, 撤销缓存区的文件, 但是假如文件添加缓存区后,
# 工作区的文件被删除, 这个操作是不会将删除的文件恢复的.
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git restore --staged ./test
# 将a.txt commit
# 修改a.txt # 未提交缓存区
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   a.txt

# 文件将被恢复到commit时的状态
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git restore a.txt

# 删除掉缓存文件a
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git rm --cached a.txt
rm 'a.txt'

# 删除掉这个文件之后, 将无法恢复
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git restore a.txt
error: pathspec 'a.txt' did not match any file(s) known to git

# 这里没办法恢复
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git checkout a.txt
error: pathspec 'a.txt' did not match any file(s) known to git

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git log --oneline
433a2f1 (HEAD -> master) change a 1
429695f 4
796c225 3
8a2b401 2
4c4b583 1

# 手动指定从哪个
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git checkout 433a2f1 a.txt
Updated 1 path from 03710bd

# 缓存区的文件也被恢复
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git ls-files
New Text Document.txt
a.txt

10.4 reset

Reset current HEAD to the specified state

  • soft: 不改变工作区和缓存区只移动 HEAD 到指定 commit.
  • mixed: 只改变缓存区不改变工作区.这是默认参数通常用于撤销git add.
  • hard:改变工作区和暂存区到指定 commit.该参数等同于重置可能会引起数据损失.git reset --hard等同于 git reset --hard HEAD

-soft

Does not touch the index file or the working tree at all (but resets the head to <commit>, just like all modes do). This leaves all your changed files "Changes to be committed", as git status would put it.

--mixed

Resets the index but not the working tree (i.e., the changed files are preserved but not marked for commit) and reports what has not been updated. This is the default action.

If -N is specified, removed paths are marked as intent-to-add (see git-add(1)).

--hard

Resets the index and working tree. Any changes to tracked files in the working tree since <commit> are discarded. Any untracked files or directories in the way of writing any tracked files are simply deleted.

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git add b.txt
warning: in the working copy of 'b.txt', LF will be replaced by CRLF the next time Git touches it

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   b.txt

# 这一步的操作也相当于撤销掉add操作
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git reset head b.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        b.txt

nothing added to commit but untracked files present (use "git add" to track)

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ cat b.txt
b

# 添加 => 删掉本地文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   b.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    b.txt

# 没有恢复文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git reset head b.txt

# 缓存区的文件就会丢失
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git ls-files
readme.txt
test.txt

# 缓存区已经不存在文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
nothing to commit, working tree clean
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   b.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   b.txt

# b修改(工作区)不受影响
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git reset head b.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        b.txt

nothing added to commit but untracked files present (use "git add" to track)

# 这里的操作就即可以说撤销b的修改, 也可以说恢复b的文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git checkout b.txt
Updated 1 path from the index
# 使用缓存区的文件覆盖掉工作区的b文件

# 这里和restore的操作不同, checkout没有撤销add操作
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   b.txt
# 没有删除缓存区的文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git_test (master)
$ git ls-files
b.txt
readme.txt
test.txt
# 延续上面的checkout恢复删除的文件
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git reset -- a.txt --mixed
Unstaged changes after reset:
D       a.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    a.txt

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

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ ls
'New Text Document.txt'

# 文件被恢复至缓存区, 本地磁盘不受影响
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git ls-files
New Text Document.txt
a.txt

# soft => 数据还是恢复到缓存区
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git reset -- a.txt --soft
Unstaged changes after reset:
D       a.txt

# 这里D的delete的标记

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ ls
'New Text Document.txt'

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git ls-files
New Text Document.txt
a.txt

# 再文件被删除之后, 文件的恢复只能到缓存区
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git reset -- a.txt --hard
Unstaged changes after reset:
D       a.txt

Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/git beta (master)
$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    a.txt

no changes added to commit (use "git add" and/or "git commit -a")
# 延续checkout部分的内容

# soft的作用, 移动指针
@Lian ➜ ~\..\test-a ( base 3.9.12) git(6361c5a)  git reset --soft b67d9525e5dce122439fd2a8dc0795dbbe9140d3
# 只是指针的移动, 对于工作区和缓存区无影响
@Lian ➜ ~\..\test-a ( base 3.9.12) git(b67d952)  cat a.txt
a-0
@Lian ➜ ~\..\test-a ( base 3.9.12) git(b67d952)  git cat-file -p 5b23
a-0
pSbEJ8s.png

十一. 远程仓库

pSXOKF1.png

gitee/github在多数的操作上应该时相同的, 差异的话应该不会很大.

关于验证方式, 有非常多种类型.

这里, gitee我使用的是账号密码的方式(上面安装``git时安装有账号/密码管理器(credential.helper)); github`使用的是机器授权码.

11.1 初始化

# 将项目克隆到本地
git clone https....git

... 修改内容后

# 推送
git push origin master
# 安装了凭证管理会弹窗提示输入账号密码
# 加 参数: -f, 将覆盖掉可能存在冲突的readme.md文件

11.2 pull和fetch的差异

pSOvk36.png

上图中的merge的产生就是pull执行, updatemaster, 就是fetch

  • pull, 执行后, 马上进入合并阶段.

    @Lian ➜ ~\..\test_push ( base 3.9.12) git(master) 2.422s git status
    On branch master
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
            b.txt
    
    nothing added to commit but untracked files present (use "git add" to track)
    

    合并后, 本地的未追踪的文件, 并无丢失.

  • fetch, 则是在本地仓库, 不会影响当前的工作

    # fetch远程仓库
    Lian@DESKTOP-F6VO5U4 MINGW64 ~/test-project (master)
    $ git fetch origin
    remote: Enumerating objects: 5, done.
    remote: Counting objects: 100% (5/5), done.
    remote: Compressing objects: 100% (3/3), done.
    remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
    Unpacking objects: 100% (3/3), 1012 bytes | 101.00 KiB/s, done.
    From https://gitee.com/lian_hwang/test-project
       6687f7f..019a9f8  master     -> origin/master
    
    @Lian ➜ ~\..\test_push ( base 3.9.12) git(master)  git reset --hard origin/master
    HEAD is now at 0ec7294 update main.py.
    @Lian ➜ ~\..\test_push ( base 3.9.12) git(master)  cat main.py
    # 1
    # 2
    def test_main():
        print('change content')
    
    def add(a, b):
        print(a + b)
    @Lian ➜ ~\..\test_push ( base 3.9.12) git(master)  git status
    On branch master
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
            b.txt
    
    nothing added to commit but untracked files present (use "git add" to track)
    
    pSOvTKO.png

    并未如此所言, 出现丢失的情形.

    pSOvLad.png

11.3 本地修改

# 查看远程仓库和本地文件的差异
Lian@DESKTOP-F6VO5U4 MINGW64 ~/test-project (master)
$ git diff origin/master
diff --git a/main.py b/main.py
index dc37942..522cd30 100644
--- a/main.py
+++ b/main.py
@@ -1,4 +1,2 @@
 # 1
 # 2
-def test_main():
-    print('change content')    # 这里是在远端修改的内容
\ No newline at end of file

# 将合并远程和本地仓库
Lian@DESKTOP-F6VO5U4 MINGW64 ~/test-project (master)
$ git pull
Updating 6687f7f..019a9f8
Fast-forward
 main.py | 2 ++
 1 file changed, 2 insertions(+)

11.4 推送

创建仓库时, 假如创建以下文件, 而本地不存在.

pSOXexO.png
# 出现不一致的提示
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/markdown_project (main)
$ git push -u origin main
To https://github.com/Kyouichirou/markdown_project.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://github.com/Kyouichirou/markdown_project.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Lian@DESKTOP-F6VO5U4 MINGW64 ~/Desktop/markdown_project (main)
$ git pull origin main
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 1.19 KiB | 121.00 KiB/s, done.
From https://github.com/Kyouichirou/markdown_project
 * branch            main       -> FETCH_HEAD
 * [new branch]      main       -> origin/main

# 出现合并错误的提示
fatal: refusing to merge unrelated histories
pSOXYz8.png

这个内容, 在``git`上并看不到.

# git新增的特性
git pull origin main --allow-unrelated-histories
# 即可
git push -u origin main

十二. 常用命令参数

12.1 add

git-add - Add file contents to the index

添加文件到索引(缓存区)

git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
          [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] [--sparse]
          [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
          [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
          [--] [<pathspec>…]

 # n, 只是测试, 不是实际操作, 只是为了测试, 到底哪些文件会被添加(忽略)

12.2 reset

git-reset - Reset current HEAD to the specified state

修改当前指针到特定的阶段

git reset [-q] [<tree-ish>] [--] <pathspec>…
git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>…]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
DEPRECATED: git reset [-q] [--stdin [-z]] [<tree-ish>]

注意--hard参数的使用, 这将可能导致工作区的内容丢失.

12.3 commit

git-commit - Record changes to the repository

提交修改到仓库

git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]
           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>)]
           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
           [--date=<date>] [--cleanup=<mode>] [--[no-]status]
           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
           [(--trailer <token>[(=|:)<value>])…] [-S[<keyid>]]
           [--] [<pathspec>…]

# amend, 修改提交信息(最后一次)
# m, 执行commit的同时, 同时提交commit的内容(否则打开默认编辑器进行操作)

--no-abbrev-commit
Show the full 40-byte hexadecimal commit object name. This negates --abbrev-commit, either explicit or implied by other options such as "--oneline". It also overrides the log.abbrevCommit variable.
# 可以更方便获取commit的id
--oneline
This is a shorthand for "--pretty=oneline --abbrev-commit" used together.

12.4 checkout

git-checkout - Switch branches or restore working tree files

切换分支/重置工作区文件

git checkout [-q] [-f] [-m] [<branch>]
git checkout [-q] [-f] [-m] --detach [<branch>]
git checkout [-q] [-f] [-m] [--detach] <commit>
git checkout [-q] [-f] [-m] [[-b|-B|--orphan] <new-branch>] [<start-point>]
git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>…
git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]
git checkout (-p|--patch) [<tree-ish>] [--] [<pathspec>…]

12.5 pull

git-pull - Fetch from and integrate with another repository or a local branch

合并其他的仓库或者本地的分支

git pull [<options>] [<repository> [<refspec>…]]

12.6 diff

git-diff - Show changes between commits, commit and working tree, etc

查看仓库区和工作区的内容的变化

git diff [<options>] [<commit>] [--] [<path>…]
git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>…]
git diff [<options>] [--merge-base] <commit> [<commit>…] <commit> [--] [<path>…]
git diff [<options>] <commit>…<commit> [--] [<path>…]
git diff [<options>] <blob> <blob>
git diff [<options>] --no-index [--] <path> <path>

12.7 merge

git-merge - Join two or more development histories together

合并分支

git merge [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
        [--no-verify] [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
        [--[no-]allow-unrelated-histories]
        [--[no-]rerere-autoupdate] [-m <msg>] [-F <file>]
        [--into-name <branch>] [<commit>…]
git merge (--continue | --abort | --quit)

12.8 branch

git-branch - List, create, or delete branches

展示, 创建, 删除 分支

git branch [--color[=<when>] | --no-color] [--show-current]
        [-v [--abbrev=<n> | --no-abbrev]]
        [--column[=<options>] | --no-column] [--sort=<key>]
        [--merged [<commit>]] [--no-merged [<commit>]]
        [--contains [<commit>]] [--no-contains [<commit>]]
        [--points-at <object>] [--format=<format>]
        [(-r | --remotes) | (-a | --all)]
        [--list] [<pattern>…]
git branch [--track[=(direct|inherit)] | --no-track] [-f]
        [--recurse-submodules] <branchname> [<start-point>]
git branch (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
git branch --unset-upstream [<branchname>]
git branch (-m | -M) [<oldbranch>] <newbranch>
git branch (-c | -C) [<oldbranch>] <newbranch>
git branch (-d | -D) [-r] <branchname>…
git branch --edit-description [<branchname>]

12.9 rebase

git-rebase - Reapply commits on top of another base tip

重置commit

git rebase [-i | --interactive] [<options>] [--exec <cmd>]
        [--onto <newbase> | --keep-base] [<upstream> [<branch>]]
git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
git rebase (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)

注意rebase`可能导致的commit`丢失.

12.10 restore

git-restore - Restore working tree files

重置工作区

git restore [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>…​
git restore [<options>] [--source=<tree>] [--staged] [--worktree] --pathspec-from-file=<file> [--pathspec-file-nul]
git restore (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>…​]

12.11 push

git-push - Update remote refs along with associated objects

推送到远程仓库

git push [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
           [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
           [-u | --set-upstream] [-o <string> | --push-option=<string>]
           [--[no-]signed|--signed=(true|false|if-asked)]
           [--force-with-lease[=<refname>[:<expect>]] [--force-if-includes]]
           [--no-verify] [<repository> [<refspec>…]]

12.12 switch

git-switch - Switch branches

切换分支

git switch [<options>] [--no-guess] <branch>
git switch [<options>] --detach [<start-point>]
git switch [<options>] (-c|-C) <new-branch> [<start-point>]
git switch [<options>] --orphan <new-branch>

-c 创建分支, 同时转移到新的分支

12.13 tag

git-tag - Create, list, delete or verify a tag object signed with GPG

标签

git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]
        <tagname> [<commit> | <object>]
git tag -d <tagname>…
git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
        [--points-at <object>] [--column[=<options>] | --no-column]
        [--create-reflog] [--sort=<key>] [--format=<format>]
        [--merged <commit>] [--no-merged <commit>] [<pattern>…]
git tag -v [--format=<format>] <tagname>…

十三. git flow

flow
@Lian ➜ ~\Desktop ( base 3.9.12)  git flow help
usage: git flow <subcommand>

Available subcommands are:
   init      Initialize a new git repo with support for the branching model.
   feature   Manage your feature branches.
   bugfix    Manage your bugfix branches.
   release   Manage your release branches.
   hotfix    Manage your hotfix branches.
   support   Manage your support branches.
   version   Shows version information.
   config    Manage your git-flow configuration.
   log       Show log deviating from base branch.

sourcetree

pSXWQu6.png

GitKraken

pSXWavt.png

十四. 名词解析

14.1 head

14.1.1 HEAD

HEAD` 指向当前所在分支提交至仓库的最新一次的 `commit
# 使用最新一次提交重制暂存区
git reset HEAD -- filename

# 使用最新一次提交重制暂存区和工作区
git reset --hard HEAD

# 将 commit log 回滚一次 暂存区和工作区代码不变
git reset --soft HEAD~1

14.1.2 HEAD~{n}

~` 是用来在`当前提交路径`上回溯的修饰符
`HEAD~{n}` 表示当前所在的提交路径上的前 `n` 个提交(n >= 0):
`HEAD` = `HEAD~0`~
`HEAD~` = `HEAD~1`
`HEAD~~` = `HEAD~2`
`HEAD{n个~}` = `HEAD~n

14.1.3 HEAD^n

^ 是用来切换父级提交路径的修饰符.当我们始终在一个分支比如 dev 开发/提交代码时每个 commit 都只会有一个父级提交就是上一次提交但当并行多个分支开发feat1, feat2, feat3完成后 merge feat1 feat2 feat3dev 分支后此次的 merge commit 就会有多个父级提交.

image.png
如上提交图所示当前的 HEAD 节点有 3 个父级:feat1(归并到主线)feat2``feat3

tips: 父级 一词本身就代表回溯了 1

HEAD 的 第一个父级

# 第一个父级提交 即 feat1 的最近第1次的提交
λ git show HEAD^
feat1 3 foo_feat1

# 第一个父级提交的上1次提交 即 feat1 的最近第2次的提交
λ git show HEAD^~1 / git show HEAD^^
feat1 2 foo_feat1

# 第一个父级提交的上2次提交 即 feat1 的最近第3次的提交
λ git show HEAD^~2 / git show HEAD^^^
feat1 1 foo_feat1

HEAD 的 第二个父级

# 第二个父级提交 即 feat2 的最近第1次的提交
λ git show HEAD^2
feat2 2 foo_feat2

# 第二个父级提交的上1次提交 即 feat2 的最近第2次的提交
λ git show HEAD^2~1 / git show HEAD^2^
feat2 1 foo_feat2

# 第二个父级提交的上2次提交 即 feat2 的最近第3次的提交
λ git show HEAD^2~2 / git show HEAD^2^^
feat2 add foo_feat2

HEAD 的 第三个父级

# 第三个父级提交 即 feat3 的最近第1次的提交
λ git show HEAD^3
feat3 2 foo_feat3

# 第三个父级提交的上1次提交 即 feat3 的最近第2次的提交
λ git show HEAD^3~1 / git show HEAD^3^
feat3 1 foo_feat3

# 第三个父级提交的上2次提交 feat3 的最近第3次的提交回归到了主线上
λ git show HEAD^3~2 / git show HEAD^3^^
master foo 2

14.1.4 示例

# 当前提交
HEAD = HEAD~0 = HEAD^0

# 主线回溯
HEAD~1 = HEAD^ 主线的上一次提交
HEAD~2 = HEAD^^ 主线的上二次提交
HEAD~3 = HEAD^^^ 主线的上三次提交

# 如果某个节点有其他分支并入
HEAD^1 主线提交(第一个父提交)
HEAD^2 切换到了第2个并入的分支并得到最近一次的提交
HEAD^2~3 切换到了第2个并入的分支并得到最近第 4 次的提交
HEAD^3~2 切换到了第3个并入的分支并得到最近第 3 次的提交

# ^{n} 和 ^ 重复 n 次的区别
HEAD~1 = HEAD^
HEAD~2 = HEAD^^
HEAD~3 = HEAD^^^
# 切换父级
HEAD^1~3 = HEAD~4
HEAD^2~3 = HEAD^2^^^
HEAD^3~3 = HEAD^3^^^

十五. commit信息规范

非标准的要求, 只是为了方便阅读和自动化处理这些commit而提出的约束规范.

  • https://github.com/conventional-changelog/standard-version
  • 约定式提交
  • conventional-changelog-metahub
  • [Git Commit message 编写指南 | Gitee 产品文档](https://help.gitee.com/enterprise/code-manage/Git 知识大全/Git Commit message 编写指南)

十六. 图形管理工具

这里使用的是GitKraken

pSTZOeS.png

这个软件之前是免费的针对个人, 但是现在已经收费, 包括之前不少人用的6.5.1这个版本(官方已经不支持这个版本的下载), 同样也只是提供7天的试用期.

付费版本和免费版本的最大差异是, 免费版本不支持PrivateGitHub仓库.

这里安装的是6.5.1, 破解的方式很简单(6.5.1这个版本已经不支持安装即用)

  • 先在hosts文件添加0.0.0.0 release.gitkraken.com(保证程序无法更新)
  • 安装好程序, 这个程序在菜单栏上的快捷方式是先执行update.exe之后再执行主程序, 所以顺便再防火墙将这个update.exe禁止出站.
  • 打开界面, 会出现强制要求登录的弹窗, 这里使用的GitHub授权登录的方式(假如出现error, 尝试多次), 退出程序.
  • hosts文件添加0.0.0.0 api.gitkraken.com.(注意不能提前禁用, 这个前面的授权需要用到).
  • 重新打开程序, 即可以看到7天的试用授权提示消失, 底部显示的也将是pro版本.

这里仅作为测试试用, 开发不易, 请支持开发者, 请勿用于商业用途.

注意挂代理(需要在代理中排除掉上述两个host, 代理绕过本地的hosts, 将导致pro变回free, 但是不影响私有仓库的使用.

pSTd9pt.png

相比于bash终端, 图形界面更为直观的看到各个分支的数据变化情况.

pS7sz0P.png

Git & pycharm

十七. 速查表

pSII8u4.png

详细的思维导图:

git-思维导图