写博客其实是一件非常反人性的事情,因为写博客需要占用自己大量的时间,而且这些时间本可以用来休息或者学习。但是写博客可以帮助自己深刻地理解相关知识点已经背后的原理,而且一段时间之后如果忘记了相关知识点,可以立马掉过头来复习,节省了很多时间。但是人性是懒惰的,是好逸恶劳的。每次写博客都是一次与自己的战斗,与自己的懒惰、好逸恶劳战斗!
希望自己还是能坚持坚持学习写博客吧。简单的事情重复做,贵在坚持。坚持下去,量变总有一天会引发质变。
所以,我们今天还是来写一篇博客,虽然这篇博客比较水。
由于公司在用Git管理新项目了,而其他老项目又是用的SVN,自己老是忘记Git的命令是怎么写的,所以本篇简单记录下,方便查阅。
Git是分布式的,每个人的本地仓库都包含了完整的版本库。虽然是分布式的,但是我们还是会找一个服务器来托管仓库(比如GitHub),方便团队成员(有可能来自世界各地)从该服务器拉取、提交代码。虽然Git这样使用(加一个托管服务器)看起来好像是中心化的,但是不是!每个团队成员都可以向其他某一团队成员推送代码、拉取该其他成员的修改,Git是点对点的。举个例子:
如上图中,团队成员A可以直接向团队成员B推送代码,或者拉取B的修改。团队成员B也可以直接向团队成员D推送代码,或者拉取B的代码(上图中为了美观没有画出B和D的直接连接箭头)。
SVN不一样,团队成员只能和中心服务器交互,团队成员之间是不能互相推送、拉取代码的,如下图。(图中,我把SVN中心仓库画得大一点,表示中心仓库很重要,但是Git的托管仓库不怎么重要)
在Git中,每一个版本保存的都是该版本下的所有文件。可以简单理解为每一次提交就是对所有文件的(注意是所有文件,不是只有修改的文件)一次备份。
在 Git 中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 快照流。
这一点是Git与SVN的最大不同,SVN记录的是是一组基本文件和每个文件随时间逐步累积的差异 。
新建一个Git仓库,会自动创建一个.git的隐藏文件夹,里面存储的是Git的版本库。其他人只要拿到.git文件夹,就可以还原我们的整个提交历史。
.git文件夹外面就是Git仓库的工作区,所有需要被该Git仓库管理的文件和文件夹都需要放到工作区(也叫Working tree)里面。
除了.git版本库和git工作区,还有一个暂存区(Staging Area),用来记录下一次提交的内容。暂存区(Staging Area)也叫索引(Index)。
举个例子:我们在工作区新建了一个文件README.md,然后需要用命令将README.md添加到暂存区中,最后再提交(commit)。
提交的一定是暂存区中的内容,没有添加到暂存区的修改是不会记录到本次提交的。
HEAD是一个指针,指向我们当前检出的分支。
比如我们通过git log命令看提交日志的时候,可以通过HEAD知道我们当前检出(check out)的分支。
origin是服务器托管仓库的简称,origin是默认的名字,也有使用upstream。
举个例子,我们服务器托管仓库的地址为[email protected]:betty-remote/netauto.git,那么为了在使用命令时不用写这么一长串地址,可以通过命令将这一长串地址设置为一个别名,如这里的origin。
origin:源,表示这个仓库是从哪儿来的。
remote是远程的意思,用来该信息是远程主机的,不是本地主机的。
FETCH_HEAD:指向从远端获取的最新的提交。
Git最强大的一点就是它的分支管理。我们在开发中,可能会创建出很多的分支,比如master主分支、develop开发分支、各种feature功能分支、hotfix修复bug分支以及用来发布版本的release分支。
需要注意的是,在Git看来所有分支都是一样的,没有任何区别。比如在GitHub创建一个仓库,将会默认创建一个master分支。那master分支有什么特别吗?没有!在Git看来所有分支都是一样的,Git并不会因为这个分支名字是master就区别对待它。只是大家都用习惯了第一个分支命名为master。
当然,Git分支需要规范化的管理,具体可见我上篇的文章《Git工作流(分支管理规范)》。
在工程中,并不是所有文件都需要保存到版本库中的,例如Unity项目中的Library目录及目录下的文件就不需要保存在版本库中。
在Git工作区的根目录下创建一个的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件或目录。
Unity项目的.gitigonre推荐模板,见GitHub。
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
#
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
# MemoryCaptures can get excessive in size.
# They also could contain extremely sensitive data
/[Mm]emoryCaptures/
# Recordings can get excessive in size
/[Rr]ecordings/
# Uncomment this line if you wish to ignore the asset store tools plugin
# /[Aa]ssets/AssetStoreTools*
# Autogenerated Jetbrains Rider plugin
/[Aa]ssets/Plugins/Editor/JetBrains*
# Visual Studio cache directory
.vs/
# Gradle cache directory
.gradle/
# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta
# Unity3D generated file on crash reports
sysinfo.txt
# Builds
*.apk
*.aab
*.unitypackage
*.app
# Crashlytics generated file
crashlytics-build.properties
# Packed Addressables
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*
在当前目录新建一个Git仓库。
git init .
从托管服务器上拷贝仓库(包含所有分支)到本地。
git clone [email protected]:betty-remote/netauto.git
从托管服务器上拷贝仓库中的指定分支,如下面例子为只拷贝develop分支到本地。
git clone -b develop [email protected]:betty-remote/netauto.git
本地创建分支,创建完成后需要检出到该分支,才能在该分支上开始工作。
git branch hotfix
git checkout hotfix
当然这两个指令可以合并为一条指令。创建并立即检出到hotfix分支。
git checkout -b hotfix
然后推送本地分支到托管服务器。
如下面指令将本地的hotfix分支推送到origin主机的hotfix分支。如果origin主机的hotfix不存在,则会被新建。
git push origin hotfix
上面这个push语句其实是git push origin hotfix:hotfix的简写。更详细的push指令见git push小节。
查看本地分支。
git branch
*开头的分支名表示我们当前所在的分支。如下图,我们本地有两个分支,master分支和develop分支,当前处于master分支。
查看远程主机的所有分支。-r中r,即remotes的缩写。
git branch -r
git branch -a
Git告诉我们本地只有一个master分支,远程的origin主机也只有一个master分支。
删除本地分支。 -d即–delete的缩写。
git branch -d develop
上面这条指令将会删除本地的develop分支。需要注意无法删除当前检出的分支,如果本身就在develop分支,执行上面这条语句是会提示无法删除的,必须切换到其他分支才可删除develop分支。
删除远程分支。
如果本地该分支有修改但还未提交,也不能删除。此时需要提交修改之后再执行或执行强制删除(将-d改为-D)。
git branch -D develop
删除远程分支。
git push origin -d hotfix
从仓库拉取本地没有的分支。
git checkout -b 本地分支名 origin/远程分支名
若拉取不成功,先fetch以下,再拉。即:
git fetch
git checkout -b 本地分支名 origin/远程分支名
将工作区文件的修改添加到暂存器。
git add . 指令将添加当前目录的所有文件到暂存区。
git add .
用于查看当前工作区的状态。
git status
举个例子:
目前我们没有任何修改,执行git status,返回的信息告诉我们当前在master分支,工作区是干净的没有修改。
然后修改一下README.md,并添加一个test.cs脚本。执行git stauts,然后git会告诉我们README.md被修改了,但是还没有添加到暂存区,需要用git add指令添加到暂存区以便下次提交包含此文件,或者使用git restore指令丢弃工作区该文件的修改;还告诉我们test.cs文件没有被追踪,需要使用git add指令将其加入Git的版本管理中来。
处理完成后再执行git status,Git告诉我们以下文件将会记录在下次提交中,可以使用git resotre --staged指令将其取消暂存。
提交暂存区的修改。后面的 -m “xxx”,就是本次提交的日志。
git commit -m "提交日志"
如果忘记了后面日志,只执行了git commit,Git会使用打开默认的vim编辑器,然后叫你输入日志。
这时需要如下操作:
将本地分支推送到远程主机的指定分支。
git push <远程主机名> <本地分支名>:<远程分支名>
如git push orgin develop:develop,就是将develop分支推送到远程主机的develop分支。
如果本地分支名与远程分支名一样,可以将<本地分支名>:<远程分支名>简化为<本地分支名>。
即git push origin develop。
如果当前分支只有一个追踪分支,那么主机名都可以省略,直接使用git push即可。
fetch是获取远程分支的修改。
pull是拉取远程分支的修改。
fetch和pull是有点差别的,git pull实际上是先git fecth,然后再执行git merge,即先获取然后合并。
获取远程主机origin的所有分支。
git fecth origin
获取origin主机的develop分支。
git fetch origin develop
然后本地切换到develop分支。
git checkout develop
然后合并远程主机的develop分支修改。
git merge oringin/master
或
git merge FETCH_HEAD
git pull <远程主机名> <远程分支名>:<本地分支名>
如果远程分支是与当前分支合并,则冒号后面的部分可以省略:
git pull origin develop
上面这条语句的意思就是拉取develop分支:即先获取origin主机的develop分支,然后合并到本地的develop分支上。
git pull 和 git pull origin的区别:
git pull origin develop
git pull origin develop 动作是去获取远程仓库中 develop 分支上的 commits,然后把origin/develop merge 到你目前 checkout 下来的分支中
git pull
git pull 就是去你之前 checkout 的分支上去操作,比如,如果你本地的 checkout 的分支track 的就是 origin/develop,那么 git pull 就等于 git pull origin develop
合并分支。
下面指令将hotfix分支的修改合并到当前分支。
git merge hotfix
合并分为快速前向合并(Fast-Forward)和三路(3-Way)合并。
比如,我们当前再master分支,现在想合并SDN分支的修改到master分支。
我们先切换到master分支,然后执行git merge SDN,就会执行一个快速前向合并。
因为未合并前master分支指向的提交点是SDN分支的祖先节点,那么只需要将master指针指向SDN指向的提交点即可,不用做其他任何额外工作。
Fast-Forward合并前:
Fast-Forward合并后:
比如我们想在想把auth分支的修改合并到master分支来。
合并前的提交历史如下图。
此时我们在master分支执行git merge auth,将会执行3路合并。所谓的3路是指哪3路呢?指两分支所在的祖先节点以及两分支分别所在最新的节点。
3路合并就是把这三个提交点的修改合并,然后创建一个新的提交。
所以,需要注意的是,3路合并将会创建一个新的提交(commit)。
3路合并有可能会产生冲突,这是合并指令会暂停,手动处理完毕冲突后(然后使用git add . 并git commit -m “xxx”)会继续执行合并。当然遇到冲突也可以直接中断并丢弃本次合并,此时需要使用
git merge --abort指令。
git merge --abort
rebase中文翻译为变基,改变基础。
指令如下。
git rebase master
假设当前分支为develop分支,即将develop分支的提交在master分支上重放一遍。这听起来有点不知所云,没关系,咱们看看rebase到底是怎么工作的。
git rebase 和 git merge的功能类似,同一目的都是将其他分支的修改合并到当前分支。不同点在于提交记录不同。
我们上面讲过,git merge在执行3路合并的时候会产生一个额外的新的提交。在多人同时开发的时候,很多人同时使用git merge将会时提交信息看起来非常凌乱。比如这样。
而使用rebase来合并可以让我们提交历史保持在一条线上,就像这样。
git rebase指令并不是直接执行合并,而是执行“重放”。
比如我们想把experiment分支的修改rebase(变基)到master分支来。
我们先将当前分支切换为experiment分支,然后执行变基(rebase)。
git checkout experiment
git rebase master
rebase的原理如下:
首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master) 的最近共同祖先 C2,然后对比当前分支相对于该祖先C2的历次提交,提取相应的修改并存为临时文件, 然后将当前分支(experiment)指向目标基底 C3, 最后以此将之前另存为临时文件的修改依次应用。
就相当于将experiment分支的修改在master分支上重放一遍,然后再将experiment分支执向rebase后的提交。
最后,需要再切换为master分支,然后执行快速合并。
git checkout master
git merge experiment
只能在本地分支执行rebase,切记不能在远程主机的分支上执行rebase!
只能在本地分支执行rebase,切记不能在远程主机的分支上执行rebase!
只能在本地分支执行rebase,切记不能在远程主机的分支上执行rebase!
比如我们想把feature/player分支的修改合并到develop分支,使用rebase的工作流如下:
git checkout feature/player
git rebase develop
git checkout develop
git merge feature/player
即:
git checkout feature/player
git rebase develop
git checkout develop
git merge feature/player
git stash指令用于解决如下这种情况:
比如我们正在develop分支上开发某一新功能,但是突然需要修复一个紧急Bug,但是新功能还没开发好,不能提交,此时,我们想把当前的工作区内容给存起来,等把紧急Bug修复完毕后再来接着开发。
git stash指令就是用来缓存我们当前的工作内容的。
缓存当前工作内容。
git stash
可以使用git stash save "xxx"指定缓存日志。
git stash save "缓存(贮藏)日志"
查看缓存的列表。
git stash list
比如,我们这里只有一个缓存,编号是0(stash@{0}),是在master分支上的29efbb0那次提交执行的缓存。
应用最近的缓存。
git stash apply
应用特定缓存。
git stash apply stash@{x}
删除最新的缓存。
git stash pop
git config --global 配置全局用户名和邮箱
git config --global user.name "John Doe"
git config --global user.email [email protected]
查看提交日志。
–online 每次提交只用一行展示
–graph 用图形展示
-x 指看最新的x条提交
git log --oneline --graph -x
挑选一个或多个提交合并到当前分支。
单个。
git cherry-pick 想合并的提交哈希
多个。哈希之间用空格隔开。
git cherry-pick 想合并的提交哈希1 哈希2
连续多个(需注意哈希2一定要晚于哈希1)。
git cherry-pick 想合并的提交哈希1..哈希2
有时合并过程中,会有冲突。此时,有3种操作:
①手动解决冲突,然后继续执行cherry-pick命令
// 手动解决冲突,然后提交到暂存区
git add .
// 继续执行合并
git cherry-pick --continue
②中止cherry-pick,回到执行cherry-pick前的状态
git cherry-pick --abort
③中止cherry-pick,但保留当前的的状态(车祸现场)
git cherry-pick --quit
配置全局用户名和邮箱。
git config --global user.name "username"
git config --global user.email "email"
TODO
git checkout .
之前公司很多项目是用SVN来做版本管理的,SVN做版本管理有个非常致命的缺点是不能离线提交代码,但是有时出差到客户那里,由于客户的保密要求电脑不能联网,此时SVN无法使用,此时就需要将SVN仓库切换为git来管理。
git svn
指令就是来做这件事的。
常用git svn工作流如下:
1.拉取仓库
git svn clone svn的地址
2.然后按照git的工作流程进行工作(创建分支开发,如develop分支)
3.功能开发完成后,切换回master分支并拉取svn仓库的修改,然后将develop分支的修改rebase到master分支
// 切换回master分支
git branch master
// 拉取svn仓库的修改
git svn rebase
// 将develop分支的修改rebase到master分支
git checkout develop
git rebase master
git checkout master
git merge develop
4.上传修改到SVN仓库
git svn dcommit
参考 git与SVN协同的工作流程
SVN仓库的ip地址更改了,此时我们需要更新git svn创建的git仓库的url。
修改git仓库中的.git\config文件中[svn-remote “svn”]下的url和rewriteRoot项,url改为新的ip地址,rewriteRoot为旧的ip地址。
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[svn-remote "svn"]
url = svn://x.x.x.x/unity/demo/branches/client
rewriteRoot = svn://192.168.1.110/unity/demo/branches/client
fetch = :refs/remotes/git-svn
如果SVN仓库的地址变了,但本地git的url未更改,是用git svn dcommit
时会报如下错误
unable to determine upstream SVN information,参考StackOverflow。
如下图,想将最新的2个commit(即AI和NPC类的提交)合并为一个。
此时可以这样操作。
git rebase -i HEAD~2
然后单击键盘上的i,进入编辑模式。
将第一个后面的commit信息修改为fixup,即保留commit信息,舍弃commit提交日志。
修改完毕后,输入:wq保存并退出。
此时再看日志,会发现两个commit已经合并为一个了。
有时可能需要修改下提交日志,如下图操作,StackOverFlow。
另外,再执行rebase时可能会有冲突,如果我们想保留其中一个分支的修改舍弃另一分支的修改,该如何操作呢?
1.先使用git rebase --abort
终止当前的rebase操作
2.再重新执行rebase,只不过这次需要加上参数-X theirs 或者 -X ours
git rebase -X theirs develop
当有文件冲突时,是保留当前分支的修改,丢弃develop分支的修改git rebase -X ours develop
当有文件冲突时,是保留develop分支的修改,丢弃当前分支的修改举个例子:
在feature分支git rebase develop之后,即将feature分支的修改变基到develop后,想将变基之后的feature推送到远端仓库,此时若只执行git push origin feature,git会提示push无效。
这种情况下,可以是用git push --force-with-lease origin feature来将本地的rebase推送到远端仓库,避免使用git push --force origin feature。
参考Git Force vs Force with Lease
TODO
TODO
博主本文博客链接。