用命令行驾驭 git
Git 101
Git 基础操作
初始化一个新的repository |
---|
git init |
添加文件到repository |
---|
git add . |
git commit -m “message” |
获取commit 历史 |
---|
git log |
返回结果如下:
1
2
3
4
5
commit 9f1944464c12e9ff09a9b1f32245bfc705e8732d (HEAD -> main)
Author: minicoderwen <373441770@qq.com>
Date: Fri Dec 22 00:28:11 2023 +0000
update karabiner # 这个是 commit message
然后就可以用获取到的commit id
来
切换到某个commit |
---|
git checkout 9f1944464c12e9ff09a9b1f32245bfc705e8732d |
Branch 分支
查看所有branch |
---|
git branch |
创建branch 的几种方式 |
---|
git branch new-branch |
git checkout new-branch |
git checkout -b new-branch |
git switch -c new-branch |
合并branch |
---|
git merge new-branch |
切换branch |
---|
git switch existing-branch |
查看staging area 中的所有文件 |
---|
git ls-files |
Git Undo 撤销
如何删除已经 staged
的文件?
首先,删除文件本身,再
告诉 git 我们要删除这个文件 |
---|
git rm some-file |
git add . |
这两条命令实现同样的效果,那就是告诉 git 不要再追踪这个文件了。
如何撤销对 unstaged
文件的修改?
假设我们修改了some-file
,现在我们想要撤销修改。我们可以git checkout some-file
让他回退到上一个commit
的初始状态。
那么如何撤销对所有文件的修改呢?
撤销所有修改 |
---|
git checkout . |
这条命令表示回退当前分支下所有被追踪的文件到上一个commit
的状态。checkout
是 git 常用撤销修改的命令。但是 git 提供了一个更明确的指令,叫作restore
,能实现同样的效果。
撤销修改 |
---|
git restore some-file |
git restore . |
如何删除 untracked
的文件?
假设我们在项目中新增了一个文件some-file
,在git add some-file
之前这个文件是处于untracked
状态的。如果我们想要删除这个文件,我们可以用git clean
命令。
-d
表示删除目录-f
表示强制删除-n
表示列举出要删除的文件,但是不真的删除
删除 untracked 文件和目录 |
---|
git clean -d -f some-file |
列举出 untracked 的文件和目录 |
---|
git clean -d -n |
如何撤销对 staged
文件的修改?
git checkout
对已经staged
的文件不生效了。
旧方法 |
---|
git reset some-file |
git reset
会把some-file
最新一次commit
的内容覆盖到staging area
,这个命令可以撤销对staged
文件的修改,但是这个命令会把staged
的文件变成unstaged
的文件。如果我们不想要这个副作用,我们可以用git restore
命令。
新方法 |
---|
git restore –staged some-file |
如何删除本地的 commit
?
回退 1 个 commit |
---|
get reset HEAD~1 |
回退 2 个 commit |
---|
git reset HEAD~2 |
- 使用
--soft
- –soft 表示回退到某个
commit
,但是保留所有修改在staging area
,只有 commit 被回退了,可以直接再次git commit
。 - 不用 –soft 表示修改的内容会被从
staging area
移除到unstaged
,但是修改不会被删除。
- –soft 表示回退到某个
使用
--hard
- –hard 表示删除某一个(几个)
commit
并且删除所有的修改。
- –hard 表示删除某一个(几个)
如何删除 branch
使用 -d 删除 branch |
---|
git branch -d some-branch |
使用 -D 删除 branch |
---|
git branch -D some-branch |
-d
表示删除一个已经merge
的branch
,如果branch
没有被merge
,则会报错。-D
表示强制删除一个branch
,不管branch
有没有被merge
。
什么是 detached HEAD
当我们checkout
某一个特定的commit
,这时候就处于detached
的状态,表示HEAD
指向的不是一个branch
,而是一个commit
。有时候我们在commit
的时候忘了一些东西,我们可以用这种方法来切换到那个commit
特定的节点,去补充我们落下的东西。
那么如何在detached HEAD
模式下进行修改并合并进main
目标分支呢?
1
2
3
4
5
6
# 首先 checkout 某一个 commit
git checkout 9f1944464c12e9ff09a9b1f32245bfc705e8732d
# 进行修改,然后 commit
git add some-file
git add .
git commit -m "some message"
此时main
分支是不包含这个commit
的,我们需要新建一个分支包含这个commit
,再合并到main
目标分支。
1
2
3
4
5
6
git log # 获取我们要合并的 commit id,类似 037e3f3
git branch new_branch 037e3f3 # 新建一个分支包含这个 commit
git branch # 此时查看分支,会发现 detached HEAD 的提示没有了
git checkout main # 切换到 main 分支
git merge new_branch # 合并 new_branch 分支
git branch -d new_branch # 删除 new_branch 分支,因为已经不需要这个分支了
什么是.gitignore?
我们可以创建一个.gitignore
来告诉 git 哪些文件不需要被追踪,比如 IDE 的配置文件、编译后的文件、日志文件等。
1
2
3
4
5
6
*.log # 忽略所有的 log 文件
!test.log # 但是不忽略 test.log
some_folder/ # 忽略 some_folder 目录下的所有文件
**/some_folder/ # 忽略所有目录下的 some_folder 目录
**/some_folder/** # 忽略所有目录下的 some_folder 目录和文件
Git 基础命令总结
命令 | 作用 |
---|---|
git –version | 查看 git 版本 |
git init | 初始化一个新的 repository |
git status | 查看当前状态 |
git log | 查看 commit 历史 |
git ls-files | 查看 staging area 中的所有文件 |
git add some-file | 添加文件到 staging area |
git add . | 添加所有文件到 staging area |
git commit -m “message” | 提交 staging area 中的文件到 repository |
git checkout commit-id | 切换到某个 commit(detached HEAD) |
git branch branch-name | 新建一个 branch |
git checkout branch-name | 切换到某个 branch |
git checkout -b branch-name | 新建一个 branch 并切换到这个 branch |
git switch -c branch-name | 新建一个 branch 并切换到这个 branch |
git merge other-branch | 合并 other-branch 到当前 branch |
git rm some-file | 删除文件并且告诉 git 不要再追踪这个文件 |
git restore some-file | 撤销对 unstaged 文件的修改 |
git restore –staged some-file | 撤销对 staged 文件的修改 |
git clean -df some-file | 删除 untracked 文件 |
git reset HEAD~1 | 回退 1 个 commit |
git reset –soft HEAD~1 | 回退 1 个 commit,但是保留所有修改在 staging area |
git reset –hard HEAD~1 | 回退 1 个 commit,删除所有修改 |
git branch -D branch-name | 强制删除 branch |
什么是 Git Stash
如果我们在branch1
上修改了一些文件,但是我们想要切换到branch2
上去,但是我们又不想要把修改的内容commit
到branch1
上,这时候我们可以用git stash
命令。git stash
会将已经修改的内容存到栈中,然后回退到修改前HEAD
的状态,我们可以在后续应用这些修改。
假设我们有一个文件file.txt
,内容如下:
1
This is the original content.
修改file.txt
,内容如下:
1
2
This is the original content.
This is added.
git stash
执行git stash
,然后我们再查看file.txt
,会发现文件恢复到了修改前的状态。
1
This is the original content.
再次修改file.txt
,内容如下:
1
2
This is the original content.
This is added again.
git stash list
再次执行git stash
,此时栈中会有两个版本的stash
。我们可以使用git stash list
查看栈中的内容。
1
2
stash@{0}: WIP on main: aa2183d commit name
stash@{1}: WIP on main: aa2183d commit name
git stash apply
如果想要应用stash
中的修改,我们可以用git stash apply
命令,这个命令会将栈顶(也就是stash@{0}
)的修改应用到当前分支。
git stash apply
永远应用的是最新的stash
,所以在这里This is added again.
会被应用而不是This is added.
如果我们想要应用指定的stash
,执行git stash apply 1
。此时,This is added.
会被应用。
git stash push
我们可以用git stash push -m "some message"
来记录某一个stash
的信息。此时执行git stash list
,stash@{0}
会显示
1
stash@{0}: On main: some message
这个方法可以让我们更好地区分不同的stash
。
git stash pop
当我们决定好要应用哪一个stash
后,我们可以使用git stash pop
,这个命令会将栈顶的stash
应用到当前分支,并且将栈顶的stash
删除。此时之前的stash@{1}
会变成stash@{0}
。
git stash drop
我们可以用git stash drop
来删除某一个stash
,比如git stash drop 1
。
git stash clear
我们可以用git stash clear
来清空栈中所有的stash
。
什么是 Git reflog
git reflog
可以查看所有的commit
历史,包括reset
和rebase
的历史。我们可以用这个命令来找回丢失的commit
和branch
。
1
2
3
4
5
6
7
aa2183d (HEAD -> main, origin/main, origin/HEAD) HEAD@{0}: reset: moving to HEAD
aa2183d (HEAD -> main, origin/main, origin/HEAD) HEAD@{1}: reset: moving to HEAD
aa2183d (HEAD -> main, origin/main, origin/HEAD) HEAD@{2}: commit: nvim add trouble
60d9e5b HEAD@{3}: commit: update nvim
34fb74a HEAD@{4}: commit: update nvim
4b97fc3 HEAD@{5}: commit: update zshrc
f1259a9 HEAD@{6}: commit: update nvim
使用git reflog
找回丢失的 commit。
1
2
3
git reflog
git reset --hard 60d9e5b
git log # 此时 60d9e5b 又变成了 HEAD
使用git reflog
找回丢失的 branch。
1
2
3
4
5
6
7
8
git checkout -b new_branch
git add .
git commit -m "file added"
git switch main
git branch -D new_branch # 删除分支
git reflog # 找到 "file added" 这个 commit id
git checkout 60d9e5b # 此时处于 detached 模式
git switch -c new_branch # 创建一个包含这个 commit 的新分支
什么是 Git Merge
假设有一个new_branch
分支需要合并到master
分支:
- 如果
new_branch
有新的commit
,master
没有,git merge
会默认使用fast-forward
进行合并。 - 如果
new_branch
和master
都有新的commit
,git merge
会默认使用non-fast-forward recursive merge
进行合并。
什么是 fast-forward
1
2
3
4
git checkout -b new_branch # 从 master 创建一个新分支
# 在新分支上修改新增内容并提交
git checkout master # 切换到 master 分支
git merge new_branch # 将 new_branch 合并到 master
从master
上创建新分支并进行开发,之后新分支的提交合并回master
分支,此时fast-forward
发生。
fast-forward
不会产生新的commit
,只是将master
指向了new_branch
的commit
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
commit dc266f220355690a48a56b520dbdd0aeb2a8d4e8 (HEAD -> main, origin/main, origin/HEAD)
Author: minicoderwen <373441770@qq.com>
Date: Thu Jan 4 11:58:23 2024 +0000
new_branch commit 2 <<<<<<<<<<< fast-forward merge 之后,新的 HEAD 指向这个 new_branch 的最后一次 commit
commit f2a4922b8d01b2365e6056bee765701ad063adbe
Author: minicoderwen <373441770@qq.com>
Date: Sat Dec 30 05:31:36 2023 +0000
new_branch commit 1
commit 3d6ff634564a0d17a0f3cd83d2f8e5c4acf86ff5
Author: BigWen <48466898+minicoderwen@users.noreply.github.com>
Date: Thu Dec 28 07:24:21 2023 +0000
original master commit <<<<<<<<<<< 原来的 master 分支指向这个分支
了解 –squash
我们可以执行git reset --hard HEAD~2
先回退master
分支到original master commit
。
1
git merge --squash new_branch
--squash
会将new_branch
的所有commit
合并成一个commit
,然后将这个commit
应用到master
分支上。
此时执行git log
1
2
3
4
5
commit 3d6ff634564a0d17a0f3cd83d2f8e5c4acf86ff5 (HEAD -> main, origin/main, origin/HEAD)
Author: BigWen <48466898+minicoderwen@users.noreply.github.com>
Date: Thu Dec 28 07:24:21 2023 +0000
original master commit
此时并没有新的分支被创建,但是new_branch
上的所有改动已经被添加到了staging area
。
最后执行git commit -m "some message"
,new_branch
上的所有改动就被合并到了master
上。
什么是 non-fast-forward recursive merge
了解 –no-ff
--no-ff
表示不使用fast-forward
进行合并。
1
git merge --no-ff new_branch
--no-ff
的应用场景:new_branch
创建之后,master
有了新的commit
,new_branch
也有了新的commit
,此时这两个分支的base
已经不一样了。
此时使用--no-ff
进行合并,会产生一个新的commit
,这会将master
和new_branch
上的所有修改合并成一个commit
。
此时执行git log
,new_branch
上的分支已经被合并到master
了。
如果要回退,只需要git reset --hard HEAD~1
(不需要数new_branch
的commit
数量)。此时new_branch
上的分支也会被从master
上回退。
什么是 Git rebase
如图所示,git rebase
可以将feature
分支中的f.1
和f.2
这两个commit
添加到已经新增了m.3
这个commit
的master
分支上。这个属于fast-forward merge
。
原理解析:
- “m.3”变成
feature
分支的base
。 master
分支rebase
到feature
分支。- 合并
feature
分支到master
分支。
rebase
并不会移动commit
,而是创建新的commit
。所以不要rebase
到不是你的分支上。
1
2
3
4
git checkout feature
git rebase master
git checkout master
git merge feature
什么时候使用 rebase?
- 当
feature
分支还没合入master
但master
已经有了新的commits
。
注意:
rebase
会更改代码的历史,因为rebase
后commit id
变了。
如何处理冲突?
git merge --abort
终止合并。
git log --merge
查看合并历史。
git diff
查看冲突。
解决冲突后正常commit
就可以了。
什么是 Cherry-Pick
假设我们在feature
分支有 3 个新的commit
,我们想要把某一个特定的commit
合并到master
分支。
1
2
3
git log # 获取这个 commit 的 id
git checkout master
git cherry-pick 3d6ff634564a0d17a0f3cd83d2f8e5c4acf86ff5
注意:
cherry-pick
会创建一个新的commit
,所以commit id
会变化。
运用 Tag 管理版本
当某一个commit
具有一定的意义,比如v1.0
,我们可以给那个commit
打上 Tag。
1
2
3
4
5
6
7
git tag # 获取所有的`tag`。
git tag v1.0 3d6ff634564a0d17a0f3cd83d2f8e5c4acf86ff5 # 给这个分支打上标签
git show v1.0 # 查看这个 tag 的信息
git checkout v1.0 # 切换到这个 tag 所在的分支,注意是 detached-HEAD 模式
git tag -d v1.0 # 删除这个 tag
git tag -a v2.0 -m "some message" # 创建一个 annotated tag
git show v2.0 # 查看这个 tag 的信息,会显示"some message"
GitHub 101
首先在 GitHub 上创建一个新的仓库,然后连接本地仓库至 GitHub 远程仓库。
仓库名注意和本地的仓库名保持一致。
origin
是远程仓库的别名,可以自定义。
1
2
3
4
5
6
git init
git add .
git commit -m "Initial commit"
git branch -M master
git remote add origin \<URL\>
git push -u origin master
GitHub 的一些概念
当我们执行git push origin master
,Git 会将master
的内容创建一个新的分支叫作 Remote Tracking Branch (READ-ONLY),好让 Git 知道这个本地仓库要推送到一个别名叫作origin
的仓库的master
分支上。
同样,当我们执行git pull
的时候,Git 会先将远程仓库的内容fetch
到 Remote Tracking Branch 分支上,然后再merge
回本地分支。
所以,本地的master
分支和远程的master
分支并没有直接的关联,而是要通过 Remote Tracking Branch。
我们同样可以
git push origin feature
来在远程仓库创建一个新的 feature 分支,道理是一样的。
假设我们在远程仓库上创建了一个新的分支remote-feature
,此时在本地git branch -a
是看不到的,我们可以使用get fetch origin
将远程仓库的内容fetch
到本地作为 Remote Tracking Branch,此时git branch -a
就能看到了。
Remote && Local Tracking Branch
命令 | 作用 |
---|---|
git remote | 查看远程仓库 |
git branch -a | 查看本地和远程所有分支 |
git branch -r | 查看远程分支 |
git remote show origin | 查看远程仓库的详细信息 |
git branch -vv | 查看 Local Tracking Branch 和他们的 Remote |
git branch –track feature origin/feature | 创建一个 Local Tracking Branch |
当我们git branch -c new_branch
的时候,git branch -a
会显示远程仓库是没有追踪 t 这个分支的。
我们可以使用git push origin new_branch
在远程创建这个分支。
如果远程仓库已经存在,我们可以git branch --track new_branch origin/new_branch
来创建一个 Local Tracking Branch。
此时git branch -vv
会显示new_branch
和origin/new_branch
的关联。
如何 clone 一个远程仓库
1
git clone <URL>
了解 Upstream
-u
的用处:
当我们git push origin new_branch
的时候,会发现本地和远程仓库是没有被互相追踪的。我们需要手动去创建 Remote && Local Tracking Branch 来创建分支之间的联系。
所以我们可以git push -u origin new_branch
,这样 Git 会自动创建 Remote && Local Tracking Branch。
删除远程分支和提交
不能直接使用git branch -D remote-feature
来删除远程分支。可以执行:
1
2
git branch --delete --remotes origin/remote-feature
git push origin --delete remote-feature
如何删除提交?
1
git reset --hard HEAD~1 # 回退到上一个 commit
此时修改只是本地的,这个时候 push 会提示失败,因为此时这个分支的历史已经和远程分支不一样了。
1
hint: Updates were rejected because the tip of your current branch is behind its remote counterpart.
我们可以使用git push --force
来强制推送。
1
git push --force origin master