美文网首页
gitbook 读书笔记

gitbook 读书笔记

作者: 君子Python | 来源:发表于2017-07-03 00:35 被阅读0次

    gitbook 简介

    1. 欢迎来使用Git, 它是一个快速的分布式版本控制系统。
    2. 以介绍Git如何存储数据为始,了解它和其它版本控制系统有何异同
    3. 介绍一些Git的基本用法,将在90%的时间都在使用的命令。
    4. Git中级用法,这些用法也许会替换掉前面的基本用法。在了解前面的基本用法后, 这些看起来像魔术一样的命令,你可能会用起来很爽。
    5. Git的高级用法,在特定的环境会非常有用,获得全面的Git知识。
    6. 学习 Git 配合脚本、部署工具、编辑器和其它工具一起工作。将Git 集成进工作环境。
    7. Git hacker :low-level documentation,包括Git 的内核和协议如何运作等等。

    GIT对象模型

    1. SHA: 区分不同对象名。
    2. 对象:每个对象(object) 包括三个部分:类型,大小和内容。
    3. 与SVN的区别
    4. Blob对象: 一个blob通常用来存储文件的内容.
    $ git show 6ff87c4664
    
    1. Tree 对象: 一个tree对象有一串(bunch)指向blob对象或是其它tree对象的指针,它一般用来表示内容之间的目录层次关系。
    $ git ls-tree fb3a8bdd0ce
    
    1. Commit对象: "commit对象"指向一个"tree对象", 并且带有相关的描述信息; 一个“commit”只指向一个"tree",它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,如时间戳、最近一次提交的作者、指向上次提交(commits)的指针等等。
    $ git show -s --pretty=raw 2be7fcb476
    
    1. 对象模型
      commit到一个Git仓库中示意图
      你可以看到: 每个目录都创建了 tree对象 (包括根目录), 每个文件都创建了一个对应的 blob对象 . 最后有一个 commit对象来指向根tree对象(root of trees), 这样我们就可以追踪项目每一项提交内容.
    2. 标签对象: 一个标签对象包括一个对象名(即SHA1签名), 对象类型, 标签名, 标签创建人的名字("tagger"), 还有一条可能包含有签名(signature)的消息.
    $ git cat-file tag v1.5.0
    

    Git目录 与 工作目录

    1. Git目录: 是为你的项目存储所有历史和元信息的目录
    $>tree -L 1
    .
    |-- HEAD         # 这个git项目当前处在哪个分支里
    |-- config       # 项目的配置信息,git config命令会改动它
    |-- description  # 项目的描述信息
    |-- hooks/       # 系统默认钩子脚本目录
    |-- index        # 索引文件
    |-- logs/        # 各个refs的历史信息
    |-- objects/     # Git本地仓库的所有对象 (commits, trees, blobs, tags)
    `-- refs/        # 标识你项目里的每个分支指向了哪个提交(commit)。
    
    1. 工作目录:
    • 'Git目录' 一般就是指项目根目录下的'.git'目录.
    • Git的 '工作目录' 存储着你现在签出(checkout)来用来编辑的文件. 当你在项目的不同分支间切换时, 工作目录里的文件经常会被替换和删除. 所有历史信息都保存在 'Git目录'中 ; 工作目录只用来临时保存签出(checkout) 文件的地方, 你可以编辑工作目录的文件直到下次提交(commit)为止.

    Git索引

    Git索引是一个在你的工作目录和项目仓库间的暂存区(staging area). 有了它, 你可以把许多内容的修改一起提交(commit). 如果你创建了一个提交(commit), 那么提交的是当前索引(index)里的内容, 而不是工作目录中的内容.

    1. 查看索引
    $>git status
    # On branch master
    # Your branch is behind 'origin/master' by 11 commits, and can be fast-forwarded.
    #
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #   modified:   daemon.c
    #
    # Changed but not updated:
    #   (use "git add <file>..." to update what will be committed)
    #
    #   modified:   grep.c
    #   modified:   grep.h
    #
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #
    #   blametree
    #   blametree-init
    #   git-gui/git-citool
    

    使用 git status 命令是查看索引内容的最简单办法, 运行可看到:

    • 哪些文件被暂存了(就是在你的Git索引中);
    • 哪些文件被修改了但是没有暂存;
    • 还有哪些文件没有被跟踪(untracked).

    如果完全掌握了索引(index), 你就一般不会丢失任何信息, 只要你记得名字描述信息(name of the tree that it described)就能把它们找回来.


    Git 安装与初始化

    Git 配置

    $ git config --global user.name "zhangjun"
    $ git config --global user.email "zhangjun@zhihu.com"
    

    基本用法

    获得一个Git仓库

    1. Clone一个仓库
    $ git clone https://git.coding.net/staufenzhang/xsbAppTest.git
    
    1. 初始化一个新的仓库
      假设有一个”project.tar.gz”的压缩文件里包含了一些文件,执行命令让它置于Git的版本控制管理之下:
    $ tar xzf project.tar.gz
    $ cd project
    $ git init
    

    Git会输出:
    Initialized empty Git repository in .git/
    project目录下会有一个名叫".git" 的目录被创建,这意味着一个仓库被初始化了。


    正常的工作流程

    $ git add file1
    $ git diff 
    $ git status
    $ git commit "修改注释"
    $ git push
    
    
    git正常的提交流程.png

    git正常的撤销流程.png

    分支与合并@基础

    分支与合并.png
    1. 一个Git仓库可以维护很多开发分支。创建一个新的叫”experimental”的分支:
    $ git branch experimental
    $ git branch
    >  experimental
    >* master
    
    1. “experimental” 分支是你刚才创建的,“master”分支是Git系统默认创建的主分支。星号(“”)*标识了你当工作在哪个分支下,输入:
    $ git checkout experimental
    
    1. 切换到”experimental”分支,先编辑里面的一个文件,再提交(commit)改动,最后切换回 “master”分支。
      切换到”experimental”分支,先编辑里面的一个文件,再提交(commit)改动,最后切换回 “master”分支。
    (edit file)
    $ git commit -a
    $ git checkout master
    
    1. 你现在可以看一下你原来在“experimental”分支下所作的修改还在不在;因为你现在切换回了“master”分支,所以原来那些修改就不存在了。
      你现在可以在“master”分支下再作一些不同的修改:
    (edit file)
    $ git commit -a
    
    1. 这时,两个分支就有了各自不同的修改(diverged);我们可以通过下面的命令来合并“experimental”和“master”两个分支:
    此处有疑问?$ git commit -a.png
    $ git merge experimental
    
    1. 如果这个两个分支间的修改没有冲突(conflict), 那么合并就完成了。如有有冲突,输入下面的命令就可以查看当前有哪些文件产生了冲突:
    $ git diff
    
    1. 当你编辑了有冲突的文件,解决了冲突后就可以提交了:
    $ git commit -a
    
    1. 提交(commit)了合并的内容后就可查看一下:
    $ gitk
    

    执行了gitk后会有一个很漂亮的图形的显示项目的历史。

    1. 这时你就可以删除掉你的 “experimental” 分支了(如果愿意):
    $ git branch -d experimental
    

    git branch -d只能删除那些已经被当前分支的合并的分支. 如果你要强制删除某个分支的话就用git branch –D;下面假设你要强制删除一个叫”crazy-idea”的分支:

    $ git branch -D crazy-idea
    

    分支是很轻量级且容易的,这样就很容易来尝试它。


    如何合并

    1. 你可以用下面的命令来合并两个分离的分支:git merge:
    $ git merge branchname
    
    1. 这个命令把分支"branchname"合并到了当前分支里面。如有冲突(冲突--同一个文件在远程分支和本地分支里按不同的方式被修改了);那么命令的执行输出就像下面一样
    $ git merge next
     100% (4/4) done
    Auto-merged file.txt
    CONFLICT (content): Merge conflict in file.txt
    Automatic merge failed; fix conflicts and then commit the result.
    
    1. 在有问题的文件上会有冲突标记,在你手动解决完冲突后就可以把此文件添 加到索引(index)中去,用git commit命令来提交,就像平时修改了一个文件 一样。
      如果你用gitk ?来查看commit的结果,你会看到它有两个父分支:一个指向当前 的分支,另外一个指向刚才合并进来的分支。

    解决合并中的冲突

    1. 如果执行自动合并没有成功的话,git会在索引和工作树里设置一个特殊的状态, 提示你如何解决合并中出现的冲突。
      有冲突(conflicts)的文件会保存在索引中,除非你解决了问题了并且更新了索引,否则执行 git commit都会失败:
    $ git commitfile.txt: needs merge
    
    1. 如果执行 git status会显示这些文件没有合并(unmerged),这些有冲突的文件里面会添加像下面的冲突标识符:
    <<<<<<< HEAD:file.txt
    Hello world
    =======
    Goodbye
    >>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
    
    1. 你所需要的做是就是编辑解决冲突,(接着把冲突标识符删掉),再执行下面的命令:
    $ git add file.txt
    $ git commit
    

    注意:提交注释里已经有一些关于合并的信息了,通常是用这些默认信息,但是你可以添加一些你想要的注释。
    上面这些就是你要做一个简单合并所要知道的,但是git提供更多的一些信息来帮助解决冲突。


    撒销一个合并

    1. 如果你觉得你合并后的状态是一团乱麻,想把当前的修改都放弃,你可以用下面的命令回到合并之前的状态:
    $ git reset --hard HEAD
    

    或者你已经把合并后的代码提交,但还是想把它们撒销:

    $ git reset --hard ORIG_HEAD
    

    但是刚才这条命令在某些情况会很危险,如果你把一个已经被另一个分支合并的分支给删了,那么 以后在合并相关的分支时会出错。

    快速向前合并

    1. 还有一种需要特殊对待的情况,在前面没有提到。通常,一个合并会产生一个合并提交(commit), 把两个父分支里的每一行内容都合并进来。
    2. 但是,如果当前的分支和另一个分支没有内容上的差异,就是说当前分支的每一个提交(commit)都已经存在另一个分支里了,git 就会执行一个“快速向前"(fast forward)操作;git 不创建任何新的提交(commit),只是将当前分支指向合并进来的分支。

    查看历史 -Git日志

    $ git log
    
    1. 日志统计
      如果用--stat选项使用'git log',它会显示在每个提交(commit)中哪些文件被修改了, 这些文件分别添加或删除了多少行内容.
    $ git log --stat
    
    git log --stat.png
    1. 格式化日志
    • 你可以按你的要求来格式化日志输出。‘--pretty'参数可以使用若干表现格式,如‘oneline':
    $ git log --pretty=oneline
    
    • 或者你也可以使用 'short' 格式:
    $ git log --pretty=short
    
    • 你也可用‘medium','full','fuller','email' 或‘raw'. 如果这些格式不完全符合你的相求, 你也可以用‘--pretty=format'参数(参见:git log)来创建你自己的"格式“.
    $ git log --pretty=format:'%h was %an, %ar, message: %s'
    
    • 另一个有趣的事是:你可以用'--graph'选项来可视化你的提交图(commit graph),就像下面这样:
    $ git log --pretty=format:'%h : %s' --graph
    

    它会用ASCII字符来画出一个很漂亮的提交历史(commit history)线。

    1. 日志排序
    • 按默认情况,提交(commits)会按逆时间(reverse chronological)顺序显示。

    • 但是你也可以指定‘--topo-order'参数,这就会让提交(commits)按拓朴顺序来显示(就是子提交在它们的父提交前显示). 如果你用git log命令按拓朴顺序来显示git仓库的提交日志,你会看到“开发线"(development lines)都会集合在一起.

    $ git log --pretty=format:'%h : %s' --topo-order --graph
    
    • 你也可以用'--date-order'参数,这样显示提交日志的顺序主要按提交日期来排序. 这个参数和'--topo-order'有一点像,没有父分支会在它们的子分支前显示,但是其它的东东还是按交时间来排序显示。你会看到"开发线"(development lines)没有集合一起,它们会像并行开发(parallel development)一样跳来跳去的:
    $ git log --pretty=format:'%h : %s' --date-order --graph
    
    • 最后,你也可以用 ‘--reverse'参数来逆向显示所有日志。

    比较提交 - Git Diff

    1. 你可以用 git diff 来比较项目中任意两个版本的差异。
    $ git diff master..test
    

    上面这条命令只显示两个分支间的差异,如果你想找出‘master’,‘test’的共有 父分支和'test'分支之间的差异,你用3个‘.'来取代前面的两个'.' 。

    1. 哪些内容会被提交(commit)
      如果你要看在下次提交时要提交的内容(staged,添加到索引中),你可以运行:
    $ git diff --cached
    

    上面的命令会显示你当前的索引和上次提交间的差异;这些内容在不带"-a"参数运行 "git commit"命令时就会被提交。

    $ git diff HEAD
    

    上面这条命令会显示你工作目录与上次提交时之间的所有差别,这条命令所显示的 内容都会在执行"git commit -a"命令时被提交。

    1. 更多的比较选项
    • 如果你要查看当前的工作目录与另外一个分支的差别,你可以用下面的命令执行:
    $ git diff test
    
    • 这会显示你当前工作目录与另外一个叫'test'分支的差别。你也以加上路径限定符,来只 比较某一个文件或目录。
    $ git diff HEAD -- ./lib 
    
    • 如果不是查看每个文件的详细差别,而是统计一下有哪些文件被改动,有多少行被改 动,就可以使用‘--stat' 参数。
    $>git diff --stat
    

    分布式的工作流程

    1. 假设Alice现在开始了一个新项目,在/home/alice/project建了一个新的git 仓库(repository);另一个Bob的工作目录也在同一台机器,他要提交代码。
      Bob 执行了这样的命令:
    $ git clone /home/alice/project myrepo
    

    这就建了一个新的叫"myrepo"的目录,这个目录里包含了一份Alice的仓库的 克隆(clone). 这份克隆和原始的项目一模一样,并且拥有原始项目的历史记 录。

    1. Bob 做了一些修改并且提交(commit)它们:
    (edit files)
    $ git commit -a
    (repeat as necessary)
    
    1. 当他准备好了,他告诉Alice从仓库/home/bob/myrepo中把他的修改给拉 (pull)下来。她执行了下面几条命令:
    $ cd /home/alice/project
    $ git pull /home/bob/myrepo master
    
    • 这就把Bob的主(master)分支合并到了Alice的当前分支里了。
    • 如果Alice在 Bob修改文件内容的 同时 也做了修改的话,她可能需要手工去修复冲突**. (注意:"master"参数在上面的命令中并不一定是必须的,因为这是一个 默认参数)
    1. git pull命令执行两个操作: 它从远程分支(remote branch)抓取修改 的内容,然后把它合并进当前的分支。
      如果你要经常操作远程分支(remote branch),你可以定义它们的缩写:
    $ git remote add bob /home/bob/myrepo
    
    1. 这样,Alic可以用"git fetch"" 来执行"git pull"前半部分的工作, 但是这条命令并不会把抓下来的修改合并到当前分支里。
    $ git fetch bob
    
    1. 我们用 git remote 命令建立了Bob的运程仓库的缩写,用这个(缩写) 名字我从Bob那得到所有远程分支的历史记录。在这里远程分支的名字就叫bob/master
    $ git log -p master..bob/master
    

    上面的命令把Bob从Alice的主分支(master)中签出后所做的修改全部显示出来。

    1. 当检查完修改后,Alice就可以把修改合并到她的主分支中。
    $ git merge bob/master
    

    这种合并(merge)也可以用pull来完成,就像下面的命令一样:

    $ git pull . remotes/bob/master
    

    注意:git pull 会把远程分支合并进当前的分支里,而不管你在命令 行里指定什么。

    1. 其后,Bob可以更新它的本地仓库--把Alice做的修改拉过来(pull):
    $ git pull
    
    1. 如果Bob从Alice的仓库克隆(clone),那么他就不需要指定Alice仓库的地 址;因为Git把Alice仓库的地址存储到Bob的仓库配库文件,这个地址就是 在git pull时使用:
    $ git config --get remote.origin.url/home/alice/project
    

    (如果要查看git clone创建的所有配置参数,可以使用"git config -l", git config 的帮助文件里解释了每个参数的含义.)
    Git同时也保存了一份最初(pristine)的Alice主分支(master),在 "origin/master"下面.

    $ git branch -r origin/master
    
    1. 如果Bob打算在另外一台主机上工作,他可以通过ssh协议来执行"clone" 和"pull"操作:
    $ git clone alice.org:/home/alice/project myrepo
    
    • git有他自带的协议(native protocol),还可以使用rsync或http; 你可以点 这里 git pull 看一看更詳細的用法。
    • Git也可以像CVS一样来工作:有一个中心仓库,不同的用户向它推送(push) 自己所作的修改;

    公共Git仓库

    1. 另外一个提交修改的办法,就是告诉项目的维护者(maintainer)用 git pull 命令从你的仓库里把修改拉下来。这和从主仓库"里更新代码类似,但是是从 另外一个方向来更新的。
    • 如果你和维护者(maintainer)都在同一台机器上有帐号,那么你们可以互相从对 方的仓库目录里直接拉(pull)所作的修改;git命令里的仓库地址也可以是本地 的某个目录名:
    $ git clone /path/to/repository$ git pull /path/to/other/repository
    
    • 也可以是一个ssh地址:
    $ git clone ssh://yourhost/~you/repository
    
    • 如果你的项目只有很少几个开发者,或是只需要同步很少的几个私有仓库, 上面的方法也许够你用的。
    1. 然而,更通用的作法是维护几个不同的公开仓库(public repository).这样可以把每个人的工作进度和公开仓库清楚的分开。
    • 你还是每天在你的本地私人仓库里工作,但是会定期的把本地的修改推(push) 到你的公开仓库中;其它开发者就可以从这个公开仓库来拉(pull)最新的代码。 如果其它开发者也有他自己的公共仓库,那么他们之间的开发流程就如下图 所示:
                            you push
      your personal repo ------------------> your public repo
        ^                                     |
        |                                     |
        | you pull                            | they pull
        |                                     |
        |                                     |
        |                   they push             V
      their public repo <------------------- their repo
    

    将修改推到一个公共仓库

    1. 通过http或是git协议,其它维护者可以抓取(fetch)你最近的修改,但是他们 没有写权限。这样,这需要将本地私有仓库的最近修改上传公共仓库中。
    • 译者注: 通过http的WebDav协议是可以有写权限的,也有人配置了git over http.
    • 最简单的办法就是用 git push命令 和ssh协议; 用你本地的"master" 分支去更新远程的"master"分支,执行下面的命令:
    $ git push ssh://yourserver.com/~you/proj.git master:master
    

    或是:

    $ git push ssh://yourserver.com/~you/proj.git master
    
    • 和git-fetch命令一样git-push如果命令的执行结果不是"快速向前"(fast forward) 就会报错; 下面的章节会讲如何处理这种情况.
    1. 推(push)命令的目地仓库一般是个裸仓库(bare respository). 你也可以推到一 个签出工作目录树(checked-out working tree)的仓库,但是工作目录中内 容不会被推命令所更新。如果你把自己的分支推到一个已签出的分支里,这 会导致不可预知的后果。
    • 在用git-fetch命令时,你也可以修改配置参数,让你少打字:)。
      下面这些是例子:
    $ cat >>.git/config <<EOF
    [remote "public-repo"]
        url = ssh://yourserver.com/~you/proj.git
    EOF
    
    • 你可以用下面的命令来代替前面复杂的命令:
    $ git push public-repo master
    

    当推送代码失败时要怎么办

    1. 如果推送(push)结果不是"快速向前"(fast forward),那么它 可能会报像下面一样的错误:
    error: remote 'refs/heads/master' is not an ancestor of
    local  'refs/heads/master'.
    Maybe you are not up-to-date and need to pull first?
    error: failed to push to 'ssh://yourserver.com/~you/proj.git'
    
    1. 这种情况通常由以下的原因产生:
    • git-reset --hard 删除了一个已经发布了的一个提交,或是

    • git-commit --amend 去替换一个已经发布的提交,或是

    • git-rebase 去rebase一个已经发布的提交.

    1. 你可以强制git-push在上传修改时先更新,只要在分支名前面加一个加号。
    $ git push ssh://yourserver.com/~you/proj.git +master
    
    1. 通常不论公共仓库的分支是否被修改,他都被修改为指向原来指向的提交(commit) 跟随的下一个提交(commit)。如果在这种情况下强制地推送,你就破坏了之前的约定。

    2. 尽管如此,这也是一种通常的用法来简单地发布一系列正在修正的补丁,并且只要你 通知了其他的开发者你打算怎样操作这个分支,这也是一种可以接受的折中办法。

    3. 一个推送(push)也可能因为其他人有向这个仓库(repository)推送的权利而失败。 在这种情况下,正确地解决办法是首先用"pull"命令或者"fetch"命令和"rebase" 命令更新你的代码,然后重新尝试推送(push);


    Git标签

    1. 轻量级标签
      我们可以用 git tag不带任何参数创建一个标签(tag)指定某个提交(commit):
    $ git tag stable-1 1b2e1d63ff
    
    • 这样,我们可以用stable-1 作为提交(commit) "1b2e1d63ff" 的代称(refer)。
    • 前面这样创建的是一个“轻量级标签",这种分支通常是从来不移动的。
    • 如果你想为一个标签(tag)添加注释,或是为它添加一个签名(sign it cryptographically), 那么我们就需要创建一个 ”标签对象".
    1. 标签对象
    • 如果有 "-a", "-s" 或是 "-u " 中间的一个命令参数被指定,那么就会创建 一个标签对象,并且需要一个标签消息(tag message). 如果没有"-m " 或是 "-F " 这些参数,那么就会启动一个编辑器来让用户输入标签消息(tag message).
    • 译者注:大家觉得这个标签消息是不是提交注释(commit comment)比较像。
    • 当这样的一条命令执行后,一个新的对象被添加到Git对象库中,并且标签引用就指向了 一个标签对象,而不是指向一个提交(commit). 这样做的好处就是:你可以为一个标签 打处签名(sign), 方便你以后来查验这是不是一个正确的提交(commit).
      下面是一个创建标签对象的例子:
    $ git tag -a stable-1 1b2e1d63ff
    
    • 标签对象可以指向任何对象,但是在通常情况下是一个提交(commit). (在Linux内核代 码中,第一个标签对象是指向一个树对象(tree),而不是指向一个提交(commit)).
    1. 签名的标签
    • 如果你配有GPG key,那么你就很容易创建签名的标签.首先你要在你的 *.git/config 或 *~.gitconfig里配好key.
      下面是示例:
    [user] 
           signingkey = <gpg-key-id>
    
    • 你也可以用命令行来配置:
    $ git config (--global) user.signingkey <gpg-key-id>
    
    • 现在你可以直接用"-s" 参数来创“签名的标签”。
    $ git tag -s stable-1 1b2e1d63ff
    
    • 如果没有在配置文件中配GPG key,你可以用"-u“ 参数直接指定。
    $ git tag -u <gpg-key-id> stable-1 1b2e1d63ff
    

    相关文章

      网友评论

          本文标题:gitbook 读书笔记

          本文链接:https://www.haomeiwen.com/subject/evnncxtx.html