1.创建仓库、commit、查看状态、版本跳转
git只能跟踪文本文件的改动;
在合适的目录里建 仓库(版本库),里面的文件被git管理起来,所有文件的修改、增加、删除会被git记录,随时追踪,需要的时候还原,新建命令:
这样就可以在当前位置下新建一个空的 .git 文件夹,这就是git仓库了。
向git仓库中添加文件:
把文件提交到仓库:
git commit
git commit -m
"备注"
|
其中,commit可以一次提交很多文件,所以可以先用add增加多个文件,再用commit 一次性提交,比如:
git add file1.txt
git add file2.txt
git add .
git commit
|
查看当前状态:
这个命令可以用来查看当前git仓库的状态,比如如果你的文件 readme.txt 当前有一个变化(文件修改),不过还没有提交或add,它的信息是这样的:
还可以查看具体的变化:
结果将以”+-“号的形式给出,比如在上面的例子中,可见是文件里增加了一个“distribute”字段:
还可以查看之前几次commit的情况:
git
log
git
log
—pretty=oneline
|
每个提交的版本都有一个版本号,另外还可以用HEAD代表当前版本,规律如下:
HEAD
HEAD^
HEAD^^
HEAD ~2
HEAD ~100
|
使用git reset命令来退回到之前的版本:
git reset HEAD^
git reset 3628164
|
这时再使用git log命令,就看不到最新的那个commit了,想再换回去最新的版本,除了往上翻窗口记录找到版本号,还可以使用git reflog命令, 会显示之前的所有命令和执行那条命令时候的版本号,这样就可以知道回退之前的版本号,可以再使用reset命令切换回去:
head指针的概念:
2.工作区、版本库、暂存区、删除、撤销
工作区即是.git文件夹所在目录,我们在这里工作、编写。
.git即是版本库,中间存放着stage(暂存区)、master分支以及指向master的HEAD指针:
结合这个概念,则1中的命令有如下意思:
git add readme.txt
git commit
|
提交就是向当前所在分支提交,当前我们还没有新建别的分支,所以是向master分支上提交。
commit之后,如果没有再对工作区修改,那么这时候暂存区应该是空白的:
只有先放到了暂存区,才可以提交成功。
每次修改,如果不add到暂存区,那就不会加入到commit中,如果在第一次修改之后放到暂存区,然后又做了修改,之后没有add立即提交,则这次提交不会记录第二次的修改。
用checkout--命令来对修改进行撤销:
git checkout --readme.txt
|
checkout后面"--"不可少,否则就变成了切换分支的命令。
如果直接想将已经在暂存区的修改一并撤销,还可以使用reset的另一种用法,将已经存入暂存区的修改撤销,也就是将暂存区清空:
git reset HEAD readme.txt
|
如果已经commit了,还想要撤销,就只能使用之前的reset命令回退版本了;如果已经推送到了远程库,那就没办法了。
删除文件也是一种修改,所以如果当前工作区commit 过,之后在工作区删除了文件的话,这时工作区和版本库已经不一样了,可以通过回退版本的方法来找回这次在工作区删除的文件。
如果真的想删,可以直接用rm命令,将已经commit到版本库的那个文件彻底删除:
git rm readme.txt
git commit
|
如果删错了,自然可以使用之前的checkout --命令来回到最新版本,或者使用reset回之前到任何一个版本,不过这次除了删除之外的修改,也不再保留了。
3.远程仓库
一般使用git的情况:
一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的最新提交。
所以,就分为本地仓库和远端仓库两个概念,
远端库的名字一般叫做"
origin"。
可以自己在本地建立一个仓库之后,再在远端建一个,然后将二者关联:
git remote add origin git@github.com:michaelliao/learngit.git
|
关联之后,就可以将本地库的东西推送到远端库去了,可以作为备份,也可以供多人协作开发:
git push -u origin master
|
把本地库的内容推送到远程,用git push命令,实际上是把所在的当前分支推送到远程,这其中包含了merge的操作;
这是第一次推送,由于远程库是空的,我们第一次推送master分支时,加上了
-u 参数,Git不但会把本地的master分支内容推送到远程新的master分支,还会
把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
这样做之后,可以立刻看到远程库的内容已经和本地一模一样。
在这之后,只要本地的修改进行了本地的提交(commit),就可以再将它推送到远端的版本库了:
更推荐的做法,是先在远端建立仓库,然后拉到本地进行工作(从远程库克隆),命令是git clone:
git clone git@github.com:michaelliao/gitskills.git
|
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了(克隆的是远端的最新commit)。
4.分支的概念
之前偶尔提到分支的概念,现在就来具体解释一下,下面的图来自网络,个人感觉这种图形有些歧义,这里先暂时贴一下。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
创建新的分支并切换过去,如dev,git就将新建指针叫做dev,然后将这个指针指向和master相同的提交:
所以HEAD指针就是说明当前在哪个分支的哪个commit。
保持当前的状态,继续开发,则就是在dev分支上进行的了,如果再进行提交,也就是dev前进一步:
如果在dev分支上开发完成,就可以将两个分支合起来,一个简单的方法就是让master分支向dev分支的当前提交,其实也就是将master指针换个指向的问题:
这叫做快速合并,之后还会提到具有冲突的合并情况。
之后直接删除dev分支,完成这次开发:
相关命令行语句:
git branch dev
git checkout dev
git checkout -b dev
git branch
git checkout master
git merge dev
git branch -d dev
|
其中git branch命令会列出所有分支,当前分支前面会标一个 *号,如下:
因为创建、合并和删除分支非常快,所以Git鼓励使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
5.冲突(conflict)的概念
从master分支新建分支feature1,在feature1中修改并提交(commit),回到master分支,在相同位置再做其他修改并提交,现在两个分支都做了修改并有新的提交,试图将两个分支合并,将会出现冲突:
出现冲突导致无法合并,是因为这两个commit是在
相同的位置做了不一样的修改,git无法判断应该留下哪个,如果是不同的地方的修改,git会将两个修改都保留下来并合并成功。
冲突的信息如下,git会让你解决冲突:
用编辑器查看具体的文件内容,会用符号标出冲突的位置:
这里就可以看到两个commit都修改了同一个地方,让Git不知道怎么合并,所以这时需要我们手动的去解决冲突。
可以在两处修改中,去掉在master中修改的地方,或者直接将两者都保留,但将git增加的标记符号删掉,表明master是做了两处修改,其中包含feature1上的修改,
再次提交之后,就重新执行merge命令,合并成功了。
所以,合并时候的重点是:
如果有冲突,解冲突,然后再合并。
PS:个人理解,合并分支就是将二者的最新commit指针指向同一个修改。
在实际开发中,我们应该按照几个基本原则进行分支管理:首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都拉取dev分支到本地,然后在本地的dev分支上干活,每个人都有自己的分支,时不时地往远端的dev分支上推送并合并就可以了(当然有时候会需要解决冲突)。
所以,团队合作的分支看起来就像这样:
6.暂存的概念
加入当前正在dev分支进行开发,开发到一半,意识到之前的代码出现bug时,可以切换回master分支新开一个分支专门用来解决bug,之后合并分支,删除新分支即可。
但这时正在dev分支开发新的功能,只开发了一半,所以也没法add和commit,则这时可以使用暂存功能stash。
我一开始以为stash和add是同一个操作(因为add进去的那个stage区域也被叫做暂存区...),后来仔细看了内容才知道
stash和add完全不同,这个操作会将本地和暂存区(add)的文件一起存起来。
git stash
git stash save -a
"message"
|
使用git status查看当前状态,会发现工作区是干净的,可以安心新建分支进行bug的修复。
bug修改结束切换回dev分支,工作区是干净的,使用git stash apply或git stash pop命令恢复之前暂存的工作状态:
git stash list
git stash pop
git stash apply
git stash drop
|
可以多次使用stash功能暂存并重新使用,在这之前可以先用list命令查看对应stash号。如果使用source tree一类的GUI工具,会更加直观。
在实际操作中意识到,应用(apply)之前暂存的stash时,如果当前的工作区不是干净的,那么可能会需要手动解决冲突。
另外在网络上查阅资料时还得到一个结论:如果没有事先commit或者stash,git不会让你切换(checkout)到别的分支去。
7.多人协作
开发新功能时,为防止把dev分支弄乱,最好每个新功能都新建一个feature分支,在上面开发完毕后合到dev分支,然后删除feature分支。
从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
推送分支,就是把
该分支上的所有本地提交推送到远程库(这里也有merge的操作在里面)。推送时,要指定本地分支,这样,Git就会把
该分支推送到远程库对应的远程分支上:
git push origin master
git push origin dev
|
多人协作刚开始时,大家需要从远程库上clone项目文件,注意这里是clone,
和后面的pull(拉取)有些区别。
从远程库clone时,默认情况下,只能看到本地的master分支:
git clone git@github.com:michaelliao/learngit.git
|
要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是用这个命令创建本地dev分支:
git checkout -b dev origin/dev
|
现在,就可以在dev上继续修改,然后,时不时地把本地的dev分支push到远程的dev(记得先commit):
如果合作的开发者已经向远程的dev分支(origin/dev分支)推送了提交,而自己也对相同的文件进行了修改并试图推送时,可能会发现对方已经推送的提交和我准备推送的提交有冲突,导致提交失败的情况,即是说因为包含了merge的操作在里面,所以可能会需要解决冲突:
这时可以使用git pull把最新的提交从远端拉下来,在本地与自己想推送的commit进行合并(或者用另外会提到的rebase),解决冲突,之后再推送就可以了,关于pull操作可以参考 git pull、git push、git rebase 。
具体的操作可能是:暂存当前修改(工作区变为空白)、拉取最新提交分支、apply之前的暂存(包含了merge的操作,需要解决冲突)、提交、再次push到远端;
当然不是说本地的dev就只能推到远端的dev,如果你要推送的目标分支名和你的本地开发分支名并不一样(我们组平时开发就是这样的,因为会用这个分支相关的task来给分支命名,推过去之后等待大家review过后才有权限进行合并,push和merge并不是一步完成的,也可以理解成推送时在远端新建了同名分支,之后再由有权限者将这个分支合到远端的dev中去)。这里假设推送的本地分支名字叫bugfix,推送目标时远端的dev,那么你也可以这么操作:切换到本地的dev、pull最新dev、将本地bugfix与本地dev进行合并或rebase(解决冲突)、push向远端的dev。
另外,在使用pull时也可能出现问题,如下例:
这是说明没有进行本地dev分支与远端dev分支之间的链接 (参考git pull、git push、git rebase),需要按照提示先进行如下操作:
git branch —set-upstream dev origin/dev
|
之后就可以pull成功,但是在pull到本地并自动合并时,会出现冲突:
这时需要和之前一样,手动解决冲突,
提交,之后再推送。
总的来说,多人协作的工作模式:
a.试图推送自己的修改到远端;
git push origin branch-name
|
b.可能会推送失败,因为远程分支比本地更新,这时需要用pull命令将最新提交拉下来,然后进行本地的合并(解决冲突),最后再次提交、推送。
8.Reference
参考了很多网络上的资源和wiki,这里不一一列出,主要参考:Git教程