最近新的项目需要抛弃svn使用git,之前对git一脸懵逼仅限于会拼写,趁这几天不算忙学习记录下。
常用命令
git clone url
clone项目代码到本地git add file_name
添加修改的文件到暂存区git commit -m "command"
将暂存区的修改提交到本地仓库git push origin branch_name
将本地仓库的某个分支更新到远程仓库git status
查看当前所在分支的状态git reset --soft/--hard/--mixed commit_id
回退到某次提交的状态(多人协作开发不建议使用)git revert commit_id
回退某次提交的修改,会产生一个新的commit,不改变原来的commit记录git branch branch_name
新建一个分支git checkout branch_name
切换到某个分支git branch -b branch_name
创建并切换到该分支git merge branch_name
将某分支合并到该分支git branch -a
查看本地和远程的所有分支git branch -d branch_name
删除本地分支git push origin -d branch_name
删除远程分支git log --oneline --decorate --graph --all
查看分支详细信息git submodule init
第一次clone带有子模块的项目时需要进行子模块配置的初始化git submodule update --remote
更新子模块
基础
git与svn最大的一个区别就是对于提交文件的存储方式,对于每次提交,svn是存储文件的差异,而git是存储文件的快照,用官网的两张图可以更清楚的看出区别:
svn:
git:
这样子做有啥子好处呢?官网有具体的说明,但是我也没有理解到底有啥好处…感受就是对于同一份项目代码,从svn上clone下来花的时间比git clone下来的时间慢的多,不知道是我司对git服务器有加速?还是这种存储方式导致的。
首先需要明确的一个概念是:对于一个repository,可以说有两个存储仓库,一个在本地,一个在远程服务器,每次修改的都是本地存储,只有在执行push指令之后才会将本地的存储推(更新)到远程存储,所以只要不push,什么add,commit可劲造,不会影响到别人,造完记得reset还原就行。
还有一个概念也要提一下,每次修改文件到提交到本地仓库,其实这个文件经历了三个存储区域(三种状态)
- 第一个是工作区域,就是存储原始文件的地方,对代码的各种修改都是在这个区域下完成的。
- 第二个是暂存区域,修改之后必须要经过暂存区域才能将文件存储到本地仓库,可以将其理解为一个缓冲。修改之后执行git add指令就会将指定文件放到暂存区域了,我一般直接在根目录下
git add .
它会递归的在这个目录下寻找所有修改过的文件都添加到暂存区域。当然如果有一些文件你不想提交就要挨个add了。官方的说法这个区域是确实存在的,格式是一个文件,里面存放了add过(待提交)的文件列表信息,默认这个文件存放在git仓库目录中。 - 第三个就是你的本地仓库,
git commit
就可以将文件从暂存区域提交到本地存储了。只有将改动提交到本地存储,你才能推到远程仓库。
还有一个非常实用的命令git status
,可以很清楚的看出你当前的文件都处于一个什么状态,当然你从修改完到add到commit到push一气呵成,这个指令确实没什么用。但是如果一气呵不成流程被中断了,这个指令就比较有意思了。
当新建了一个test.txt文件,没有add时,运行
git status
,git bash会显示:
untracked表示没有跟踪,对于跟踪的概念官网有官方的解释,可以简单粗暴的认为,没有跟踪的就是新建的文件。这时候咋整,add->commit->push。大白话来说就是这个文件git在现有仓库中没有见过,啥叫见过,clone之后git会在本地建一个数据库,以文件内容或目录结构进行一个SHA-1校验和的计算,将这个值作为该文件的索引。当改了文件内容,保存了没有add,或者add了没有commit,此时
git status
显示:
changes not staged for commit就是改了没有add到暂存区域,add->commit->push。
changes to be committed就是说已经add到暂存区域,可以commit提交了,commit->push。
如果你不想提交add过的修改咋整,看括号git restore --staged
,改过但是没有add,git restore
会将这个文件还原成上一次提交的状态,相当于一键ctrl-z,git提示简直不能太友好啊有木有!当commit了但是没有push,
git status
显示:
很直观,有一次提交,还没有push到远程。
那如果说这个时候你提交过的有问题,或者还需要加东西要撤销本次提交,不想push到远程祸害别人,不要慌,git reset
了解下。有三个常用参数
--soft
只是撤销这次commit,改过的内容不会恢复,这个时候可以重新git commit
进行提交,或者在这个代码基础上新增改动,然后add->commit。--hard
就比较粗暴了,直接将代码完全还原成上一次提交的状态mixed
这是默认的参数,作用是移除暂存区,相当于到达add之前的状态,同样不会改变工作目录的代码。
但是在和别人合作完成项目的时候不建议使用git reset
,因为它会使HEAD指针前移,覆盖之后的提交记录。直白的说就是会删除提交历史记录,这样会导致一个严重的问题,没办法恢复撤销。常用的撤销是git revert
,它会撤销你做出的某次修改,但是会将这次撤销作为一个commit进行提交,而不改变提交历史记录,常用的参数:git revert HEAD
撤销上一次的提交git revert commit_id
撤销某次的提交
实验中发现,如果是跳跃的撤销连续增加的情况就会有冲突问题需要解决,比如说我这里提交了3次,分别是增加了1111,2222,3333:
如果我想撤销2222的那次提交,执行git revert 2e6b6e430cfaf444e2a6b6191bf8b9a72292dd96
就会出现冲突提示:
需要手动处理下冲突。
当涉及文件重命名的时候也比较有意思,如果直接在工作目录下进行重命名,git status
会发现状态是将原有文件删除,新增一个untracked未跟踪文件,你需要将改名前的文件和改名后的文件都add一下然后commit。这里有一个更好的方式是直接在git命令行中运行git mv old_filename new_filename
,再git status
就会发现状态是renamed,直接add该文件就可以提交了。
分支
不管你信不信,官网是吹爆了git的分支机制,称为git的必杀技。嗯,我还没有悟透,除了拉分支秒完成以外还没有发现它牛X在哪。
当从远程clone下来之后,git会在本地创建一个master分支,跟踪到远程仓库的master分支(origin/master),可以直接在master分支上进行开发,但是git不建议这样,因为master一般存放的是稳定可交付的一些提交,多人协作开发时你提交了一些测试状态的代码到master上对别人会有影响。建议自己在本地拉一个dev分支,在这个分支上进行开发,开发完成测试ok之后再merge到本地仓库的mater中然后push到远程仓库。这里其实不需要提交dev分支到远程仓库,这个前提是你有一个环境可以拉取到本地dev分支的代码并且可以运行。
当创建一个分支时,git只是创建了一个可以移动的新的指针,使用git branch branch_name
即可创建新的分支,此时新分支与master分支的指针均指向当前最后一次提交对象的位置,git还有一个特殊的指针HEAD来区分此时在哪一个分支
如果想切换到某个分支,需要执行git checkout branch_name
,这样HEAD指针就会指向你切换到的分支。也可以执行git branch -b branch_name
,它表示创建一个分支并且切换到该分支上去,相当于git branch branch_name
+git checkout branch_name
。切换到指定分支后,所有代码修改提交都是在该分支上,每次提交当前分支指针后移,其他分支指针不动
当开发完成自测通过后需要合并到master分支上,此时需要切换当前分支到master,然后执行git merge branch_name
,这样就会把branch_name分支的改动合并到当前所在的master分支上,合并时可能会发生冲突,因为可能你某个时候改过master分支
此时需要手动去处理冲突,处理完成后就是add->commit->push了。但是!但是!细想下这里是不是也可能有问题,你一直都在修改本地仓库,可能在你上一次git pull
之后,别人又提交过新的修改到远程master分支了,这个时候怎么可能正常push。对头,这里我专门做了这个case的测试,发现git会报错
黄色提示的很明白了,有人更新过,你需要再执行一次git pull
才能正常push。但是!但是!这里是不是也可能会有冲突,别人提交的和你将要提交的冲突,pull会失败。亲测后发现是的,再手动解决冲突就好了
所以要留心每次执行命令后的提示,有没有fail这个字眼。我用的vscode开发,发现安装各种git插件后简直香的飞起,冲突显示的也很友好,尤其是git提交日志,当前在哪个分支,远程分支是什么状态,改了哪些文件,分支合并状态一目了然。奥,还有add->commit也很方便。
需要知道当前分支的提交状态或者树结构的话可以执行git log --oneline --decorate --graph --all
,结果大概长这样(vscode真香)
使用git branch
可以看到当前的本地仓库都有哪些分支,我一般还会加-a
参数,这样可以看到本地和远程的所有分支,--merged
参数表示哪些分支已经合并了,对应的就有没有合并分支的参数--no-merged
,合并过的分支如果不在需要就可以删除,删除本地仓库的分支使用git branch -d branch_name
,-D
是强制删除,一般没有合并过的分支是不允许删除的,如果某个分支你只是用来测试或者瞎搞确实没什么用了就可以使用-D
进行删除。删除远程仓库的分支使用git push origin -d branch_name
子模块
在项目中需要使用第三方库,这个库也是一个独立的项目时,最好在项目中引入git子模块。
在一个已经存在的git项目中需要添加子模块时,使用git submodule add project_url
。此时会在当前目录下新建一个.gitmodules的配置文件,存储了拉取的项目存储到本地的目录与该项目的url地址的映射。
克隆含有子模块的项目时默认会包含该子模块目录,但是目录下没有任何文件,需要cd到子模块目录下执行git submodule init
来初始化本地配置文件,然后git submodule update
进行抓取子模块的所有数据。也有一种更简洁的方式,在clone时加入--recursive
参数就可以省略后面的两步,它会自动初始化并更新仓库中的每一个子模块。
在使用子模块时,比较好的一个方法是将自己视为对子模块的代码只有只读权限,这也很好理解,你的项目引入的是一个第三方库,项目不止你一个人在维护,你改了第三方库对别人肯定会有影响。
我们当前的项目用到的子模块也是自己开发的,所以我一般在子模块初始化完成后的日常开发中只跑几条指令:
git submodule update --remote
更新子模块git status
查看状态git add filename
git commit -m "comand"
git push origin master
push的时候如果有冲突了就git fetch
->git merge origin/master
一下,或者直接git pull
。在这里我的位置是子模块对应的项目的开发者,当然要对子模块进行读写,如果你只是引入一个第三方库,就只需要第一条指令就可以,不要独自修改子模块的代码!