Git中的提交会记录工作历史记录,并且保证所有的更改都是正式有效的,但是该提交本身却不是不可修改的。
Git中的提交可以进行更改,以便修改完善版本库中的提交。
一般来说,只要没有其它人已经获得了你的版本库的副本,你就可以自由修改和完善版本库提交历史记录。相反,如果一个分支已经发布供他人获取了,并且可能已经存在于其它版本库中了,那么你就不应该重写,修改或更改该分支的任何部分。
git reset命令会把版本库和工作目录改变为目录状态。也就是说,git reset调整HEAD引用指向给定的提交,默认情况下还会更新索引以匹配新提交。同时根据不同的参数选项,该命令也可以修改工作目录以匹配已知的状态,即该命令会覆盖并销毁工作目录中的修改。
git reset命令有三个主要选项:
总结如下表:
选项 | HEAD | 索引 | 工作目录 |
--soft | 是 | 否 | 否 |
--mixed | 是 | 是 | 否 |
--hard | 是 | 是 | 是 |
同时git reset命令还会把原始HEAD值存储在ORIG_HEAD中。
这里看一下这三个命令参数的区别,在此之前,首先执行下述命令:
git init
echo abc > file1
git add file1
git commit -m "commit file1"
echo abcd > file2
git add file2
git commit -m "commit file2"
echo abcde > file1
git add file1
echo abcdef > file2
执行git status后的状态为:
$ git status
On branch master
Changes to be committed:
(use "git restore --staged ..." to unstage)
modified: file1
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
modified: file2
执行带有--soft的git reset后的状态为:
$ git reset --soft HEAD
$ git status
On branch master
Changes to be committed:
(use "git restore --staged ..." to unstage)
modified: file1
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
modified: file2
执行前后的状态一致,表示该命令并没有对工作命令和索引产生影响。
执行带有--mixed或直接执行git reset的git reset后的状态为:
$ git reset HEAD
Unstaged changes after reset:
M file1
M file2
$ git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
modified: file1
modified: file2
no changes added to commit (use "git add" and/or "git commit -a")
此时file1状态发生了变化,file1变为了未暂存状态,即此时--mixed选项将索引中的内容更新为了版本库中的形式。
而执行带有--hard的git reset后的状态为:
$ git reset --hard HEAD
HEAD is now at a598158 commit file2
$ git status
On branch master
nothing to commit, working tree clean
$ cat file1
abc
$ cat file2
abcd
即此时file1和file2都回到了修改前的状态,即工作目录和索引都更新为了版本库中的形式。
同时该命令也不止可以更新到HEAD这个应用,也是可以为另外的提交。看下边的例子:
$ git log
commit a5981584cac9c3afd423281efb4d6b5e516c0f8b (HEAD -> master)
Author: wood_glb
Date: Sun Jul 3 11:41:25 2022 +0800
commit file2
commit 884769c51374a0bbea8ba90df0ce4bf38d7f36ba
Author: wood_glb
Date: Sun Jul 3 11:41:23 2022 +0800
commit file1
$ git reset --hard HEAD^
HEAD is now at 884769c commit file1
$ git log
commit 884769c51374a0bbea8ba90df0ce4bf38d7f36ba (HEAD -> master)
Author: wood_glb
Date: Sun Jul 3 11:41:23 2022 +0800
commit file1
$ git ls-files
file1
$ echo abcdefg > file2
git commit -m "commit file2"
$ git add file2
warning: LF will be replaced by CRLF in file2.
The file will have its original line endings in your working directory
$ git commit -m "commit file2"
[master cdae69b] commit file2
1 file changed, 1 insertion(+)
create mode 100644 file2
$ git log
commit cdae69bb23e5481eb3da955af4a2a23ea101826a (HEAD -> master)
Author: wood_glb
Date: Sun Jul 3 11:46:17 2022 +0800
commit file2
commit 884769c51374a0bbea8ba90df0ce4bf38d7f36ba
Author: wood_glb
Date: Sun Jul 3 11:41:23 2022 +0800
commit file1
在上边的例子中,发现HEAD对应的提交有点问题,因此想要返回到HEAD之前的状态,比如HEAD^,然后使用git reset更新到HEAD^提交对应的状态,此时git ls-files显示的文件列表只剩下了file1,然后重新更改提交,此时也就把原来的提交 a59815更改为了cdae69,而884769这个提交则是不变的。
但同时git reset的命令还需要注意,git reset只能够在在一个分支上执行,来指定该分支上的某个提交。也就是说,这跟检出分支不太一致,这里再看一个例子,首先执行如下命令:
git init
echo abc > file1
git add file1
git commit -m "commit file1"
git checkout -b other
echo abcdefg > file2
git add file2
git commit -m "commit file2"
此时的文件都有:
$ git checkout master
Switched to branch 'master'
$ git ls-files
file1
$ git rev-parse HEAD
798f8d61a2857d4f13003117e27f67cc8d96088e
$ git checkout other
Switched to branch 'other'
$ git ls-files
file1
file2
$ git rev-parse HEAD
8da992dad262da803e8260e9878cf1fd377966a0
$ git checkout master
Switched to branch 'master'
而如果使用git reset想要更新到other分支对应的提交:
$ git reset --soft other
$ git rev-parse HEAD
8da992dad262da803e8260e9878cf1fd377966a0
$ ls
file1
这里显示的HEAD对应的文件散列值为8da992,是other分支的HEAD。而如果执行git branch:
$ git branch
* master
other
但此时所在的分支为master分支,但HEAD指向的却是other分支。
此时在当前分支执行如下操作:
$ echo 1111 > file3
$ git add file3
warning: LF will be replaced by CRLF in file3.
The file will have its original line endings in your working directory
$ git commit -m "commit file3"
[master 450d3af] commit file3
2 files changed, 1 insertion(+), 1 deletion(-)
delete mode 100644 file2
create mode 100644 file3
最后的打印提示删除了file2,创建了file3,但其实master分支上没有file2。这是因为:
$ git cat-file -p HEAD
tree d1e61d1627686b46ac00e81aba25e93d1234f115
parent 8da992dad262da803e8260e9878cf1fd377966a0
author wood_glb 1656826449 +0800
committer wood_glb 1656826449 +0800
commit file3
即该提交的父提交为8da992,对应other的HEAD。
$ git rev-parse HEAD^
8da992dad262da803e8260e9878cf1fd377966a0
$ git log
commit 450d3afa73e2b1c746895104b07d7766dddadd9d (HEAD -> master)
Author: wood_glb
Date: Sun Jul 3 13:34:09 2022 +0800
commit file3
commit 8da992dad262da803e8260e9878cf1fd377966a0 (other)
Author: wood_glb
Date: Sun Jul 3 13:24:38 2022 +0800
commit file2
commit 798f8d61a2857d4f13003117e27f67cc8d96088e
Author: wood_glb
Date: Sun Jul 3 13:24:36 2022 +0800
commit file1
这里的打印也说明了问题,HEAD^对应的是other分支的HEAD,而git log打印的结果也说明了最近的提交其父提交为8da992。
这里也就说明了这种搭配是错误的,而一切错误的源头都是因为在活动分支上执行git reset到另外的分支。
再利用git reflog看下版本库中引用变化的历史记录。
$ git reflog
450d3af (HEAD -> master) HEAD@{0}: commit: commit file3
8da992d (other) HEAD@{1}: reset: moving to other
798f8d6 HEAD@{2}: checkout: moving from other to master
8da992d (other) HEAD@{3}: checkout: moving from master to other
798f8d6 HEAD@{4}: checkout: moving from other to master
8da992d (other) HEAD@{5}: checkout: moving from master to other
798f8d6 HEAD@{6}: checkout: moving from other to master
8da992d (other) HEAD@{7}: commit: commit file2
798f8d6 HEAD@{8}: checkout: moving from master to other
798f8d6 HEAD@{9}: commit (initial): commit file1
从上面的打印也可以看出,在HEAD@{2}中分支从other切换到了master,并在HEAD@{1}中看出此时HEAD转换到了other,而此时并没有别的操作,只有git reset命令的执行,这也说明了这种异常的来源。
而若不小心执行错了git reset命令,比如上面的错误,想要回到正确的提交,则可以:
$ git reset --hard 798f8d
HEAD is now at 798f8d6 commit file1
$ git log
commit 798f8d61a2857d4f13003117e27f67cc8d96088e (HEAD -> master)
Author: wood_glb
Date: Sun Jul 3 13:24:36 2022 +0800
commit file1
此时对应的就是正常master的头,如果想要在other分支上进行提交,则需要先检出到other分支,然后才能提交。
git cherry-pick会在当前分支上应用给定提交引入的变更,即使用该命令并不改变版本库中的现有历史记录,而是添加历史记录。
git cherry-pick通常用于将版本库中的一个分支的特定提交引入一个不同的分支中,常见用法是把维护分支的提交移植到开发分支。
比如某个版本库中存在两个分支,一个用于正常开发,一个用于版本发布。而通常情况下,开发分支上的bug修复也需要同步到版本分布的分支上:
$ git log
commit 3945557e6b4db7a7015d3cf1e716e9fb3cb7dc2e (HEAD -> master)
Author: wood_glb
Date: Sun Jul 3 14:07:50 2022 +0800
commit file1
$ git cherry-pick other
[master 5c2ff8a] commit file2
Date: Sun Jul 3 14:07:53 2022 +0800
1 file changed, 1 insertion(+)
create mode 100644 file2
$ git log
commit 5c2ff8a588562e41f7b5af5832b81576b3384d8a (HEAD -> master)
Author: wood_glb
Date: Sun Jul 3 14:07:53 2022 +0800
commit file2
commit 3945557e6b4db7a7015d3cf1e716e9fb3cb7dc2e
Author: wood_glb
Date: Sun Jul 3 14:07:50 2022 +0800
commit file1
上面的代码示例说明的还是之前的例子,在master分支上只存在一个提交,在other分支则存在两个提交,利用git cherry-pick便可将other分支上的最新提交同步到master分支上。
因此该命令的应用还存在两个场景:
git revert与git cherry-pick大致相同,但该命令用于给定提交的逆过程,即该命令用于引入一个新提交来抵消给定提交的影响。同时git revert命令不修改版本库的现存历史记录,而是往历史记录中添加新提交。
其实说的简单点,该命令可以认为对某个提交进行回退:
$ ls
file1 file2
$ git log
commit 6445ce0b262cdc28368e41db390f4016152efeae (HEAD -> other)
Author: wood_glb
Date: Sun Jul 3 14:26:51 2022 +0800
commit file2
commit 48edffebd6f5ce3402f29c92e2b113bfa0eba831 (master)
Author: wood_glb
Date: Sun Jul 3 14:26:45 2022 +0800
commit file1
$ git revert HEAD
Removing file2
[other 43cbded] Revert "commit file2"
1 file changed, 1 deletion(-)
delete mode 100644 file2
$ git log
commit 43cbded56a851bce955421a732c51d1f47b6eb07 (HEAD -> other)
Author: wood_glb
Date: Sun Jul 3 14:27:31 2022 +0800
Revert "commit file2"
This reverts commit 6445ce0b262cdc28368e41db390f4016152efeae.
commit 6445ce0b262cdc28368e41db390f4016152efeae
Author: wood_glb
Date: Sun Jul 3 14:26:51 2022 +0800
commit file2
commit 48edffebd6f5ce3402f29c92e2b113bfa0eba831 (master)
Author: wood_glb
Date: Sun Jul 3 14:26:45 2022 +0800
commit file1
$ ls
file1
上面的代码中,other分支上原有file1和file2两个文件,分别来自两次提交,而使用git revert后并没有删去原来的提交,只是将该提交的内容回退至该提交之前,并添加了新的历史记录,此时的文件中只剩下了file1。
这三个命令的区别或用途在于:
checkout和reset的区别在于,git reset会恢复到某个已知状态,因此该命令可用于清理工作目录或索引,但git checkout却没有这个作用,因为工作目录中的未被追踪的文件和目录会被checkout忽略,同时若文件的本地修改不同于新分支上的变更,checkout可能也会失败。这是因为checkout的作用在于从对象库中提取文件,然后将其放置到工作目录中。
因此利用checkout可以用于以下的场景:
改变当前分支最近一次提交的最简单的方法之一就是使用git commit --amend。通常情况下,amend意味着提交内容基本相同,但某些方面需要调整或清理,而引入对象库的实际提交对象自然不同。
git commit --amend常用的作用在于对最近的提交修改录入错误。
$ git log
commit 45df6cc1b7446f5688b3a206f6d3e52c578981f4 (HEAD -> other)
Author: wood_glb
Date: Sun Jul 3 14:53:42 2022 +0800
commit file2
commit 5c63c5bf1d53fed0746dab0337b6c85b3c590991 (master)
Author: wood_glb
Date: Sun Jul 3 14:53:38 2022 +0800
commit file1
$ echo 1111 > file2
$ git add file2
warning: LF will be replaced by CRLF in file2.
The file will have its original line endings in your working directory
$ git commit -amend -m "modify file2"
error: did you mean `--amend` (with two dashes)?
$ git commit --amend -m "modify file2"
[other 7a82ce2] modify file2
Date: Sun Jul 3 14:53:42 2022 +0800
1 file changed, 1 insertion(+)
create mode 100644 file2
$ cat file2
1111
$ git log
commit 7a82ce243e096b6fd775c88a2587c85e88f3c76d (HEAD -> other)
Author: wood_glb
Date: Sun Jul 3 14:53:42 2022 +0800
modify file2
commit 5c63c5bf1d53fed0746dab0337b6c85b3c590991 (master)
Author: wood_glb
Date: Sun Jul 3 14:53:38 2022 +0800
commit file1
比如之前的例子中,若other分支上最新提交不是特别正确,需要对其进行修改,然后重新进行暂存,最后利用--amend进行提交,通过前后两次git log的打印,也可以发现并没有出现多余的历史记录,即此时的提交只是对最新提交的简单修改,并不是一次新的提交。
git rebase是用来改变一串提交是以什么为基础的,此命令至少需要提交将迁往的分支名。通常情况下,不在目标分支中的当前分支提交会变基。
git rebase的常见用途是保持正在开发的一系列提交相对于另一个分支是最新的,通常是master分支或者来自另一个版本库的追踪分支。
比如下面的例子:
$ git show-branch
* [master] commit file2
! [other] commit file4
--
+ [other] commit file4
+ [other^] commit file3
* [master] commit file2
*+ [other~2] commit file1
$ git rebase master other
Successfully rebased and updated refs/heads/other.
$ git show-branch
! [master] commit file2
* [other] commit file4
--
* [other] commit file4
* [other^] commit file3
+* [master] commit file2
可以看出执行git rebase后,other分支上从other和master的共有节点开始到other分支的HEAD结束都会移植到master的HEAD上,从而使other分支的HEAD变为master分支的HEAD。
同时上边的git rebase命令也等效于:
git checkout other
git rebase master
可以理解为将当前分支变基到master,变基的“基”应该就是该分支的最早提交,即当前分支和目标分支的共有提交。
同时变基过程中,可能也需要进行冲突解决,在冲突的过程中可能会使用到git rebase的下列选项:
git rebase中的-i可以将提交重新排序,编辑,删除,将多个提交合并为一个。该命令允许用户修改一个分支的大小,然后将它们放回原来的分支或者不同的分支。
首先执行下列代码:
git init
echo abc > file
git add file
git commit -m "first commit"
echo abcd > file
git add file
git commit -m "second commit"
echo abcde > file
git add file
git commit -m "third commit"
echo abcdef > file
git add file
git commit -m "forth commit"
这里会出现4个提交:
$ git show-branch --more=5
[master] forth commit
[master^] third commit
[master~2] second commit
[master~3] first commit
然后使用git rebase -i对提交进行变基修改:
git rebase -i master~3
执行上边的命令后,可能会出现:
pick a7d2a8e second commit
pick d4252c7 third commit
pick b7b0cee forth commit
# Rebase 0afa6de..b7b0cee onto 0afa6de (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop = remove commit
# l, label
然后将之修改为:
pick d4252c7 third commit
pick a7d2a8e second commit
pick b7b0cee forth commit
之后退出,不过中途可能需要经过多次的冲突解决,最终的结果可能是:
$ git show-branch --more=5
[master] forth commit
[master^] second commit
[master~2] third commit
[master~3] first commit
在冲突的过程中只是file的内容进行修改,而不修改提交信息,因此也就有了上面的打印,表示提交此时出现了更改。
重新执行下述命令:
git rebase -i master~3
此时可能会有如下打印:
pick d4252c7 third commit
pick a7d2a8e second commit
pick b7b0cee forth commit
# Rebase 0afa6de..b7b0cee onto 0afa6de (3 commands)
#
将之更改为:
pick d4252c7 third commit
squash a7d2a8e second commit
squash b7b0cee forth commit
此时最后的结果为:
$ git show-branch --more=5
[master] third commit
[master^] first commit
即将forth,second,third三个提交合并为一个提交:
$ git log
commit 3487d1243c5b821a4f5b69902462278d18e13798 (HEAD -> master)
Author: wood_glb
Date: Sun Jul 3 16:10:25 2022 +0800
third commit
second commit
forth file
commit 0afa6de1e6e4d1a0fe894aaf39c92514413d13ef
Author: wood_glb
Date: Sun Jul 3 16:10:22 2022 +0800
first commit
这点从上面的打印结果也可以看出来。
将一系列提交变基到一个分支的头与合并两个分支是相似的,在这两种情况下,该分支的新头都有两个分支代表的组合效果。
变基一系列提交的过程会导致Git生成一系列全新的提交,它们会有新的散列值,基于新的初始状态,代表不同的差异。
考虑如下的例子:
git init
echo 1111 > file1
git add file1
git commit -m "commit file1"
echo 2222 > file2
git add file2
git commit -m "commit file2"
echo 3333 > file3
git add file3
git commit -m "commit file3"
echo 4444 > file4
git add file4
git commit -m "commit file4"
git branch dev HEAD~2
git checkout dev
echo 5555 > file5
git add file5
git commit -m "commit file5"
echo 6666 > file6
git add file6
git commit -m "commit file6"
echo 7777 > file7
git add file7
git commit -m "commit file7"
git branch dev2 HEAD^
git checkout dev2
echo 8888 > file8
git add file8
git commit -m "commit file8"
echo 9999 > file9
git add file9
git commit -m "commit file9"
git checkout master
此时版本库的状态为:
$ git show-branch --more=20
! [dev] commit file7
! [dev2] commit file9
* [master] commit file4
---
+ [dev2] commit file9
+ [dev2^] commit file8
+ [dev] commit file7
++ [dev2~2] commit file6
++ [dev2~3] commit file5
* [master] commit file4
* [master^] commit file3
++* [dev2~4] commit file2
++* [dev2~5] commit file1
用图表示为:
如果对上边的提交图执行变基操作:
$ git rebase master dev
Successfully rebased and updated refs/heads/dev.
$ git show-branch --more=20
* [dev] commit file7
! [dev2] commit file9
! [master] commit file4
---
* [dev] commit file7
* [dev^] commit file6
* [dev~2] commit file5
* + [master] commit file4
* + [master^] commit file3
+ [dev2] commit file9
+ [dev2^] commit file8
+ [dev2~2] commit file6
+ [dev2~3] commit file5
*++ [master~2] commit file2
*++ [master~3] commit file1
用图表示为:
上图中,会将dev分支的5,6,7变基到master分支上,而7此时因为dev分支的变基而变得不可达,因此会被删除,而dev2分支的5,6则会因为dev2分支的存在而保留。
而之后若执行如下的变基操作:
$ git rebase dev^ dev2
Successfully rebased and updated refs/heads/dev2.
$ git show-branch --more=20
! [dev] commit file7
* [dev2] commit file9
! [master] commit file4
---
* [dev2] commit file9
* [dev2^] commit file8
+ [dev] commit file7
+* [dev2~2] commit file6
+* [dev2~3] commit file5
+*+ [master] commit file4
+*+ [master^] commit file3
+*+ [master~2] commit file2
+*+ [master~3] commit file1
用图表示为:
这个变化过程很可能超出了用户的预期,比如yoghurt很可能希望在执行第一次变基操作的时候就将原始的版本库调整为上图的样子,但是实际上因为该命令的实际关系,可能需要经过连续的两次变基才可以。这就意味着用户需要完全明白变基操作的内涵。
同时变基操作的原理则说明该操作可能会在分支情况较为复杂时出现问题,比如存在多个交叉合并的版本库,此时执行变基可能会带来意想不到的结果。
比如下面的示例:
git init
echo 1111 > file1
git add file1
git commit -m "commit file1"
echo 2222 > file2
git add file2
git commit -m "commit file2"
echo 3333 > file3
git add file3
git commit -m "commit file3"
echo 4444 > file4
git add file4
git commit -m "commit file4"
git branch dev2 HEAD~2
git checkout dev2
echo 5555 > file5
git add file5
git commit -m "commit file5"
echo 6666 > file6
git add file6
git commit -m "commit file6"
echo 7777 > file7
git add file7
git commit -m "commit file7"
git branch dev HEAD^
git checkout dev
echo 8888 > file8
git add file8
git commit -m "commit file8"
git merge dev dev2 -m "merge dev2 to dev"
echo 9999 > file9
git add file9
git commit -m "commit file9"
git branch -d dev2
其版本库结果为:
$ git branch
* dev
master
$ git log --graph --pretty=oneline --abbrev-commit
* ba66f8c (HEAD -> dev) commit file9
* 995d005 merge dev2 to dev
|\
| * 9b7fd94 commit file7
* | 07d3867 commit file8
|/
* d6b476b commit file6
* 96d0410 commit file5
* 4e13519 commit file2
* 9d7891a commit file1
$ git show-branch --more=20
* [dev] commit file9
! [master] commit file4
--
* [dev] commit file9
* [dev^^2] commit file7
* [dev~2] commit file8
* [dev~3] commit file6
* [dev~4] commit file5
+ [master] commit file4
+ [master^] commit file3
*+ [dev~5] commit file2
*+ [dev~6] commit file1
用图表示为:
若想要将5开始的整个分支移植到master的4上时,可能会执行如下的变基指令:
$ git rebase master dev
Successfully rebased and updated refs/heads/dev.
$ git log --graph --pretty=oneline --abbrev-commit
* 4d35c7b (HEAD -> dev) commit file9
* d5cf3e0 commit file7
* db82d92 commit file8
* c42be48 commit file6
* c5e8b4b commit file5
* 1d3df99 (master) commit file4
* e1d9b55 commit file3
* 4e13519 commit file2
* 9d7891a commit file1
$ git show-branch --more=20
* [dev] commit file9
! [master] commit file4
--
* [dev] commit file9
* [dev^] commit file7
* [dev~2] commit file8
* [dev~3] commit file6
* [dev~4] commit file5
*+ [master] commit file4
*+ [master^] commit file3
*+ [master~2] commit file2
*+ [master~3] commit file1
从上边的打印结果来看,merge操作对应的提交没有了,用图表示为:
这个结果似乎不是用户想要的,因此如果想要达到用户的期望,可能需要添加如下的选项:
$ git rebase --preserve-merges master dev
warning: git rebase --preserve-merges is deprecated. Use --rebase-merges instead.
Successfully rebased and updated refs/heads/dev.
$ git log --graph --pretty=oneline --abbrev-commit
* 17226d6 (HEAD -> dev) commit file9
* d20081e merge dev2 to dev
|\
| * b236dac commit file7
* | 7eb8c51 commit file8
|/
* 4c9ab46 commit file6
* 1097f73 commit file5
* 89e4e4b (master) commit file4
* 0712b2c commit file3
* a4fb6ab commit file2
* c2f2d25 commit file1
$ git show-branch --more=20
* [dev] commit file9
! [master] commit file4
--
* [dev] commit file9
* [dev^^2] commit file7
* [dev~2] commit file8
* [dev~3] commit file6
* [dev~4] commit file5
*+ [master] commit file4
*+ [master^] commit file3
*+ [master~2] commit file2
*+ [master~3] commit file1
这个的--preserve-merges提示要废弃了,需要使用--rebase-merges来代替。
鉴于上面的内容,使用变基的基础是需要对该命令有较为深刻的了解,在复杂的提交图中应谨慎使用,此时可使用合并来代替变基。
变基的操作总结一下就是: