git
git
git是一个分布式版本控制系统。分布式是相对于集中式来说的,对于集中式版本控制(如SVN),所有的代码都放在集中的服务器上,大家都从这个服务器下载代码,然后在本地开发,然后再上传。而分布式版本控制没有中央服务器,每个人的本地电脑都是一个完整的版本库,可以在本地创建分支,在本地提交代码,还可以提交到远程分支;
git创建
git的仓库就是一个文件夹目录(又叫repository),里面的所有文件都被git进行管理;
所以首先创建一个空目录
$ mkdir myrep //linux创建文件夹命令 $ cd myrep
然后使用 git init 把这个普通目录变成git仓库
$ git init
目录下会生成一个 .git 文件夹,里面是管理git仓库相关文件;
可以不必在空文件夹下git init, 也可以直接在现有文件目录下git init;
版本控制系统只追踪文本文件的改动,对于二进制文件则无法追踪其内容变化,只能知道大小变了;
其它linux相关命令:
$ ls -al //查看当前目录下所有文件,包括隐藏文件 $ cd mydir //打开文件夹 $ touch abc.txt //创建文件 $ echo "12345" >> abc.txt //向abc.txt写入字符串 $ cat abc.txt //查看abc.txt的文本内容
时光机穿梭
git时光机
git status 命令,用于随时掌握仓库的当前状态:
$ git status On branch master //显示当前在那个分支 Changes not staged for commit: //变动了的文件,但还没有add到staged区 (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: readme.txt //具体是哪个文件被改变了 no changes added to commit (use "git add" and/or "git commit -a")
git status 一共有3种:
untracked files 从未被追踪过的文件,即从来没有被add过的文件;
changes not staged for commit 文本发生了变动,但本次变动没有被add到staged区;
changes to be commited 文本的变动已经add到了staged区,就等着被commit啦;
git diff 命令,就是看difference
$ git diff readme.txt //diff命令,用于查看对比分析区别 diff --git a/readme.txt b/readme.txt //目前进行比较的是a版本的readme.txt和b版本的readme.txt index 46d49bf..9247db6 100644 //前两个数是两个版本的git的哈希值,最后一个数先不管 --- a/readme.txt //---表示变动前的文件 +++ b/readme.txt //+++表示变动后的文件 //下面是合并格式的首部;首部用@@分隔,前两个数-1和2,-表示第一个文件,1表示从第一行开始,2表示一共2行; +1和2用同样的方法表示第二个文件 @@ -1,2 +1,2 @@ //下面是合并格式的正文内容 -Git is a version control system. //-表示第一个文件,所以这是第一个文件的第1行 +Git is a distributed version control system. //这是第二个文件的第2行 Git is free software. //这是两个文件共有的部分,所以是第一个文件和第二个文件的第2行
版本回退
每一次commit都会产生一个版本,所以版本回退就是回退到某次commit的结果;
使用 git reset --hard HEAD^ 可以会退到上一次的版本; 注:--hard参数暂时忽略
//在git中,HEAD表示当前版本,HEAD^表示上一个版本,同理HEAD^^表示上上个版本
//HEAD实质上是一个指针,改变指针的指向就会切换到不同的版本 $ git reset --hard HEAD^ HEAD is now at e475afc add distributed
使用 git reset --hard 某版本号 回退到指定版本号的版本
$ git reset --hard 1094a //1094a为某个版本号的前几位,版本号没有必要写全,只要系统能识别就行 HEAD is now at 83b0afe append GPL
使用git log来查看历史提交的commit
$ git log commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) Author: Michael Liao <askxuefeng@gmail.com> Date: Fri May 18 21:06:15 2018 +0800 append GPL commit e475afc93c209a690c39c13a46716e8fa000c366 Author: Michael Liao <askxuefeng@gmail.com> Date: Fri May 18 21:03:36 2018 +0800 add distributed commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 Author: Michael Liao <askxuefeng@gmail.com> Date: Fri May 18 20:59:18 2018 +0800 wrote a readme file
当回退到旧版本后,又想前进到新版本(回到未来),这时是无法找到新版本的commit id的,这时就需要使用 git reflog 来找出以前的命令:
$ git reflog e475afc HEAD@{1}: reset: moving to HEAD^ 1094adb (HEAD -> master) HEAD@{2}: commit: append GPL e475afc HEAD@{3}: commit: add distributed eaadf4e HEAD@{4}: commit (initial): wrote a readme file
工作区和暂存区
工作区:就是电脑里面能看到的仓库的目录,即最早初始化git,使用git init 的地方。
版本库:是工作区文件目录里的隐藏的 .git 目录,这就是git的版本库;工作区和版本库的关系如下:
你平时写的所有代码只有一份,存储在工作区中,平时写代码也是对工作区的代码文件进行编辑;
而使用 git add 命令就会把代码的变动情况存储到stage区,也叫索引区、暂存区(注意是把代码的变化情况存入stage,而非把整个工作区代码存入stage);
使用 git commit 命令就可将stage区的缓存一次性提交到当前HEAD所指向的分支;
git管理的是修改而非整个文件
举例来说,如果有操作序列: 第一次修改 -> git add -> 第二次修改 -> git commit;则会发现第二次的修改没有被提交,执行 commit 后git会提示 changes not staged for commit,这就是说第二次的修改没有被commit,即说明git管理的是文件的变动,而非整个文件(可以把修改当成一个对象实体,git管理的就是这样的对象)
改为 第一次修改 -> git add -> 第二次修改 -> git add -> git commit,则顺利将第二次的修改也提交了。
撤销修改
要和版本回退相区分;
场景一:修改了某文件,但还没有add到暂存区,想撤销修改时,用命令 git checkout -- fileName;
注:git checkout -- fileName 命令,当做了修改但没有add时,使用该命令后撤回到修改前的原始状态;当add过后又做了修改时,使用该命令撤回到刚add后的状态;
场景二:修改了文件,并add到了暂存区,想要撤销修改,分两步走
先使用 git reset HEAD fileName, 可以将暂存区的修改撤销掉,使之变成 changes not staged for commit 状态
然后再用场景一中的 git checkout -- fileName 进行撤销修改
场景三:实在不行就版本回退吧;
删除文件
//先正常删除文件 $ re test.txt //这时若使用 git status 查看状态,会发现处于 changes not staged for commit状态 //如果确实需要删除文件,则用git rm 从版本库删除文件,并commit $ git rm test.txt $ git commit -m "remove test.txt" //如果是误删,想恢复,则参照上一节的撤销修改操作,使用 git checkout -- fileName $ git checkout -- test.txt
远程仓库
用github做远程服务器
第一步:创建ssh key。 在用户主目录下(C盘user目录)查看是否有id_rsa和id_rsa.pub文件,如果没有则需要生成。用bash或shell创建ssh密钥:
$ ssh-keygen -t rsa -C "youremail@xxx.com"
然后一路默认直到完成。之后会在主目录里发现 .ssh目录, 里面有上述两个文件,其中 id_rsa.pub 是公钥,id_rsa是私钥。(原理是数字签名)
第二步,登录github,在ssh的key设置中添加公钥。
之后就可以使用ssh协议将本地的git和远程的GitHub关联了
添加远程仓库
第一步:在github上新建一个repository,随便起名比如myrepo
第二步:在本地仓库运行 git remote add,将本地仓库与远程仓库相关联
//添加后远程库的名字就是origin,也可以起别的名字 $ git remote add origin git@github.com:yourGitHubID/myrepo.git //将本地内容推送到远程库, origin是远程库的名字,master是当前本地分支
//实质是将本地库的当前分支推送到远程
$git push -u origin master
由于在push前远程库是空的,所以第一次push时,加上-u参数,git不仅会将本地的master分支内容推送到远程的此时新建的master分支,还会把本地master分支和远程master分支关联起来。
以后push就只需要使用 git push origin master 推送新的修改了
远程库克隆至本地
// git@github...等一串字符是远程仓库地址 $ git clone git@github.com:yourGitHubID/yourRepo.git
分支管理
创建与合并分支,及其原理
开始的时候,master分支是一条线,git用mater指向最新的提交,再用HEAD指向master,就能确定当前分支和分支的提交点。
每次提交,master都会向前移动一步,因此HEAD即当前分支始终指向最新。
当创建新的分支,如dev时,git上新建一个指针叫dev,指向与master相同的提交,再将HEAD指向dev,表示当前在dev分支
现在开始,修改和提交都是针对dev分支了,如果再在dev分支提交一次,会出现master指针不变,而dev指针向前移动了一步
如果在dev上的开发工作完成了,需要把dev的工作合并到master分支,git会直接把master指针指向dev当前的指向,完成合并
合并完成后,可以删除dev分支了
相关指令:
$ git branch //查看分支 $ git branch Name //创建分支 $ git checkout Name //切换分支 $ git checkout -b Name //创建+切换分支 $ git merge Name //合并Name分支到当前分支,【注意一定是合并到当前分支,而非合并到Name分支】 $ git branch -d Name //删除分支
解决冲突
当两个不同分支修改了同一个文件时,再试图合并这两个分支时,就会出现冲突;如下master和feature1分支都修改了readme.txt,并且都进行了提交,若试图合并则会导致冲突
$ git merge feature1 //尝试合并, git提示冲突,冲突文件为readme.txt Auto-merging readme.txt CONFLICT (content): Merge conflict in readme.txt Automatic merge failed; fix conflicts and then commit the result
这时需要解决冲突,可以打开 readme.txt, 发现git已经将冲突部分标识出来了
// <<<<<< 标记冲突开始,后面跟的是当前分支的代码 // ====== 用于分隔 // >>>>>> 与等号之间的部分是需要合并过来的分支的冲突代码 Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files. <<<<<<< HEAD Creating a new branch is quick & simple. ======= Creating a new branch is quick AND simple. >>>>>>> feature1
这时只需要手动修改冲突部分,可以将当前分支(master)的代码改成带合并过来的代码,然后在当前分支commit;然后再进行合并;
上图就是将master分支的代码进行了修改,与feature保持一致,然后将feature合并到master
分支管理策略
团队开发时的分支策略如上图
master分支:应该是非常稳定的,仅仅用来发布新版本
dev:dev分支非常不稳定,用来在上面开发
每个人各自的分支:用于c克隆dev的代码,并在上面开发
另【个人理解】:master分支和dev分支应该是在本地和远程都有
Bug分支
场景如下:
master上上线的1.0版本;
你在自己的zqw分支上开发2.0版本,但只开发到一半;此时用户反映1.0版本有Bug,你需要紧急修复1.0中的Bug
你想直接切换到master分支,再在master分支的基础上创建Bug分支修复Bug,修复完再切回zqw分支继续开发2.0,但这样会留下一个不怎么好的没有意义的commit记录,还很麻烦;这么做可以但没必要;
或者你一想,干脆别commit了,直接切回master分支,再在master分支下创建Bug分支,再做修复等等操作.... 【这样不可行】,理由是未经add 和 commit 的内容不属于任何一个分支,也就是对所有分支而言,工作区和stage区是公共的,你没commit就切换分支,之前工作区的一些编辑操作仍会保留;
此时就需要用stash功能了,它可以把2.0的开发工作不经过add 和 commit 就保存下来;然后你就可以放心切换到master分支修复Bug,然后完成后再切换回2.0的zqw分支继续开发;
步骤如下
$git stash 然后切到master分支,创建bug分支改bug,合并会master分支 $git checkout dev //切回开发分支 $git stash pop //恢复现场
多人协作
常用指令
$ git remote -v //查看远程库信息 //本地新建的分支,如果不推送到远程,则对他人不可见; //若是本地新建分支推送到远程,则会在远程新建同名的分支,但不会自动生成本地分支到远程对应分支的关联 $ git push origin branch-name //从本地将branch-name分支推送到远程origin $ git pull <远程主机名> <远程分支名>:<本地分支名> //从远程分支取回代码,并和本地分支做合并; <>内容都可省略,省略后则默认为当前本地分支,和其对应关联的远程分支 //git pull 等价于 git fetch + git merge,实际最好使用fetch+merge $git fetch origin master:temp //从远程master分支获取代码,并在本地建立temp分支 $git merge temp //将temp分支合并到当前分支 $ git checkout -b branch-name origin/branch-name; //在本地创建新分支,该分支和远程某分支对应 $ git branch --set-upstream-to branch-name origin/branch-name; //建立本地分支和远程分支的关联 $git branch -vv //查看本地分支与远程分支的关联情况
多人协作的模式:
首先用 git push origin your_branch 推送自己的修改;
如果推送失败,则说明远程分支比你的当前本地分支更新,你需要先将远程分支pull到本地做合并
如果合并有冲突,则手动修改解决冲突,并在本地提交;
然后再push到远程