这是我在学习Git的过程中所做的读书笔记的相关总结,希望能对刚刚接触Git或Github的小伙伴们带来一点点帮助,欢迎在我的博客下或者本文下给予意见和帮助!
Git是当代最流行的版本控制系统,这是我在阅读了廖雪峰的Git基础教程后所作的笔记和总结,希望能对你有帮助。
Git概述
Git是一个分布式版本控制系统。就是基于它,著名的GitHub网站才能为开源项目免费提供Git存储,即Git的远程仓库服务。Git是完全免费的,GitHub对于开源仓库也是免费的。其他的免费的版本控制系统(Version Control System,即VCS)还有CSV,SVN等,它们都是集中式的,速度慢,而且还必须联网才能使用。
Git的产生原因是由于维护Linux项目需要一个VCS的支持。起初BitKeeper(属于BitMover公司)被授予Linux社区免费使用,而后来有人因试图破解BitKeeper而使BitMover公司撤回了对Linux社区的授权,Linus就趁机写了自己的Git来取代BitKeeper的版本控制功能。
那么集中式VCS和分布式VCS各自都有什么特点呢?
集中式是指版本库集中放在中央服务器中,每次工作都需要从中央服务器中取得最新的版本,在本机上进行修改,然后推送到远程的中央服务器上。中央服务器就好比是一个图书馆。必须联网才能工作。
分布式是指并没有中央服务器,每个人的电脑上都是一个完整的版本库,只互相推送各自的修改。因为两人电脑通常不在一个局域网内,两台电脑互相访问不了或者没有开机等等,导致在两人之间的电脑上无法推送版本库的修改,所以通常也有一台充当“中央服务器”的电脑,方便“交换”大家的修改。
常见的版本控制系统有CVS,它是免费和开源的、集中式版本控制系统。由于CVS自身设计问题,会造成提交文件不完整,版本库莫名其妙损坏的情况。同样开源免费的SVN就修正了CVS的一些稳定性问题,是目前用的最多的集中式版本库控制系统。
收费的集中式版本控制系统有IBM的ClearCase、微软的VSS(集成在Visual Studio中)等。分布式版本控制系统有Git、BitKeeper、Mercurial、Bazaar等。
安装Git
Linux系统(我的是Ubuntu14.04)可以直接使用命令 sudo apt-get install git
来安装。这里是在默认的软件仓库中安装,版本比较低,如果需要安装较新版本的Git,输入以下命令
$ sudo add-apt-repository ppa:git-core/ppa
$ sudo apt-get update
$ sudo apt-get install git
注意,在老版本的debian或者ubuntu linux,要用 sudo apt-get install git-core
安装,因为有个软件(GNU Interactive Tools)也叫GIT,所以git只能叫git-core,但现在Git名气实在太大,后来就把GNU Interactive Tools改名为gnuit了,git-core正式改为git。
如果要通过Git的源码安装,就要走经典的 ./config
、 make
、 sudo make install
三部曲了。
除了CLI的Git,还有一些图形化的Git工具,比如gitg,安装命令如下
$ sudo apt-get install gitg
安装完成后要配置自己的个人信息,在你的shell或git bash中输入以下命令,配置自己的用户名和邮箱
$ git config --global user.name "Your Name"
$ git config --global user.email "[email protected]"
其中--global参数代表该配置对所有本机的Git仓库都生效,这些配置都被保存在用户目录下的.gitconfig文件中,如果不加--global参数,则必须要在Git仓库目录中配置,该配置仅在该Git仓库中有效,配置文件在.git/config
中。
配置Git使用的默认文本编辑器,使用
$ git config --global core.editor "subl -w" # 配置Sublime
$ git config --global core.editor "vim" # 配置vim
注意subl程序要在PATH路径下,加-w参数是为了让Sublime在文件被保存后再返回。
个性化Git配置
让Git显示颜色
$ git config --global color.ui true
配置别名alias
$ git config --global alias.st status
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
$ git config --global alias.unstage 'reset HEAD'
$ git config --global alias.last 'log -1' # 显示最后一次提交
$ git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
其中--global参数是全局参数,也就是说这些配置在这台电脑的所有Git仓库下都可用。
Git基本操作
git init
$ mkdir learngit
$ cd learngit
$ pwd
$ git init # 把当前目录变成Git可以管理的Git仓库
.git目录是git来跟踪管理版本库的,也可以在非空目录下建立git仓库。
也可以在一个Git仓库中再次使用init
命令,会重新初始化已存在的Git仓库。
如果你使用Windows系统,确保仓库路径名不包含中文。
其实,所有的版本控制系统,只能跟踪文本文件的改动,比如TXT文件、网页、程序代码等等。
图片视频等二进制文件,虽然也能由版本控制系统管理,但无法跟踪文件的变化,只能把二进制文件每次的改动串起来,比如只能知道图片从100KB改成了120KB,但到底改了啥,VCS无法知道。
git add & git commit
使用下列命令来将readme.txt文件添加进暂存区,并提交为一个版本commit。
$ git add readme.txt
$ git commit -m "wrote a readme file"
-m参数后紧跟本次提交的说明信息,你可以多次add,然后用一次commit提交很多文件,如
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
git add -n .
只显示将要添加的文件,而并不实际添加到index,也可以用git add --dry-run .
。git add . -v
将会显示详实信息。git add . -i
交互式地添加文件。git add -A
添加工作区中所有的文件到index。
有关git add -A
、git add .
和git add -u
的区别和在Git不同版本中的表现,参见下图:
git commit -a
可以自动暂存被修改或删除的已跟踪文件,对于Git未跟踪的文件则不会被提交。
git commit -c
可以在之前某个commit信息的基础上再编辑,作为这次commit的提交信息。
git默认不允许提交空提交,即和父commit完全一样的commit不被允许,但是可以加上--allow-empty参数来强制提交。
如果你不想写提交信息,使用--allow-empty-message参数,强烈不建议!
如果需要修改上一次commit的信息,可以使用以下命令
$ git commit --amend
该命令会删除上一次commit,然后用这次commit替换。
git diff
使用下列命令
$ git diff file 可以查看file文件的修改 或者
$ git diff -- file # '--' 可以省略,如果当前文件不存在,则必须加上'--'
$ git diff HEAD [--] readme.txt 查看工作区和版本库里面最新版本的区别
$ git diff test 查看当前工作区和test分支的差别
$ git status 查看仓库当前的状态
$ git diff --stat 不查看具体详细差别,而是查看统计哪些文件被改动,有多少行被改动
不加参数的git diff
会比较当前工作目录中Git所跟踪的尚未暂存的文件和上一次commit版本间的差异(如果已经staged,又进行修改,则比较当前工作目录和index中的差别),如果要查看已经暂存的文件(如果新文件已经被add进index,则也会被比较)和上一次commit版本间的差异,使用git diff --cached
命令。使用git diff HEAD
来查看你当前的工作目录与上一次commit版本之间的所有差别。
git diff --cached(--staged同义词) [] # 比较当前index和commit间的差异,默认commit是HEAD,即上一次提交,如果HEAD不存在(如初始化后未进行任何commit的Git仓库),则显示所有index中的内容。
git diff比较分支之间的差别
$ git diff master..test # 比较分支master和test之间的差异(test相对于master的增量)
$ git diff master...test # 比较master和test的LCA(最近公共祖先)和test的差异(test相对于LCA的增量)
# 即如果A是B的父分支,则git diff A...B会显示出B分支自创建后的改变(因为LCA是A,即B相对于A的增量),而git diff B...A会显示空,因为A相对于A的增量为0\.
git reset
Reset current HEAD to the specified state.
git reset [-q] [] [--] ...
重置\指定的文件在index中的映像为它们在\中的样子(不影响工作区或当前分支),这意味着git reset
与git add
相反。然后使用git checkout
来将其从index改变到工作区.或者git checkout
指定\来直接一次将某个commit中的文件内容更新到index和工作区。
git reset [] []
这种形式的命令将当前分支HEAD指向\,根据\来更新index或者工作区。\的默认值是--mixed。\可以是以下值之一:
- --soft
不改变index和working tree。所有改变都处于'Changes to be commited'状态。
- --mixed
更新index,但不更新working tree,所有改变都处于'not marked for commit'状态。
- --hard
更新index和working tree,所有从\以后的已跟踪文件的修改都将被舍弃。
- --merge
重置index,更新工作区中从\到HEAD之间不同的文件
- --keep
与--merge类似。
git checkout
git checkout [-p|--patch] [] [--] ...
当\或者--patch给定时,git checkout
不会切换分支。它从index或者(通常是一个commit)指定的提交来更新工作区中\指定的文件。\参数能够被用来指定一个特定的tree-ish(也就是一个commit、tag或者tree),以便在更新工作区之前先更新index为该\的样子。
每一个commit都会对应一个commit id(版本号),它是由SHA1计算出来的一个非常大的数字,用来标识这次commit。它不仅单纯根据当前版本的文件内容,还包含了这次提交时的一切元数据(meta data),如提交时间等。
版本回退,用HEAD表示当前版本 上一个版本是HEAD,上上版本是HEAD^
往上100个版本写成HEAD~100。
$ git reset --hard HEAD^
$ git reset --hard cce56e9
如果上次的提交有错误,想要撤销。可以使用
$ git revert HEAD # 创建一个取消上次提交的commit
$ git revert HEAD^ # 创建一个取消上上次提交的commit,上次提交(HEAD)的修改仍会保留,如果在HEAD^^之后的commit和HEAD^^,双方修改了同一个文件,则会造成冲突,需要解决冲突
或者修改提交来修复错误,即利用--amend
参数。
git rm
从工作区和暂存区中删除文件
$ git rm --cached file # 仅从index中删除文件
$ git rm file # 从working tree和index中都删除,只能通过之前的commit恢复
$ rm file # 仅从working tree删除,可以通过git checkout -- file恢复
如果要删除的文件,与HEAD相比做了修改,则要使用--cached
保留本地文件,或用-f
强制删除。
git log
Show commits logs.
git log [] [] [[\--] ...]
默认不加任何参数,git log
会按提交时间从晚到早(从新到旧)列出所有的更新,会显示一个SHA-1校验和,作者名字和邮件地址,提交时间,最后一个缩进段落显示提交说明。
-p展开显示每次提交和之前上一次提交的内容差异,用-2仅显示最近两次更新。
$ git log -p -2
有些时候,行层面的对比满足不了要求,需要单词层面的对比,使用--word-diff选项。
$ git log -U1 --word-diff
对比显示在行间,新增加的单词被[+ +]括起来,被删除的单词被[- -]括起来。使用-U1来将上下文行数从默认的3行改为1行。
$ git log --stat
仅显示简要的增改行数统计。
$ git log --pretty=oneline
pretty选项指定格式显示历史,如oneline、short、full、fuller、format:等。
--pretty=oneline 每个提交用一行显示 更好看
format选项可以自定制要显示的记录格式,便于后续编程提取分析
$ git log --pretty=format:"%h - %an, %ar : %s"
常用的格式占位符写法和意义见:git-log format常用选项
作者和提交者有何区别呢?
作者指的是实际做出修改的人,提交者指的是最后将此工作成果提交到仓库的人。比如,当你为某个项目发布补丁,然后某个核心成员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者。
--graph选项添加了一些ASCII字符串来形象地展现分支、合并历史等。
其它选项:
1. --stat和--shortstat,显示统计信息。
2. --name-only和--name-status,前者仅显示已修改的文件清单,而后者还显示是M(修改)、A(添加)还是D(删除)。
3. --abbrev-commit,仅显示SHA-1的前几个字符,而非完整的40个字符。
4. --relative-date,使用较短的相对时间显示(如,“2 weeks ago”)。
5. -p,按补丁格式显示每个更新之间的差异。
限制git log
的输出长度:
$ git log - # 展示最近的n条记录
$ git log --since=2.weeks # 最近两周内的提交
$ git log --since="2008-01-15" # 从2008-01-15以后的提交
$ git log --since="2 years 1 day 3 minutes ago"
$ git log --author snk # 显示作者snk的提交 --committer类似
$ git log --grep search_text # 搜索commit message中的关键字
$ git log --all-match # 如果要得到同时满足2个搜索条件的提交,需要用--all-match选项,否则,满足任意一个条件的提交都会被匹配出来
$ git log -Sfunction_name # 找出添加或删除了某些字符串的提交
$ git log -- # 只关心某些文件或目录的历史提交
$ git log --no-merges # 非合并的提交
$ git log --no-color # 不显示颜色
与--since选项类似的还有--after、--until、--before等。
git reflog
$ git reflog
当你本地仓库中的分支(branch)或者其他引用被更新时,它会记录你的操作。它在帮助其他Git命令确定一个旧引用时十分有用。
使用git log -g
也可以查看所有的历史操作记录。
工作区 && 暂存区
工作区(working directory):在电脑里能看到的目录
版本库(repository):.git文件夹来维护git的版本仓库,存放了很多东西,最重要的是暂存区(stage、index),还有Git自动创建的第一个分支 master 以及 指向 master 的一个指针 HEAD。
git add
就是把文件修改添加到暂存区,
git commit
把暂存区的所有内容提交到当前分支。
每次修改,如果不add到暂存区,那就不会加入到commit中。
撤销修改,丢弃工作区的改动
$ git checkout -- readme.txt
两种情况:
readme.txt自从修改后还没有被放到暂存区,此时撤销修改就回到和版本库一模一样的状态;
readme.txt已经添加到暂存区,又做了修改,此时撤销修改就回到添加到暂存区时的状态。
$ git reset HEAD file
可以把送到暂存区的修改撤销掉(unstaged),重新放回工作区。
删除文件
$ git rm file # 来提交删除到暂存区 或者 直接使用 git add file
$ git commit -m "remove sth."
误删恢复:
$ git checkout -- file
远程仓库
GitHub配置
github网站是用来提供Git仓库托管服务的,那么github网站上的仓库就相当于是一个远程仓库。
本地Git仓库与GitHub仓库之间的传输是通过SSH加密的。SSH是
Secure Shell的缩写,即安全外壳协议,它是建立在应用层和传输层基础上的安全协议。
GitHub验证的验证方式有两种:
基于口令的安全验证 容易受到中间人攻击
基于密钥的安全验证 登录过程较慢
SSH配置:
1.创建SSH Key。用户主目录的.ssh目录下生成id_rsa和id_rsa.pub文件,id_rsa是私钥(不能泄露) id_rsa.pub是公钥(可以放心地告诉任何人),如果没有.ssh目录,需要通过以下命令来创建SSH Key。
$ ssh-keygen -t rsa -C "邮件地址"
2.登陆github, 在右上角的头像处,点击下拉菜单中的Settings,选择左侧的SSH and GPG Keys里面设置SSH公钥,点击new SSH key,在文本域中填入你的id_rsa.pub文件中的内容,填一个有标志性的title,然后点击Add SSH Key即可。
Github允许添加多个key,因此可以用若干台电脑(公司、家里等)来往Github推送。
在Github上免费托管的Git仓库,都是公开的,但只有自己可以修改。
如果不想让别人看到Git库,你可以:
1.付费使用GitHub的私有仓库。
2.自己搭建Git服务器。
SSH警告:
当第一次使用Git的clone或者push命令连接GitHub时,会得到一个警告。
这是因为Git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时,需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器,输入yes回车即可。
如果担心有人冒充GitHub服务器,输入yes前可以对照 https://help.github.com/articles/what-are-github-s-ssh-key-fingerprints/ 中的GitHub的RSA Key的指纹信息是否与SSH连接给出的一致。
相关Git命令
添加远程库
$ git remote add [remote-shortname(如origin)] [url(如ssh或https)]
git默认使用origin来标识克隆的原始仓库。
查看所有远程仓库
$ git remote [-v] # --verbose 显示详实信息
从远程仓库拉取数据
$ git fetch [remote-short-name]
从远程仓库remote-short-name中拉取所有你在本地仓库中还没有的数据,运行完成后,你就可以在本地访问该远程仓库中的所有分支,如remote-short-name/master,你可以合并该分支,也可以切换到这个分支一探究竟。
注意,fetch命令只是将远程仓库的数据拉取到本地仓库,并不自动合并到当前工作分支。当你确实准备好了,请手工合并。
$ git pull
如果已经设置了某个分支用于跟踪某个远端仓库的分支,可以使用该命令自动抓取数据下来,然后将远端分支自动合并到当前工作分支,实际上git clone
本质上就是自动创建了本地的master分支用于跟踪远程仓库中的master分支。
推送数据到远程仓库
$ git push -u [remote-name] [branch-name]
把本地的branch-name分支,推送到remote-name的服务器上。如果不指定remote-name和branch-name,克隆得到的仓库会自动使用默认的master和origin名字。
如果在你推送数据之前,已经有别人推送了若干更新,那么你的推送操作就会被驳回,你必须先把他们的更新拉取到本地,合并到自己的项目中,然后才可以再次推送。
由于远程库是空的,第一次推送master分支时,加上了-u参数,Git不但会把本地master分支内容推送到远程新的master分支,还会把本地的master分支和远程的master分支关联起来,为以后的推送或者拉取时就可以简化命令。以后,只要本地有了新的commit,就可以通过命令:
$ git push origin master
把本地master分支的最新修改推送至GitHub。
从远程库克隆
从GitHub网站上首先创建一个远程库,再利用命令git clone
克隆到本地库。
$ git clone [email protected]:sonack/RemoteRepo.git
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。
Git支持多种协议:默认的git://使用ssh,但也可以使用https等其他协议。但是https 速度慢,并且每次需要输入口令,比较麻烦,但在某些只开放http端口的公司内部就只能使用https.
分支管理
主分支一般为master分支,开发分支一般为dev分支。
严格来说,HEAD指向master,master指向提交commit。
命令小结:
查看分支:
$ git branch
创建分支:
$ git branch
切换分支:
$ git checkout
创建+切换分支:
$ git checkout -b
合并某分支到当前分支:
$ git merge
删除分支:
$ git branch -d
合并方式有fast-forward,即快速合并(直接把master指向dev的当前commit,速度非常快,时间复杂性是O(1)级别)。
解决冲突
git无法执行快速合并,只能试图把各自的修改合并起来,但这种合并就可能会有冲突。
$ git merge feature1
提示冲突 也可以用git status
查看冲突的文件,手动解决冲突后
$ git add file
$ git commit -m "conflict fixed"
$ git branch -d feature1
git使用 <<<<<<<、=======、>>>>>>>标记出不同分支的内容。
用带参数的git log
命令也可以看到分支的合并情况:
$ git log --graph --pretty=oneline --abbrev-commit
分支管理策略
通常,合并分支时,如果可能,Git会用fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果强制禁用fast forward模式,那么Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
$ git merge --no-ff -m "merge with no-ff" dev
因为这种合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
分支策略:
原则:
master分支应该是非常稳定的,也就是仅用来发布新版本,平时不在上面干活;
在dev分支上干活,dev分支是不稳定的,到某个时候,再把dev分支合并到master分支上。
每个人都在各自的dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并。
BUG分支:
工作还没完成,不能commit,必须要先解决bug.
或者可以用stash功能,把当前工作现场"储藏"起来,等以后恢复现场后继续工作。
在dev分支上没有被git管理的文件是不能被stash的(只有修改和暂存区等能被管理)
$ git stash
恢复
$ git stash apply 恢复但不删除stash内容
$ git stash drop 删除stash内容
或者用一条语句
$ git stash pop 恢复的同时把stash内容也删除
恢复指定的stash
$ git stash apply stash@{0}
多人协作
当从远程仓库克隆时,实际上Git自动地把本地的master分支和远程的master分支对应起来了,并且远程仓库的默认名称为origin。
查看远程库的信息
$ git remote
或者用git remote -v
显示更详细的信息。
其可以显示有权抓取(fetch)和推送(push)的origin的地址,如果没有推送权限,就看不到push的地址。
推送分支
推送分支,就是把该分支上的所有本地提交推送到远程库,推送时,要指定本地分支,这样Git就会把该分支推送到远程库对应的远程分支上。
$ git push origin master
$ git push origin dev
或者用
$ git push --set-upstream origin dev
需要推送的分支:
master是主分支,因此要时刻与远程同步;
dev是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,没必要推送到远程;
feature分支是否推到远程,取决于是否需要合作开发。
抓取分支
克隆别人的库只能看到master分支,要在dev分支上开发,必须创建远程origin的dev分支到本地,用下面的命令创建本地dev分支
$ git checkout -b dev origin/dev
解决冲突
拉取远程更新并合并
$ git pull
如果失败,则可能因为没有指定本地dev分支和远程origin/dev分支的链接,使用:
$ git branch --set-upstream-to origin/dev dev
或者用--track选项,然后再次git pull
。
手动解决冲突后,再提交(push)
$ git add conflictFile
$ git commit -m "fix the conflict on dev branch"
$ git push
多人协作的工作模式
试图用
git push origin
推送自己的修改;如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull
试图合并;如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用
git push origin
推送分支。
标签管理
标签是版本库的一个快照,本质上是指向某个commit的指针(和分支类似,但分支可以移动,标签不能移动)。
创建标签
$ git branch
$ git checkout master # 切换到需要打标签的分支上
$ git tag v1.0
git tag
默认是将标签打在最新提交的commit上的,可以使用下面的命令指定commit
$ git tag
查看所有标签
$ git tag
注意标签不是按照时间顺序列出的,而是按照字母序排序的。
查看标签信息
$ git show
创建带有说明的标签,用-a指定标签名,-m指定说明文字,创建用私钥签名的一个标签,用-s签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报错。
删除标签
$ git tag -d v0.1
推送标签到远程
$ git push origin
一次性推送全部尚未推送到远程的本地标签
$ git push origin --tags
删除远程标签
先从本地删除,
git tag -d v0.9
然后从远程删除,删除命令也是push,即
git push origin:refs/tags/v0.9
GitHub
在fork开源仓库后即拥有其读写权限(自己的仓库),可以通过推送pull request给官方仓库贡献代码。
杂项
忽略特殊文件
git工作区的根目录下创建一个特殊的文件.gitignore文件,详情可以参考我的另一篇博文.gitignore文件解析或者参考官方文档,这里是一些常用的.gitignore文件的写法。
忽略文件的原则:
忽略操作系统自动生成的文件,比如缩略图等;
忽略编译生成的中间文件,可执行文件等。
忽略带有自己敏感信息的配置文件,比如存放口令的配置文件等。
然后可以把.gitignore文件放到版本库里,并且可以对.gitignore文件做版本管理。
搭建Git服务器
1.安装git
$ sudo apt-get install git
2.创建一个git用户,用来运行git服务
$ sudo adduser git
3.创建证书登陆
收集所有需要登陆的用户的公钥(id_ras.pub文件),把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。
4.初始化Git仓库
先选定一个目录作为Git仓库,假定是/srv/sample.git。
在/srv目录下输入
$ sudo git init --bare sample.git
Git就会创建一个裸仓库,没有工作区(bare意为空的,赤裸的)。
因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登陆到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。然后把owner改为git用户
$ sudo chown -R git:git sample.git
5.禁用shell登陆
6.克隆远程仓库
$ git clone git@server:/srv/sample.git
管理公钥:
如果团队人员很多,可以用Gitosis;
管理权限:
Git不支持权限控制,但支持钩子(hook)机制,所以可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这种工具。
最后附上一张GitCheatSheet,愿君学有所成!