跳转至

Git简介

推荐使用:

基本概念及操作

git的备份节点(commit)在底层构成一张DAG,产生的新commit会指向执行命令前所在的commit(例如:merge节点有多个前继,指向的是merge前的commit)因此,每个仓库仅有一个汇点——inital commit。

分支、HEAD、Tag仅仅是指向它们的指针,指针变动(甚至删除)并不会改变它们指向的节点,而只会导致你无法通过上述的单向指向关系找到原来的节点,即产生了unreachable的节点(类似memory leak)这些unreachable节点及其相关文件会定期被git清理掉,也就是GC。

备份与还原

  • add告诉git能管理哪些文件、记录哪些文件的变动(create/delete/modify)
  • add会将修改先同步暂存区,暂存区可以理解为一个准备提交的commit
  • commit就是确认暂存区的修改,命名并创建备份点
  • push修改远程,改动历史时push要加-f
  • git log --oneline查看commit链
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 更新已经追踪的文件,未追踪要add
# git add .
git commit -a
git push

# 前跳一次 reset默认的mixed就很好用
git reset HEAD^

# reset错了返回未来 用reflog查HEAD的移动记录
git reflog
git reset <commit>

# 仅恢复某个文件/目录
git checkout <commit> <path>

checkout

当使用git checkout命令时,Git会切换到指定的分支,但如果后面接的是文件名或路径,Git则不会切换分支,而是把文件从.git目录中复制一份覆盖到当前的工作目录。与reset --mixed相反,它不会修改暂存区,只会修改工作区。

1
2
3
4
5
6
7
# 移动HEAD
git checkout test
git checkout <commit>

# 不移动HEAD
git checkout . # 从暂存区覆盖工作区
git checkout HEAD~2 <path> # 拿两个版本前的文件覆盖

切换新任务

暂存区并不能存下你手边的任务!它不固定在原来所在的commit上。checkout切换前请任选:

  1. 直接commit
  2. 用stash

合并分支

分支合并的两种方式,所有合并操作都是别的commit合并入HEAD,且都不会对别的分支产生影响。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 1.merge多个commit
# 注:pull底层就是把origin/x合并到x
git checkout main
git merge test new
# 解决冲突
git commit -a

# 2.rebase 把从lca开始的修改叠加到某一个commit上
git rebase test
# 解决冲突
git add .
git rebase --continue

其他

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# author email
git config --global user.name "hyj0824"
git config --global user.email "2417179808@qq.com"

# commit图
git log --oneline --graph

# rm + git add:删除文件并stage
git rm *

# 取消文件追踪不删除文件 make it untracked
git rm --cached *
git restore --staged *

# reset HEAD^1后再做一次commit,也就是修改最新commit
git commit --amend -m "xxx"
git commit --amend --no-edit # 不修改message
git commit --allow-empty -m "a" # 空commit

# 记录变动并合并到最新commit
git commit -a --amend --no-edit

# 删除远端分支
git push origin :new

# cherry-pick 仅拉取特定的commit内容,而不是从lca开始的全部commit
git cherry-pick "sha1" "sha2"

# 立刻 gc
git fsck --unreachable
git reflog expire --all --expire=now
git gc --prune=now

# 路径显示中文
git config --global core.quotepath false

修改代理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
git config --global http.proxy socks5://127.0.0.1:20170
git config --global https.proxy socks5://127.0.0.1:1080

git config --global http.proxy http://127.0.0.1:20171
git config --global https.proxy https://127.0.0.1:8080

git config --global --unset http.proxy
git config --global --unset https.proxy

git config -l --global

Rebase

合并可以用 git rebase <base commit>,不仅仅可以合并分支,还可以合并commit。

Rebase的过程大概是这样的

  1. 确定公共祖先:当前commit与base commit的lca。
  2. 取出更改:取出当前commit从公共祖先开始的所有提交。
  3. 应用更改:将这些提交更改依次接续到base commit后方。

rebase 过程中,如果出现冲突,Git 会停止并允许你手动解决这些冲突。解决冲突后,你需要使用 git add 将更改加入暂存区,然后使用 git rebase --continue 命令继续 rebase 过程。

rebase 操作的好处是可以将项目的历史变得更加清晰,因为它避免了不必要的合并提交,使得项目的历史看起来像是一条直线。这对于查看项目的历史和维护项目非常有用。

更精细化的修改需要使用 git rebase -i 的交互式操作,它允许你指定从公共祖先开始的每个提交分别采取怎样的操作:

  1. pick:这是默认操作,即按照原来的顺序将lca链上的提交依次应用到base commit后方。
  2. reword:修改某个提交的消息,这会在应用该提交之前暂停变基过程,允许你编辑提交消息。
  3. squash:将提交合并到前一个提交中,并让你给合并后的提交提供一个新的提交消息。
  4. fixup:将提交合并到前一个提交中,沿用前一个提交的消息。
  5. edit:对某个提交进行修改、添加新的commit或修复错误。这会暂停变基过程,允许你进行所需的更改,然后继续变基。
  6. drop:这个选项用于删除某个提交。如果你不再需要某个提交,可以将其行的 pick 改为 drop

单次Commit的文件太多了,可能会想把它拆解得更细

用edit暂停,reset到HEAD^,一个一个add+commit。

squash和fixup不能用在rebase -i列表的首个commit,因为它前面就是要保留的base commit,无法与之合并成新commit

HEAD是一个指标,指向某一个分支,通常可以把它当作“当前所在分支”来看待。(如:refs/heads/master)。分支也是个指针,指向分支上最新的commit。例如:HEAD -> refs/heads/master -> b431fa7

HEAD如果没有指向本地的分支,则称之为detached head(断头HEAD)。可能发生这个状态的原因有:

  1. 使用 Checkout 指令直接跳到某个 Commit,而不是 Branch。
  2. Rebase 的过程其实也是处于不断的 detached HEAD 状态。
  3. 切换到某个远端分支的时候。

断头HEAD约等于创建一个叫HEAD的临时分支,可以允许你任意checkout某个commit开始工作,提交新的commit与主干分叉。但要切换到其他分支或commit时,你可以任选:

创建一个新分支

不切回原分支,给这个临时分支一个名字:git switch -c <new-branch-name>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
PS C:\Users\24171\Documents\GitHub\Playground> git checkout 41cb26f
Note: switching to '41cb26f'.

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 -

checkout回原来的branch,然后用:git branch <new-branch-name> <commit>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
PS C:\Users\24171\Documents\GitHub\Playground> git commit -a
[detached HEAD c599bef]         modified:   note.md
 1 file changed, 3 insertions(+), 6 deletions(-)
PS C:\Users\24171\Documents\GitHub\Playground> git checkout master
Warning: you are leaving 2 commits behind, not connected to
any of your branches:

  c599bef       modified:   note.md
  9a7602e       new file:   note.md

If you want to keep them by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> c599bef

Switched to branch 'master'

不新建分支

checkout回原来的branch,使用提示中的sha1来merge/rebase。

.gitignore

以”#”号开头表示注释;
以斜杠“/”开头表示目录;
以星号“*”通配多个字符;
以问号“?”通配单个字符
以方括号“[]”包含单个字符的匹配列表;
以叹号“!”表示不忽略(跟踪)匹配到的文件或目录;

1
2
3
4
5
6
*.txt , *.xls  # 表示过滤某种类型的文件
target/  # 表示过滤这个文件夹下的所有文件
/test/a.txt , /test/b.xls  # 表示指定过滤某个文件下具体文件
!*.java , !/dir/test/     # !开头表示不过滤
*.[ab]    # 支持通配符:过滤所有以.a或者.b为扩展名的文件
/test  # 仅仅忽略项目根目录下的 test 文件,不包括 child/test等非根目录的test目录

已追踪的文件,必须先取消追踪,才能被.gitignore忽略。因为其仅影响git add找未追踪文件,而不是找文件修改。

Fun Facts

就像是推送空的内容去更新在线的cat分支的内容,也算是变相地把该分支删除。

1
2
git push origin :cat
git push origin master:cat

A:B means push A as B

Git是根据文件的“内容”来计算SHA-1的值,所以文件的名称不重要,重要的是文件的内容。当更改文件名时,Git并不会为此做出一个新的Blob对象,而只是指向原来的那个Blob对象。但因为文件名变了,所以Git会为此做出一个新的Tree对象。