git 使用小结

写在前面

有关Git的诞生故事以及Git的强大,这里无须赘述。写这篇文章的原因是因为,习惯了用Git桌面工具向Github提交代码的我,换了一台笔记本后突然发现我连最基本的Git命令行都不会了。。。羞愧至极,所以就开始我的学习之路。

我的学习方法是,先大概了解Git的相关指令,然后简单试验之后,开始搜集各类博客资料,汇总之后,再次对试验过程进行整理,我整理了一些高质量的博文在附录中或者在文中。

本文是一篇阶段性的记录文章,只记录我容易遗忘的知识点,引用的文章图片都标注了出处,好了下面开始本文

为什么很多人推荐从svn转向git

从使用者角度分析:

  1. svn下载源代码慢。在git中一个几个G的版本库,一般一二十分钟就能下载完毕,但是在svn中要一个小时左右;
  2. svn随时都得要与服务器交互,无论是查看log,还是查看以往的版本你必须跟服务器相连,并且速度奇慢无比,而git做这些几乎是瞬间的事;
  3. 各个分支之间的补丁迁移麻烦,在git上只要两三个命令就可以完事的(其实一个命令,因为需要查找与分支切换),但是在svn上你必须要下载每个分支的代码,然后比较修改,再上传;

从服务器角度说为什么要用git:

  1. git版本库占用空间小(几乎是svn的分支数之一也就是说如果有四个分支,svn的版本库的体积将接近git的四倍),SVN每个分支都是一份代码的copy,而git每个分支只是各个提交点的hash值的集合。分支几乎不占用什么空间;
  2. git是分布式管理系统,完全可以不对代码进行备份,但SVN不行,一旦服务器的硬盘挂掉整个代码库就完了;
  3. git不用时时联网查询,并且对文件进行压缩,使得文件体积大大减小,并且传输速度快,svn是单个文件,git是压缩后的,在使用svn时我已经碰到过好几次服务器无响应了。由于git很多都可以在本地操作的,所以大大降低了客户端对服务器的连接,出现这种情况的概率会大大减小;
  4. 如果客户端离服务器端非常远,在网速糟糕的情况下,用svn下载代码速度远不上git.

推荐文章 http://www.cnblogs.com/common1140/p/3952948.html

git 工作原理

git 使用小结_第1张图片

git跟踪并管理的是修改,而非文件。而且git只能文本信息的修改和恢复,对于二进制文件,比如word或者图片,只能监听到改动却无法对改动进行恢复。

git 分支概念

每次提交(git commit),git都会把这些提交串成一个时间线(后文中管这个时间线叫做分支)

默认情况下,使用git init初始化的项目只有一条分支,叫做master分支。可以理解成有一个叫做master的指针指向着当前最新一次提交。还有一个概念就是存在一个HEAD指针,而且请记住HEAD永远指向当前的分支。HEAD指向的是哪个分支,当前我们就向那分支提交代码。

下图展示了一个普通git项目的分支结构

git 使用小结_第2张图片

可见,当前项目只有一个分支,就是master分支,有三次提交。然后HEAD指针,指向当前的master分支。

因分支必须指向某一次commit,所以必须先有commit才有后来的分支。某个分支必须至少有一次提交git commit之后,才会在git branch指令中显示出来

随着你的每次提交,master分支都会向前移动,HEAD指针也同样跟着向前移动。

当某个场景中,我们从当前分支master创建了新的分支,比如叫做dev。下面这条指令是创建并切换到dev分支。

git branch -b dev

背地里,Git会新建一个指针叫做dev,指向master指向的相同的提交,然后再把HEAD指向dev,就表示当前分支在dev上。

git 使用小结_第3张图片

可以看出,git创建一个分支很快,因为除了增加一个dev指针,改动HEAD指针指向以外,工作区的文件没有变化。

从现在开始,对工作区的修改和提交就是针对dev分支了。比如新提交一次后,dev指针就会往后移动一步,但是注意,master指针不变。

git 使用小结_第4张图片

git分支合并

假如我们在dev分支上的开发工作完成了,就可以吧dev分支上的代码合并到master分支上。有两种情况

  • 快进模式 Fast forward 简称FF模式
    本模式下的前提是:master分支,在dev分支拉出后,没有过提交。这样的合并,Git直接就把master指向dev指向的当前分提交即可,完成了快速合并

      git checkout master
      git merge dev
    
git 使用小结_第5张图片

合并完dev分支后,dev分支可以删掉了,删掉dev分支的操作,其实就是把dev指针给删掉就好了。现在就只剩下一个master指针指向当前提交了

    git branch -d dev

如果没有提交,就删除分支,git会提示你,你需要先提交在删除。但是可以强制删除,使用

    git branch -D dev
  • 非快进模式

本模式发生的情况就是,分支dev对某个文件,比如readme.txt文件修改后并提交,此时切换回master,发现master分支也修改了readme.txt并提交。 这样master分支和dev分支都对同一个文件进行了修改并提交,在master分支合并dev分支时,会提示冲突。如下所示

git 使用小结_第6张图片

这个时候Git无法实现快速合并,只能先尝试着将各自的修改合并后提交,但是这个时候经常伴随着冲突,比如readme.txt文件出现了冲突,文件被修改成了如下内容

    <<<<<<< HEAD
    Creating a new branch is quick
    Creating a new branch is quick & simple.
    =======
    Creating a new branch is quick AND simple.
    >>>>>>> dev

=== 分割冲突代码,上面HEAD标记的是当前分支的内容,下面dev标记的是dev分支合并过来的内容,自己进行取舍

解决完冲突后,将冲突文件 git add git commit提交,这时我们的分支图变成这个样子

git 使用小结_第7张图片

最后查看一下分支的合并情况

git log --graph --pretty=oneline --abbrev-commit
git 使用小结_第8张图片

上面的截图可以看出,解决冲突的部分被单独划归出来了

禁用快速合并模式(简称no-ff模式)保留分支commit信息

前面提到过快速合并,快速合并的场景下,快速合并没有冲突。但是有个缺点就是,在合并后的master的log日志中看不到本次合并的dev分支的commit的id和描述信息

git 使用小结_第9张图片

即,合并完成后,一旦删除了dev分支,我们既无法知道分支存在过,也无法区分那些修改是在分支上进行的。下面看一个禁止掉快速合并的情况

git merge --no-ff -m "merge with no-ff" dev
git 使用小结_第10张图片

上面可以看到 分支合并历史中记录了合并过来的分支的commitid

如果不适用no-ff模式,单纯使用git reflog查看一下日志,你能看出来的,那些分支合并过来了吗

git 使用小结_第11张图片

git stash

当前情况:此时有两个分支,master和dev,dev编辑到一半,并未成功,所以不能提交。但此时master有一个bug需要马上去修复,但因为dev无法提交,所以用stash保存现场。

git stash

转去master去把bug修复完后,checkout到dev开发分支,应该先merge master 然后再

git stash pop 

恢复dev开发现场

多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支关联起来,并且,远程仓库的默认名称是origin。

  • 查看远程仓库的名称

      git remote
    
  • 推送分支

    推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:

      git push origin master
    
  • 抓取分支

      git clone [email protected]:yourname/learngit.git
    

    从远程库中获取分支时,默认只能获取到master分支,使用下面指令创建并关联远程的dev分支

      git checkout -b dev origin/dev
    

    然后提交dev分支

      git push origin dev
    
  • 解决远程冲突

    如果你提交之前,恰好有小伙伴对当前的dev的分支也做了某些修改,则可能提示你提交失败,这个时候,你需要使用

      git pull
    

    来把最新的提交从origin/dev上抓取下来,然后本地合并,解决冲突中,再次提交

  • 删除远程库

    git push origin :

因此,多人协作的工作模式通常是这样:

  1. 可以试图用git push origin branch-name 推送自己的修改;

  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;

  3. 如果合并有冲突,则解决冲突,并在本地提交;

  4. 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!

注:如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to=origin/dev dev

更多内容参考阮一峰老师的这篇讨论git工作流程的文章
经典文章

git 后悔药系列

git diff 分析代码差异

diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
 Git is free software.

git diff #是工作区(work dict)和暂存区(stage)的比较
git diff --cached #是暂存区(stage)和分支(master)的比较

git 错误回退

  • 将还在工作区内未添加到缓存区的文件恢复到上次add之前的状态

      git checkout -- filename
    
  • 将提交到缓存区的文件回退到工作区,将缓存区的该文件恢复到上次commit之前的状态

      git reset HEAD filename
    
  • 已经commit了,想回退到之前的某次提交

    • 回退至某次快照(commit提交)

        git reset --hard 3628164(此数字为某次commit的log日志前七位)
      
    • commitid可以通过如下命令查找,翻看历史操作记录

        git reflog
      
  • 已经提交到远程库了

    无法回退

git 删除文件

使用 git rm filename 删除指定文件

执行完上面这条指令,相当于先删掉了文件,然后执行了git add,将删除的结果添加到了暂存区,所以如果想恢复文件的话,需要进行如下操作

git reset HEAD filename #恢复暂存区
git checkout -- filename #恢复工作区

删除历史文件(它会从HEAD所在的分支历史中查询并删除文件)

git filter-branch --tree-filter 'rm -f ' HEAD

关联github

先建立本地库,关联远程库

  • 生成秘钥

      ssh-keygen -t rsa -C "[email protected]"
    

    以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

  • 在github填写公钥

    登陆GitHub,打开“Account settings”,“SSH Keys”页面:然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容:

  • 将本地git项目关联远程仓储

      $ git remote add origin https://github.com/yourname/learngit.git
    
  • 将本地改动推送到远程仓库

      git push -u origin master    #将当前分支推送到远端,第二次开始不用使用-u
    

先建立远程库,用本地库关联

  • 克隆远程库

      git clone https://github.com/yourname/learngit.git
    

GitHub给出的地址不止一个,还可以用https://github.com/yourname/gitskills.git这样的地址。实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。

使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。

git 标签处理

发布版本时,我们通常会给版本库打一个标签(tag)。标签是以一个更让人理解的方式来标记commit,

比如说请给我找出4.5.6版本的提交,而不是请给我找出commit id为5f54f5sd的提交。“找到了:git show v4.5.6”

类似IP和域名的关系。

git中标签虽然是版本库的快照,也是一个指向当前commit的指针。(和分支很像,但是分支可以移动,标签不能移动),所以创建和删除标签都是瞬间完成的。

  • 在当前分支直接使用如下指令创建tag

      git tag v1.0#可以是任意有意义的字符串
    

注:然后使用 git tag查询所有标签。tag不是按时间顺序排布的,而是按照名字排序的

  • 标签是默认打在最新一次提交上的,添加commitid来对任意提交打tag

      git tag v1.0 commitid
    
  • 使用下面的指令可以用来创建带有说明的tag

      git tag -a v0.1 -m "version 0.1 released" 3628164
    
  • 使用 git show 可以查看tag的说明

  • git push origin 可以推送一个本地标签(标签默认存储在本地)

  • git push origin --tags可以推送全部未推送过的本地标签

  • git tag -d 可以删除一个本地标签;

  • git push origin :refs/tags/可以删除一个远程标签。

提高效率的小指令

  • 彩色的git输出

      git config color.ui true
    
  • 让快照的log信息紧凑输出

      git log --pretty=oneline
    
  • 以图表的形式查看提交历史

      git log --graph
    
  • 只查看commit id的精确位数(一般前7位就能识别)

      git log --abbrev-commit
    
  • 给常常的指令设置别名

      git config --global alias.st status
      git st
    
      git config --global alias.unstage 'reset HEAD'
      git unstage test.py
    
  • 只查看最近一次的log

      git log -1
    
  • 获取本地公钥的快捷方式(文本软件打开有可能出错!)

    mac
    pbcopy < ~/.ssh/id_rsa.pub

    windows
    clip < ~/.ssh/id_rsa.pub

    linux
    sudo apt-get install xclip
    xclip -sel clip < ~/.ssh/id_rsa.pub

精彩理解

我觉得这svn和git两个工具主要的区别在于历史版本维护的位置

Git本地仓库包含代码库还有历史库,在本地的环境开发就可以记录历史
而SVN的历史库存在于中央仓库,每次对比与提交代码都必须连接到中央仓库才能进行

这样的好处在于:
1、自己可以在脱机环境查看开发的版本历史
2、多人开发时如果充当中央仓库的Git仓库挂了,任何一个开发者的仓库都可以作为中央仓库进行服务

svn中央服务器挂了,那我一样可以将本地的项目重新搭建一个服务器呢?

答:不行,你的本地没有历史版本

答!! 断网了,看看能不能查看历史版本?看看能不能提交代码?

关于git commit

git commit命令确认的是最近的一次git add,如果文件最近的内容修改没有被git add,那么在git commit时,最近的文件修改内容不会被提交。

  • 指定文件可以提交未保存到暂存区(unstaged)
    vi readme.txt
    git commit readme.txt -m "commit with unstaged modify"
    vi readme.txt
    git commit -a -m "commit all file will commit unstaged modify"

  • 不使用其他参数不会提交unstaged modify
    git commit -m "commit without param will not commit unstaged modify"

是否可以把公钥私钥一起给别人呢

只需要给公钥。

原始数据经过私钥加密后只能用公钥解密,换句话说,别人收到经过加密的数据后,如果用你的公钥能够解密,那么他就可以确认这些数据是你发送的
如果把私钥给别人的话,别人就可以冒充你给别人发东西了

关于回退操作

对于没提交到stage的修改;

删除后,重新恢复,修改的内容是会直接消失的;比如你在文件中添加一个字符:‘1’;不用git add file 添加到stage;直接用rm删除后,再用git checkout -- file恢复;恢复过来后,去看文件是没有这个字符:‘1’的。

印证了 git checkout -- file恢复的是已经添加到stage的内容;

而使用git rm删除的就是stage的内。git reset HEAD -- file会从master中将被删的stage的内容拷贝过去。如果你使用了git rm之后接着使用git commit -m “remove file”则会删除master里的内容;

所以,关于一次回退流程是这样的

  • git reset --hard HEAD^可以将删除的master从回收站恢复过来;
  • 然后利用git reset HEAD -- file 从master中拷贝到stage中;
  • 最后再用git checkout -- file 从stage中拷贝到工作目录中。

关于未提交的修改

现象:
在分支修并提交后,切到主干,主干的工作区是干净的;在分支修改不提交,切回主干,主干工作区是被修改过未提交的状态

解释:
这样做的好处可能是你本来想对DEV分支进行想改,但是你忘了切换到dev你还在master就已经改了工作区,如果这时切换到dev修改的工作区内容没了,岂不是很操蛋。只有commit之后才确定修改的内容属于哪个分支。

未commit的工作区文件和stage文件是可以灵活地在且仅在任一branch存在的。这是前提。

  • 在工作区做了修改,提交到DEV的分支,再切换回master

    这时候,对master来说,工作区没有任何未提交的修正(因为所有修正都已经commit)。则工作区内容应该是与master分支最后一次提交的内容一致。(处于任何其他时间点,都意味着工作区可能存在修正,这就出现了矛盾)

  • 在工作区做了修改,没有提交到DEV分支,即切换回master
    这个时候,对master来说,工作区有了修正,那么就保持工作区的现有状态即可。

pull&& push

pull:本地 <-- 远程
push:本地 --> 远程

本质上都是同步commit

如果你本地落后远程,必然要pull
如果你本地超前远程,必然要push

课后查看

  • 为什么会产生冲突?什么时候产生冲突
  • 上面的截图可以看出,解决冲突的部分被单独划归出来了
  • 图形界面操作
  • 断网情况下查看一下svn的操作

附录

  • Git 简易教程
  • Pro Git 中文
  • 廖雪峰Git教程
  • Git 基本操作 | 菜鸟教程
  • Git 系列文章 | 伯乐在线
  • 10个提高Git水平的知识点

你可能感兴趣的:(git 使用小结)