Ubuntu如何使用GitHub(二)

来源:互联网 发布:浙江软件考试网 编辑:程序博客网 时间:2024/05/21 04:22

        本篇主要针对Github使用的基本操作及git、github相关的应用知识进行必要的普及,对我本人来说,也是一种学习和总结归纳的过程。

        闲话少说,快速进入正题。

Git前述

Git 是把数据看作是对小型文件系统的一组快照。每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。Git 对待数据更像是一个快照流

git又三种状态,你的文件只能处于其中一种:已提交(committed)、已修改(modified)和已暂存(staged)。已提交数据,表示数据已经安全地保存在本地数据库中;已修改,表示修改了文件,但还没保存到数据库中;已暂存表示对一个已修复文件的当前版本做了标记,使之包含在下次提交的快照中。由此引入git项目的三个工作区域的概念:git仓库、工作目录以及暂存区域。

git仓库目录是git用来保存项目的元数据和对象数据库的地方。这是git中最重要的部分,从其他计算机克隆仓库时,拷贝的就是这里的数据。

工作目录是项目的某个独立版本独立提取出来的内容。这些从git仓库的压缩数据库中提取出来的文件放在磁盘上供你使用或修改。

暂存区域是一个文件,保存了下次将提交的文件列表信息,一般在git仓库目录中。有时候也叫作“索引”,不过一般说法还是做暂存区域。

基本的git工作流程如下:

1、在工作目录中修改文件;

2、暂存文件,将文件的快照放入暂存区域;

3、提交更新,找到暂存区域的文件,将快照永久性存储到git仓库目录。

如果 Git 目录中保存着的特定版本文件,就属于已提交状态。如果作了修改并已放入暂存区域,就属于已暂存状态。如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。

关于初次运行git前的配置,在本系列文章的第一篇已经进行了相关的论述和说明,这里稍作些补充。

Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量。这些变量存储在三个不同的位置:

  1. /etc/gitconfig 文件: 包含系统上每一个用户及他们仓库的通用配置。如果使用带有 --system 选项的git config 时,它会从此文件读写配置变量。

  2. ~/.gitconfig~/.config/git/config 文件:只针对当前用户。可以传递 --global 选项让 Git 读写此文件。

  3. 当前使用仓库的 Git 目录中的 config 文件(就是 .git/config):针对该仓库。

每一个级别覆盖上一级别的配置,所以 .git/config 的配置变量会覆盖 /etc/gitconfig 中的配置变量。

当安装完 Git 应该做的第一件事就是设置你的用户名称与邮件地址。这样做很重要,因为每一个 Git 的提交都会使用这些信息,并且它会写入到你的每一次提交中,不可更改:

$ git config --global user.name "John Doe"$ git config --global user.email johndoe@example.com

再次强调,如果使用了 --global 选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事情, Git 都会使用那些信息。当你想针对特定项目使用不同的用户名称与邮件地址时,可以在那个项目目录下运行没有--global 选项的命令来配置。

很多 GUI 工具都会在第一次运行时帮助你配置这些信息。

如果想要检查你的配置,可以使用 git config --list命令来列出所有git当时能找到的配置。

你可能会找到重复的变量名,因为git会从不同的文件中读取同一个配置(例如:/etc/gitconfig~/.gitconfig)。这种情况下,Git 会使用它找到的每一个变量的最后一个配置。

你可以通过输入 git config <key>: 来检查 Git 的某一项配置。

若你是用git时需要获取帮助,有三种方法可以找到git命令的使用手册:
$ git help <verb>$ git <verb> --help$ man git-<verb>

Git操作

接下来的学习将是配置并初始化一个仓库(repository)、开始或停止跟踪(track)文件、暂存(stage)或提交(commit)修改。本文也将向你演示如何配置 Git 来忽略指定的文件和文件模式、如何迅速而简单地撤销错误操作、如何浏览你的项目的历史版本以及不同提交(commits)间的差异、如何向你的远程仓库推送(push)以及如何从你的远程仓库拉取(pull)文件。

获取git仓库

有两种取得 Git 项目仓库的方法。第一种是在现有项目或目录下导入所有文件到 Git 中;第二种是从一个服务器克隆一个现有的 Git 仓库。

在现有目录中初始化仓库

如果你打算使用 Git 来对现有的项目进行管理,你只需要进入该项目目录并输入:

$ git init

该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干。但是,在这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪。

如果你是在一个已经存在文件的文件夹(而不是空文件夹)中初始化 Git 仓库来进行版本控制的话,你应该开始跟踪这些文件并提交。你可通过 git add 命令来实现对指定文件的跟踪,然后执行git commit 提交:

$ git add *.c$ git add LICENSE$ git commit -m 'initial project version'

克隆现有的仓库

如果你想获得一份已经存在了的 Git 仓库的拷贝,比如说,你想为某个开源项目贡献自己的一份力,这时就要用到 git clone 命令。这是 Git 区别于其它版本控制系统的一个重要特性,Git 克隆的是该 Git 仓库服务器上的几乎所有数据,而不是仅仅复制完成你的工作所需要文件。当你执行git clone 命令的时候,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。事实上,如果你的服务器的磁盘坏掉了,你通常可以使用任何一个克隆下来的用户端来重建服务器上的仓库。

克隆仓库的命令格式是 git clone [url] 。比如,要克隆 Git 的可链接库 libgit2,可以用下面的命令:

$ git clone https://github.com/libgit2/libgit2

这会在当前目录下创建一个名为 “libgit2” 的目录,并在这个目录下初始化一个 .git 文件夹,从远程仓库拉取下所有数据放入.git 文件夹,然后从中读取最新版本的文件的拷贝。如果你进入到这个新建的 libgit2 文件夹,你会发现所有的项目文件已经在里面了,准备就绪等待后续的开发和使用。如果你想在克隆远程仓库的时候,自定义本地仓库的名字,你可以使用如下命令:

$ git clone https://github.com/libgit2/libgit2 mylibgit

这将执行与上一个命令相同的操作,不过在本地创建的仓库名字变为 mylibgit

Git 支持多种数据传输协议。上面的例子使用的是 https:// 协议,不过你也可以使用 git:// 协议或者使用 SSH 传输协议,比如user@server:path/to/repo.git

检查当前文件状态

要查看哪些文件处于什么状态,可以用 git status 命令。如果在克隆仓库后立即使用此命令,会看到类似这样的输出:

$ git statusOn branch masternothing to commit, working directory clean
这说明你现在的工作目录相当干净。换句话说,所有已跟踪文件在上次提交后都未被更改过。此外,上面的信息还表明,当前目录下没有出现任何处于未跟踪状态的新文件,否则 Git 会在这里列出来。最后,该命令还显示了当前所在分支,并告诉你这个分支同远程服务器上对应的分支没有偏离。现在,分支名是 “master”,这是默认的分支名。

现在,让我们在项目下创建一个新的 README 文件。如果之前并不存在这个文件,使用 git status 命令,你将看到一个新的未跟踪文件:

$ echo 'My Project' > README$ git statusOn branch masterUntracked files:  (use "git add <file>..." to include in what will be committed)    READMEnothing added to commit but untracked files present (use "git add" to track)

在状态报告中可以看到新建的 README 文件出现在 Untracked files 下面。未跟踪的文件意味着 Git 在之前的快照(提交)中没有这些文件;Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文件”,这样的处理让你不必担心将生成的二进制文件或其它不想被跟踪的文件包含进来。不过现在的例子中,我们确实想要跟踪管理 README 这个文件。

跟踪新文件

使用命令 git add 开始跟踪一个文件。所以,要跟踪 README 文件,运行:

$ git add README

此时再运行 git status 命令,会看到 README 文件已被跟踪,并处于暂存状态:

$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    new file:   README

只要在 Changes to be committed 这行下面的,就说明是已暂存状态。如果此时提交,那么该文件此时此刻的版本将被留存在历史记录中。你可能会想起之前我们使用git init 后就运行了 git add (files) 命令,开始跟踪当前目录下的文件。git add 命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。

暂存已修改文件

现在我们来修改一个已被跟踪的文件。如果你修改了一个名为 CONTRIBUTING.md 的已被跟踪的文件,然后运行 git status 命令,会看到下面内容:

$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    new file:   READMEChanges not staged for commit:  (use "git add <file>..." to update what will be committed)  (use "git checkout -- <file>..." to discard changes in working directory)    modified:   CONTRIBUTING.md

文件 CONTRIBUTING.md 出现在 Changes not staged for commit 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。要暂存这次更新,需要运行git add 命令。这是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。将这个命令理解为“添加内容到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。现在让我们运行git add 将"CONTRIBUTING.md"放到暂存区,然后再看看 git status 的输出:

$ git add CONTRIBUTING.md$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    new file:   README    modified:   CONTRIBUTING.md

现在两个文件都已暂存,下次提交时就会一并记录到仓库。假设此时,你想要在 CONTRIBUTING.md 里再加条注释,重新编辑存盘后,准备好提交。不过且慢,再运行git status 看看:

$ vim CONTRIBUTING.md$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    new file:   README    modified:   CONTRIBUTING.mdChanges not staged for commit:  (use "git add <file>..." to update what will be committed)  (use "git checkout -- <file>..." to discard changes in working directory)    modified:   CONTRIBUTING.md

怎么回事?现在 CONTRIBUTING.md 文件同时出现在暂存区和非暂存区。这怎么可能呢?好吧,实际上 Git 只不过暂存了你运行git add 命令时的版本,如果你现在提交,CONTRIBUTING.md 的版本是你最后一次运行 git add 命令时的那个版本,而不是你运行 git commit 时,在工作目录中的当前版本。所以,运行了 git add 之后又作了修订的文件,需要重新运行 git add 把最新版本重新暂存起来:

$ git add CONTRIBUTING.md$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    new file:   README    modified:   CONTRIBUTING.md

状态简览

git status 命令的输出十分详细,但其用语有些繁琐。如果你使用 git status -s 命令或git status --short 命令,你将得到一种更为紧凑的格式输出。运行 git status -s ,状态报告输出如下:

$ git status -s M READMEMM RakefileA  lib/git.rbM  lib/simplegit.rb?? LICENSE.txt

新添加的未跟踪文件前面有 ?? 标记,新添加到暂存区中的文件前面有 A 标记,修改过的文件前面有 M 标记。你可能注意到了 M 有两个可以出现的位置,出现在右边的 M 表示该文件被修改了但是还没放入暂存区,出现在靠左边的M 表示该文件被修改了并放入了暂存区。例如,上面的状态报告显示: README 文件在工作区被修改了但是还没有将修改后的文件放入暂存区,lib/simplegit.rb 文件被修改了并将修改后的文件放入了暂存区。而Rakefile 在工作区被修改并提交到暂存区后又在工作区中被修改了,所以在暂存区和工作区都有该文件被修改了的记录。

忽略文件

一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。在这种情况下,我们可以创建一个名为.gitignore 的文件,列出要忽略的文件模式。来看一个实际的例子:

$ cat .gitignore*.[oa]*~

第一行告诉 Git 忽略所有以 .o.a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。第二行告诉 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。要养成一开始就设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件。

文件 .gitignore 的格式规范如下:

  • 所有空行或者以 开头的行都会被 Git 忽略。

  • 可以使用标准的 glob 模式匹配。

  • 匹配模式可以以(/)开头防止递归。

  • 匹配模式可以以(/)结尾指定目录。

  • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。

所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如[0-9] 表示匹配所有 0 到 9 的数字)。使用两个星号(*) 表示匹配任意中间目录,比如`a/**/z` 可以匹配a/z, a/b/z 或 `a/b/c/z`等。

我们再看一个 .gitignore 文件的例子:

# no .a files*.a# but do track lib.a, even though you're ignoring .a files above!lib.a# only ignore the TODO file in the current directory, not subdir/TODO/TODO# ignore all files in the build/ directorybuild/# ignore doc/notes.txt, but not doc/server/arch.txtdoc/*.txt# ignore all .pdf files in the doc/ directorydoc/**/*.pdf

查看已暂存和未暂存的修改

如果 git status 命令的输出对于你来说过于模糊,你想知道具体修改了什么地方,可以用 git diff 命令。稍后我们会详细介绍git diff,你可能通常会用它来回答这两个问题:当前做的哪些更新还没有暂存?有哪些更新已经暂存起来准备好了下次提交?尽管 git status 已经通过在相应栏下列出文件名的方式回答了这个问题,git diff 将通过文件补丁的格式显示具体哪些行发生了改变。

假如再次修改 README 文件后暂存,然后编辑 CONTRIBUTING.md 文件后先不暂存,运行 status 命令将会看到:

$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    modified:   READMEChanges not staged for commit:  (use "git add <file>..." to update what will be committed)  (use "git checkout -- <file>..." to discard changes in working directory)    modified:   CONTRIBUTING.md

要查看尚未暂存的文件更新了哪些部分,不加参数直接输入 git diff

$ git diffdiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 8ebb991..643e24f 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -65,7 +65,8 @@ branch directly, things can get messy. Please include a nice description of your changes when you submit your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change-merged in.+merged in. Also, split your changes into comprehensive chunks if your patch is+longer than a dozen lines. If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's

此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。

若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --cached 命令。(Git 1.6.1 及更高版本还允许使用git diff --staged,效果是相同的,但更好记些。)

$ git diff --stageddiff --git a/README b/READMEnew file mode 100644index 0000000..03902a1--- /dev/null+++ b/README@@ -0,0 +1 @@+My Project

请注意,git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。所以有时候你一下子暂存了所有更新过的文件后,运行 git diff 后却什么也没有,就是这个原因。

像之前说的,暂存 CONTRIBUTING.md 后再编辑,运行 git status 会看到暂存前后的两个版本。如果我们的环境(终端输出)看起来如下:

$ git add CONTRIBUTING.md$ echo '# test line' >> CONTRIBUTING.md$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    modified:   CONTRIBUTING.mdChanges not staged for commit:  (use "git add <file>..." to update what will be committed)  (use "git checkout -- <file>..." to discard changes in working directory)    modified:   CONTRIBUTING.md

现在运行 git diff 看暂存前后的变化:

$ git diffdiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 643e24f..87f08c8 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -119,3 +119,4 @@ at the ## Starter Projects See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).+# test line

然后用 git diff --cached 查看已经暂存起来的变化:(--staged 和 --cached 是同义词)

$ git diff --cacheddiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 8ebb991..643e24f 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -65,7 +65,8 @@ branch directly, things can get messy. Please include a nice description of your changes when you submit your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change-merged in.+merged in. Also, split your changes into comprehensive chunks if your patch is+longer than a dozen lines. If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's

提交更新

现在的暂存区域已经准备妥当可以提交了。在此之前,请一定要确认还有什么修改过的或新建的文件还没有 git add 过,否则提交的时候不会记录这些还没暂存起来的变化。这些修改过的文件只保留在本地磁盘。所以,每次准备提交前,先用git status 看下,是不是都已暂存起来了,然后再运行提交命令 git commit

$ git commit

这种方式会启动文本编辑器以便输入本次提交的说明。(默认会启用 shell 的环境变量 $EDITOR 所指定的软件,一般都是 vim 或 emacs。当然也可以按照起步 介绍的方式,使用git config --global core.editor 命令设定你喜欢的编辑软件。)

编辑器会显示类似下面的文本信息(本例选用 Vim 的屏显方式展示):

# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.# On branch master# Changes to be committed:#new file:   README#modified:   CONTRIBUTING.md#~~~".git/COMMIT_EDITMSG" 9L, 283C

可以看到,默认的提交消息包含最后一次运行 git status 的输出,放在注释行里,另外开头还有一空行,供你输入提交说明。你完全可以去掉这些注释行,不过留着也没关系,多少能帮你回想起这次更新的内容有哪些。(如果想要更详细的对修改了哪些内容的提示,可以用-v 选项,这会将你所做的改变的 diff 输出放到编辑器中从而使你知道本次提交具体做了哪些修改。)退出编辑器时,Git 会丢掉注释行,用你输入提交附带信息生成一次提交。

另外,你也可以在 commit 命令后添加 -m 选项,将提交信息与命令放在同一行,如下所示:

$ git commit -m "Story 182: Fix benchmarks for speed"[master 463dc4f] Story 182: Fix benchmarks for speed 2 files changed, 2 insertions(+) create mode 100644 README

好,现在你已经创建了第一个提交!可以看到,提交后它会告诉你,当前是在哪个分支(master)提交的,本次提交的完整 SHA-1 校验和是什么(463dc4f),以及在本次提交中,有多少文件修订过,多少行添加和删改过。

请记住,提交时记录的是放在暂存区域的快照。任何还未暂存的仍然保持已修改状态,可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。

跳过使用暂存区域

尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit 加上-a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤:

$ git statusOn branch masterChanges not staged for commit:  (use "git add <file>..." to update what will be committed)  (use "git checkout -- <file>..." to discard changes in working directory)    modified:   CONTRIBUTING.mdno changes added to commit (use "git add" and/or "git commit -a")$ git commit -a -m 'added new benchmarks'[master 83e38c7] added new benchmarks 1 file changed, 5 insertions(+), 0 deletions(-)
看到了吗?提交之前不再需要 git add 文件“CONTRIBUTING.md”了

移除文件

要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 “Changes not staged for commit” 部分(也就是未暂存清单)看到:

$ rm PROJECTS.md$ git statusOn branch masterYour branch is up-to-date with 'origin/master'.Changes not staged for commit:  (use "git add/rm <file>..." to update what will be committed)  (use "git checkout -- <file>..." to discard changes in working directory)        deleted:    PROJECTS.mdno changes added to commit (use "git add" and/or "git commit -a")

然后再运行 git rm 记录此次移除文件的操作:

$ git rm PROJECTS.mdrm 'PROJECTS.md'$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    deleted:    PROJECTS.md

下一次提交时,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f(译注:即 force 的首字母)。这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。

另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。当你忘记添加.gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。为达到这一目的,使用--cached 选项:

$ git rm --cached README

git rm 命令后面可以列出文件或者目录的名字,也可以使用 glob 模式。比方说:

$ git rm log/\*.log

注意到星号 * 之前的反斜杠 \,因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开。此命令删除log/ 目录下扩展名为 .log 的所有文件。类似的比如:

$ git rm \*~

该命令为删除以 ~ 结尾的所有文件。

移动文件

要在 Git 中对文件改名,可以这么做:

$ git mv file_from file_to

它会恰如预期般正常工作。实际上,即便此时查看状态信息,也会明白无误地看到关于重命名操作的说明:

$ git mv README.md README$ git statusOn branch masterChanges to be committed:  (use "git reset HEAD <file>..." to unstage)    renamed:    README.md -> README

其实,运行 git mv 就相当于运行了下面三条命令:

$ mv README.md README$ git rm README.md$ git add README

如此分开操作,Git 也会意识到这是一次改名,所以不管何种方式结果都一样。两者唯一的区别是,mv 是一条命令而另一种方式需要三条命令,直接用git mv 轻便得多。不过有时候用其他工具批处理改名的话,要记得在提交前删除老的文件名,再添加新的文件名。


本篇主要面向的是git的一般基础操作,关于更多的提交方面的操作,将放在后续篇目中予以展开。

同样只是作为一种参考方法,还望各位指正,不胜感激。