`

Git入门教程

    博客分类:
  • SCM
阅读更多

以前一直是用SVN来做版本控制,最近维护公司一个产品,要求使用git来取代SVN。

 

现在就git的一些学习经验进行总结:

 

1.   概述

 

为什么要选择Git? 你真正学会使用Git时, 你就会觉得这个问题的回答是非常自然的。然而当真正需要用文字来回答时,却觉得文字好像不是那么够用。 咳,该怎么回答呢?

 

其实,关键的问题不在于如何回答这个问题。 问题的关键是公司已经决定使用它了。那么,我们的程序员们! 请开动你们的浏览器,请拿出你的搜索引擎工具,去自己发掘答案吧。在这里,我只能给你们一个最朦胧的感觉。

 

Git和 CVS、SVN不同,是一个分布式的源代码管理工具。Linux内核的代码就是用Git管理的。它很强,也很快。它给我们带来的直接好处有:

 

1. 傻瓜都会的初始化, git init, git commit -a, 就完了。对于随便写两行代码就要放到代码管理工具里的人来说,再合适不过。也可以拿 git 做备份系统,或者同步两台机器的文档,都很方便。

2. 绝大部分操作在本地完成,不用和集中的代码管理服务器交互,终于可以随时随地大胆地 check in 代码了。 只有最终完成的版本才需要向一个中心的集中的代码管理服务器提交。

3. 每次提交都会对所有代码创建一个唯一的 commit id 。不像 CVS 那样都是对单个文件分别进行版本的更改。所以你可以一次性将某次提交前的所有代码 check 出来,而不用考虑到底提交过那些文件。(其实 SVN 也可以做到这点)

4. branch 管理容易多了,无论是建立新的 branch ,还是在 branch 之间切换都一条命令完成,不需要建立多余的目录。

    5. branch 之间 merge 时,不仅代码会 merge 在一起, check in 历史也会保留,这点非常重要。

 

From gitHost.cn

1 、更方便的 Merge

分布式管理必然导致大量的 Branch Merge 操作。因此分布式版本控制系统都特别注意这方面。在传统的 CVS 里面制作 Branch Merge 简直就是噩梦, Subversion 作为一个用于替代 CVS 的系统,专门改进了 Branch 操作。然而似乎人们没有注意到, Branch 是轻松了,可是 Merge 呢?如果不能很方便地 Merge 回来,做 Branch 仍然是噩梦。事实上,我就经历过在开发团队里面由于队友操作不对而在 Merge 的时候把我的许多代码都覆盖掉了。当时正是使用的 subversion 。虽然源代码仍然在历史里面,但是要去一个一个地找出被覆盖掉的文件并恢复过来确实是一件很难忘的事情。

2 、更方便的管理

传统的版本控制系统使用中央仓库,一些仓库相关的管理就只能在仓库上进行。赋予开发团队每一个人中央仓库的管理权限是非常不好的。但是有时候确实会比较不方便的地方。

3 、更健壮的系统

分布式系统一般情况下总是比单服务端的系统要健壮,因为但服务端一旦服务器挂掉了整个系统就不能运行了。然而分布式系统通常不会因为一两个节点而受到影响。

4 、对网络的依赖性更低

虽然现在网络非常普及,但是并不是随时随地都有高速网络,甚至有时候根本没有网络可以访问。低速的网络会让人心情烦躁,有时候就呆呆地盯着屏幕上的 commit 进度,什么事情也干不了。而没有网络连接更是致命的:你无法 commit !这表示你进行任何改动以前都必须小心翼翼,否则你可能再也找不会你曾经写的一些代码了。

5 、更少的“仓库污染”

有时候你要做一个模块,它不是太大,所以没有必要为它新建一个 branch , 但是它又不是那么小,不可能一次提交就做好。于是便会提交一些不完整的代码到仓库,有时候会导致整个程序无法运行,严重影响团队里其他人的开发。大多数人 在这种情况下的解决办法都是写完之后再提交。但是作为习惯了版本控制的人来说,进行不计后果的大幅修改是经常的事情,到后来突然发现自己先前的代码没有提 交,就后悔莫及了。如果是分布式系统的话就不会存在这样的问题,因为本地仓库的修改不会影响到别人的仓库。当你完成并测试以后,就可以在邮件列表里面说: 我已经把这个模块做好了。然后感兴趣的人就可以从你这里 pull 你的成果了。

虽然网上各种对 Git 的誉美之词决不止于此,但是在 Git 的主站上,还是尽可能客观的对 Git Subversion 进行了一番比较。( GitSvnComparsion http://git.or.cz/gitwiki/GitSvnComparsion )。另外, Subversion 目前通过 SVK 也已经提供了一定程度上的源代码库分布式的管理能力。能够实现源代码的离线提交等功能。

 

当然,Git也会带给我们一些困难,首先,你想要使用好git,就要真正明白它的原理,理解它的观念, 对以那些CVS的熟手来说,改变你已经固有的纯集中式源代码管理的观念尤为重要,同时也会让你觉得有些困难。在使用git的初期,你可能会觉得有些困难, 但等你逐渐明白它时,你绝对会喜欢上它。这是一定的,就像我问你“喜欢一个温吞如水、毫无感觉的主妇,还是喜欢一个奔放如火,让你爱的痴狂恨的牙痒的情人 ”一样毋庸置疑。
    下面,就让我们进入学习Git之旅…
    请记住,这只是一个非常简单而且初级的教程, 想要成为git的专家,需要各位同事不断的自己深入挖掘。

2. Git基础命令

2.1   创建Git库—git init

你们曾经创建过CVS的库么?应该很少有人操作过吧?因为很多人都是从CVS库里checkout代码。同样,在合作开发中,如果你不是一个代码模块的发 起者,也不会使用到这个命令,更多的是使用git-clone(见2.7节)。 但是,如果你想个人开发一个小模块,并暂时用代码管理工具管理起来(其实我就常这么做,至少很多个人开发过程都可以保留下来,以便备份和恢复),创建一个 Git库是很容易和方便的。
当一个代码的Git库创建后,会添加代码文件到库里,并将这个库放到公司一个专门用来进行代码管理的服务器上,使大家可以在以后clone(不明白?没关系,继续往后看就明白了)它。对于个人来说,你可以随便将这个库放到哪里,只要你能访问的到就行。

创建一个Git库是很容易和方便的,只要用命令 git init 就可以了。在Git1.4之前(包括git1.4)的版本,这个命令是git-init。

a)         $ mkdir git_repository_test
    b)         $ cd git_repository_test
    c)         $ git init

 

这样,一个空的版本库就创建好了,并在当前目录中创建一个叫 .git 的子目录。以后,所以的文件变化信息都会保存到这个目录下,而不像CVS那样,会在每个目录和子目录下都创建一个讨厌的CVS目录。 在.git目录下有一个config文件, 需要我们添加一下个人信息后才能使用。否则我们不能对其中添加和修改任何文件。

原始的config文件是这样的,
[core]
        ls= 0
        filemode = true
        bare = false
        logallrefupdates = true
我们需要加入
[user]
        name = xxx
        emai= xxx@gmail.com
    现在已经创建好了一个 git 版本库,但是它是空的,还不能做任何事情,下一步就是怎么向版本库中添加文件了。如果希望忽略某些文件,需要在git库根目录下添加. gitignore文件。

 

2.2   一条重要的命令 -- git-update-index

   在介绍如何向git库中添加文件前,不得不先介绍git-update-index命令。这条命令可能会使很多熟悉CVS的用户疑惑, 一般来说,我们向一个源代码管理库提交代码的更改,都会抽象为以下的动作:更改文件;向源码管理系统标识变化;提交。比如从一个CVS库里删除一个文件, 需要先删除文件,然后cvs delete; 最后cvs commit。
因此, git-update-index就是向源码管理系统标识文件变化的一个抽象操作。说的简要一些,git-update-index命令就是通知git库 有文件的状态发生了变化(新添、修改、删除等待)。这条命令在早期的git版本中是非常常用的。 在新的git版本(1.5版本及以后)已经被其它命令包装起来,并且不推荐使用了。
    git-update-index最常用的方式有以下两种,更多功能请man git-update-index。

方法一:git-update-index --add 文件名列表。 如果文件存在,则这条命令是向git库标识该文件发生过变化(无论是否该文件确实被修改过),如果文件不存在,则这条命令是向git库表示需要加入一个新文件。

方法二: git-update-index --force-remove 文件名列表。 这表示向git库表示要从库中删除文件。无论该文件是否已经被删除,这条命令仅仅是通知git库要从库中删除这些文件。这些文件都不会受影响。
    因此,git-update-index仅仅是向git库起到一个通知和标识的作用,并不会操作具体的文件。

 

2.3   向git库中添加或删除文件 – git-add、git-rm

其实,说使用git-add命令向git库里添加文件是不对的, 或者说至少是不全面的。git-add 命令的本质是命令"git-update-index --add” 的一个包装。因此,git-add除了可以添加文件,还可以标识文件修改。在调用了git-add后,才可以做commit操作。git-rm 也是一样, 它是git-update-index --force-remove的一个包装。
    对于git-add来说, 如果在一个目录下调用了git-add * ,则默认是递归将子目录中所有文件都add到git库中。对于git-rm来说,也是一样。 这点和CVS有较大区别。
    此外,我们还可以通过命令git-ls-files来查看当前的git库中有那些文件。

 

2.4   查看版本库状态—git-status
    通过该命令,我们可以查看版本库的状态。可以得知那些文件发生了变化,那些文件还没有添加到git库中等等。 建议每次commit前都要通过该命令确认库状态。以避免误操作。
    其总,最常见的误操作是, 修改了一个文件, 没有调用git-add通知git库该文件已经发生了变化就直接调用commit操作, 从而导致该文件并没有真正的提交。如果这时如果开发者以为已经提交了该文件,就继续修改甚至删除这个文件,那么修改的内容就没有通过版本管理起来。如果每 次在提交前,使用git-status查看一下,就可以发现这种错误。因此,如果调用了git-status命令,一定要格外注意那些提示为 “Changed but not updated:”的文件。 这些文件都是与上次commit相比发生了变化,但是却没有通过git-add标识的文件。
    2.5   向版本库提交变化 – git-commit
    直接调用git-commit命令,会提示填写注释。也可以通过如下方式在命令行就填写提交注释:git-commit -m "Initial commit of gittutor reposistory"。 注意,和CVS不同,git的提交注释必须不能为空。否则就会提交失败。
    git-commit还有一个 –a的参数,可以将那些没有通过git-add标识的变化一并强行提交,但是不建议使用这种方式。
    每一次提交,git就会为全局代码建立一个唯一的commit标识代码,用户可以通过git-revert命令恢复到任意一次提交时的代码。 这比CVS不同文件有不同的版本呢号管理可方便多了。(和SVN类似)
    如果提交前,想看看具体那些文件发生变化,可以通过git-diff来查看, 不过这个命令的输出并不友好。因此建议用别的工具来实现该功能。在提交后,还可以通过git-log命令来查看提交记录。

 

2.6   分支管理 – git-branch
    我们迎来了git最强大,也是比CVS、SVN强大的多的功能 — 分支管理。
    大概每个程序员都会经常遇到这样的情况:
    1.需要立刻放下手头的工作,去修改曾经一个版本的bug并上线,然后再继续当前的工作。
    2.本想向中心库commit一个重要修改,但是由于需要经常备份代码,最终不得不频繁的向中心库commit。从而导致大量无用的commit信息被保留在中心库中。
    3.将一次修改提交同事进行code review, 但是由于同事code review比较慢, 得到反馈时,自己的代码已经发生了变化,从而导致合并异常困难
    这些场景,如果用CVS或者SVN来解决,虽说不一定解决不了,但过程之繁琐,之复杂,肯定另所有人都有生不如死的感觉吧!究其关键,就是CVS或者SNV的branch管理太复杂,基本不具可用性。
    在 git 版本库中创建分支的成本几乎为零,所以,不必吝啬多创建几个分支。当第一次执行git-init时,系统就会创建一个名为”master”的分支。 而其它分支则通过手工创建。下面列举一些常见的分支策略,这些策略相信会对你的日常开发带来很大的便利。
   1.创建一个属于自己的个人工作分支,以避免对主分支 master 造成太多的干扰,也方便与他人交流协作。
   2.当进行高风险的工作时,创建一个试验性的分支,扔掉一个烂摊子总比收拾一个烂摊子好得多。
   3.合并别人的工作的时候,最好是创建一个临时的分支用来合并,合并完成后在“fatch”到自己的分支(合并和fatch后面有讲述,不明白就继续往下看好了)


    2.6.1 查看分支 – git-branch
    调用git-branch可以查看程序中已经存在的分支和当前分支
    2.6.2 创建分支 – git-branch 分支名
    要创建一个分支,可以使用如下方法:
    1. git-branch 分支名称
    2. git-checout –b 分支名
    使用第一种方法,虽然创建了分支,但是不会将当前工作分支切换到新创建的分支上,因此,还需要命令”git-checkout 分支名” 来切换, 而第二种方法不但创建了分支,还将当前工作分支切换到了该分支上。
    另外,需要注意,分支名称是有可能出现重名的情况的, 比如说,我在master分支下创建了a和b两个分支, 然后切换到b分支,在b分支下又创建了a和c分支。 这种操作是可以进行的。 此时的a分支和master下的a分支实际上是两个不同的分支。 因此,在实际使用时,不建议这样的操作,这样会带来命名上的疑惑。
2.6.3 删除分支 – git-branch –D
    git-branch –D 分支名可以删除分支,但是需要小心,删除后,发生在该分支的所有变化都无法恢复。
2.6.4 切换分支 – git-checkout 分支名
    如果分支已经存在, 可以通过 git-checkout 分支名 来切换工作分支到该分支名
2.6.5 查看分支历史 –git-show-branch
    调用该命令可以查看分支历史变化情况。 如:
* [dev1] d2
! [master] m2
--
* [dev1] d2
* [dev1^] d1
* [dev1~2] d1
*+ [master] m2
    在上述例子中, “--”之上的两行表示有两个分支dev1和master, 且dev分支上最后一次提交的日志是“d2”,master分支上最后一次提交的日志是”m2”。 “--”之下的几行表示了分支演化的历史,其中 dev1表示发生在dev分支上的最后一次提交,dev^表示发生在dev分支上的倒数第二次提交。dev1~2表示发生在dev分支上的倒数第三次提 交。
2.6.6 合并分支 – git-merge
    git-merge的用法为:git-merge “some memo” 合并的目标分支 合并的来源分支。如:
    git-merge master dev1~2
    如果合并有冲突,git会由提示,当前,git-merge已经很少用了, 用git-pull来替代了。
    用法为:git-pull 合并的目标分支 合并的来源分支。 如git-pull . dev1^


2.7   远程获取一个git库 git-clone
    在2.1节提到过,如果你不是一个代码模块的发起者,也不会使用到git-init命令,而是更多的是使用git-clone。通过这个命令,你可以从远端完整获取一个git库,并可以通过一些命令和远端的git交互。
    基于git的代码管理的组织结构,往往形成一个树状结构,开发者一般从某个代码模块的管理者的git库通过git-clone取得开发环境,在本地迭代开 发后,再提交给该模块的管理者,该模块的管理者检查这些提交并将代码合并到自己的库中,并向更高一级的代码管理者提交自己的模块代码。
    对于我们公司,会有一个中心的git库, 大家在开发时,都是从中心库git-clone获取最新代码。
git-clone的使用方法如下: git-clone [ssh://]username@ipaddr:path。 其中, “ssh://”可选,也有别的获取方式,如rsync。 Path是远端git的根路径,也叫repository。
    通过git-clone获取远端git库后,.git/config中的开发者信息不会被一起clone过来。仍然需要为.git/config文件添加开发者信息。此外,开发者还需要自己添加. gitignore文件
   另外,通过git-clone获取的远端git库,只包含了远端git库的当前工作分支。如果想获取其它分支信息,需要使用”git-branch –r” 来查看, 如果需要将远程的其它分支代码也获取过来,可以使用命令” git checkout -b 本地分支名 远程分支名”,其中,远程分支名为git-branch –r所列出的分支名, 一般是诸如“origin/分支名”的样子。如果本地分支名已经存在, 则不需要“-b”参数。

2.8   从远程获取一个git分支 – git-pull
    与git-clone不同, git-pull可以从任意一个git库获取某个分支的内容。用法如下:
    git-pull username@ipaddr : 远端repository名 远端分支名:本地分支名。这条命令将从远端git库的远端分支名获取到本地git库的一个本地分支中。其中,如果不写本地分支名,则默认pull到本地当前分支。
需要注意的是,git-pull也可以用来合并分支。 和git-merge的作用相同。 因此,如果你的本地分支已经有内容,则git-pull会合并这些文件,如果有冲突会报警。

2.9   将本地分支内容提交到远端分支 – git-push
    git-push和git-pull正好想反,是将本地某个分支的内容提交到远端某个分支上。用法:
git-push username@ipaddr : 远端repository名 本地分支名:远端分支名。这条命令将本地git库的一个本地分支push到远端git库的远端分支名中。

    需要格外注意的是,git-push好像不会自动合并文件。这点我的试验表明是这样,但我不能确认是否是我用错了。因此,如果git-push时,发生了冲突,就会被后push的文件内容强行覆盖,而且没有什么提示。 这在合作开发时是很危险的事情。
2.10     库的逆转与恢复 – git-reset
    库的逆转与恢复除了用来进行一些废弃的研发代码的重置外,还有一个重要的作用。比如我们从远程clone了一个代码库,在本地开发后,准备提交回远程。但是本地代码库在开发时,有功能性的commit,也有出于备份目的的commit等等。总之,commit的日志中有大量无用log,我们并不想把这些 log在提交回远程时也提交到库中。 因此,就要用到git-reset。
    Git-reset的概念比较复杂。它的命令形式:git-reset [--mixed | --soft | --hard] [<commit-ish>]
命令的选项:
--mixed
    这个是默认的选项。 如git-reset [--mixed] dev1^(dev1^的定义可以参见2.6.5)。它的作用仅是重置分支状态到dev1^, 但是却不改变任何工作文件的内容。即,从dev1^到dev1的所有文件变化都保留了,但是dev1^到dev1之间的所有commit日志都被清除了, 而且,发生变化的文件内容也没有通过git-add标识,如果您要重新commit,还需要对变化的文件做一次git-add。 这样,commit后,就得到了一份非常干净的提交记录。
--soft
    相当于做了git-reset –mixed,后,又对变化的文件做了git-add。如果用了该选项, 就可以直接commit了。
--hard
    这个命令就会导致所有信息的回退, 包括文件内容。 一般只有在重置废弃代码时,才用它。 执行后,文件内容也无法恢复回来了。

2.11     更多的操作
    之前的10节只简要介绍了git的基本命令,更多的细节可以去linux下man git的文档。此外,http://www.linuxsir.org/main/doc/git/gittutorcn.htm 也有不少更详细的介绍。

 

分享到:
评论
1 楼 somefuture 2009-11-26  
原创?真不可思议!不错。

相关推荐

Global site tag (gtag.js) - Google Analytics