系列学习笔记首次连载于公众号:“AInewworld”,关注了解更多~
很好很全的官方教程第二版(力推)
廖雪峰的官方网站
莫烦python的视频git教程
(1)git思想:直接记录快照,而非差异比较;
Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。这是 Git 同其他系统的重要区别。
(2)近乎所有操作都是本地执行;
在 Git 中的绝大多数操作都只需要访问本地文件和资源,不用连网。
(3)文件的三种状态
对于任何一个文件,在 Git 内都只有三种状态:
已提交(committed)
已提交表示该文件已经被安全地保存在本地数据库中了;
已修改(modified)
已修改表示修改了某个文件,但还没有提交保存;
已暂存(staged)
已暂存表示把已修改的文件放在下次提交时要保存的清单中。
每个项目都有一个隐藏的 Git 目录(注:如果 git clone 出来的话,就是其中 .git 的目录;如果 git clone –bare 的话,新建的目录本身就是 Git 目录。),它是 Git 用来保存元数据和对象数据库的地方。该目录非常重要,每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据,一般情况下绝对不要人为修改。
基本的 Git 工作流程如下:
在工作目录中修改某些文件。
对修改后的文件进行快照,然后保存到暂存区域。
提交更新,将保存在暂存区域的文件快照永久转储到 Git 目录中。
更多详细参考: 安装 Git
抽出来记录下常见的安装:
(1)在 Linux 上安装
在 Ubuntu 这类 Debian 体系的系统上,可以用 apt-get 安装:
$ apt-get install git
(2)在 Mac 上安装
在 Mac 上安装 Git 有两种方式。最容易的当属使用图形化的 Git 安装工具,下载地址在:
http://sourceforge.net/projects/git-osx-installer/
(3)在 Windows 上安装
在GitHub 的页面上下载 exe 安装文件并运行:
http://msysgit.github.com/
完成安装之后,就可以使用命令行的 git 工具(已经自带了 ssh 客户端)了,另外还有一个图形界面的 Git 项目管理工具。
注:windows命令行切换盘需要/d参数 :cd /d D:
初试使用时,主要需要配置一些用户信息;一般在新的系统上,我们都需要先配置下自己的 Git 工作环境。配置工作只需一次,以后升级时还会沿用现在的配置。当然,如果需要,你随时可以用相同的命令修改已有的配置。
(1)用户信息
第一个要配置的是你个人的用户名称和电子邮件地址。这两条配置很重要,每次 Git 提交时都会引用这两条信息,说明是谁提交了更新,所以会随更新内容一起被永久纳入历史记录:
$ git config --global user.name "John Doe"
$ git config --global user.email [email protected]
这里要修改的也就是自己的名字与邮箱;其他不变
如果用了 –global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 –global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。
其他配置
(2)文本编辑器配置
接下来要设置的是默认使用的文本编辑器。Git 需要你输入一些额外消息的时候,会自动调用一个外部文本编辑器给你用。默认会使用操作系统指定的默认编辑器,一般可能会是 Vi 或者 Vim。如果你有其他偏好,比如 Emacs 的话,可以重新设置:
$ git config --global core.editor emacs
(3)查看配置信息
要检查已有的配置信息,可以使用 git config –list 命令:
$ git config --list
user.name=Scott Chacon
[email protected]
color.status=auto
color.branch=auto
color.interactive=auto
color.diff=auto
...
也可以直接查阅某个环境变量的设定,只要把特定的名字跟在后面即可,像这样:
$ git config user.name
Scott Chacon
对于一个指令可以是呀git自带帮助查看使用方法。
比如,要学习 config 命令可以怎么用,运行:
$ git help config
创建一个版本库有两种方式,一种自己新建,一种克隆已有的。
创建一个仓库用于git管理,那么里面的文件都可以被git管理记录上,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
创建一个版本库非常简单,
(1)首先,选择一个合适的地方,创建一个空目录:
$ mkdir learngit
$ cd learngit
$ pwd
/Users/michael/learngit
(2)通过 git init 命令把这个目录变成Git可以管理的仓库:
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/
建立好后,通过ls命令可以发现当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用 ls -ah 命令就可以看见
(3)把文件添加到版本库
我们可以新建现在我们编写一个readme.txt文件,内容如下:
Git is a version control system.
Git is free software.
一定要放到learngit目录下(子目录也行),因为这是一个Git仓库,放到其他地方Git再厉害也找不到这个文件。
在这之前确保完成前面说的git前的配置,比如如果你没有注册自己的姓名与邮箱,就会出现提醒“tell me who you are”的错误.之后,
第一步,用命令git add告诉Git,把文件添加到仓库:
$ git add readme.txt
执行上面的命令,没有任何显示,这就对了,Unix的哲学是“没有消息就是好消息”,说明添加成功。
第二步,用命令git commit告诉Git,把文件提交到仓库:
$ git commit -m "wrote a readme file"
[master (root-commit) cb926e7] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
其中双冒号后面表示注释,这个注释还是很重要的,当你想看你的历史版本的时候,这个注释就会显示出来,所以commit添加一个明确的注释对于版本查找来说非常重要。
如果复制一个开源项目,可以先把该项目的 Git 仓库复制一份出来,这就需要用到 git clone 命令。
克隆仓库的命令格式为 git clone [url]。比如,要克隆 Ruby 语言的 Git 代码仓库 Grit,可以用下面的命令:
$ git clone git://github.com/schacon/grit.git
这会在当前目录下创建一个名为grit的目录,其中包含一个 .git 的目录,用于保存下载下来的所有版本记录,然后从中取出最新版本的文件拷贝。如果进入这个新建的 grit 目录,你会看到项目中的所有文件已经在里边了,准备好后续的开发和使用。
如果希望在克隆的时候,自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:
$ git clone git://github.com/schacon/grit.git mygrit
唯一的差别就是,现在新建的目录成了 mygrit,其他的都和上边的一样。
(1)检测当前文件状态
:要确定哪些文件当前处于什么状态,可以用 git status 命令。比如新建了一个test.txt,然后直接git status的结果:
D:\soft\git\learngit>git status
On branch master
No commits yet
Untracked files:
(use "git add ..." to include in what will be committed)
test.txt
nothing added to commit but untracked files present (use "git add" to track)
可以注意两点:
首先,自己建立了一个主分支 branch master;
其次出现了一个未跟踪的文件 Untracked files,提醒需要跟踪。
(2)跟踪新文件
使用命令 git add 开始跟踪一个新文件。
其实 git add 的潜台词就是把目标文件快照放入暂存区域,也就是 add file into staged area,同时未曾跟踪过的文件标记为需要跟踪。这样就好理解后续 add 操作的实际意义了。
注意:运行了 git add 之后又作了修订的文件,需要重新运行 git add 把最新版本重新暂存起来:
添加一个文件:
git add test.txt
假如一下有好多个文件需要添加:
git add .
(4)提交更新 重要
现在的暂存区域已经准备妥当可以提交了。在此之前,请一定要确认还有什么修改过的或新建的文件还没有 git add 过,否则提交的时候不会记录这些还没暂存起来的变化。所以,每次准备提交前,先用 git status 看下,是不是都已暂存起来了,然后再运行提交命令 git commit:
>> git commit -m "change1"
[master (root-commit) 0e77290] change1
3 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 1.txt
create mode 100644 2.txt
create mode 100644 test.txt
记住,提交时记录的是放在暂存区域的快照,任何还未暂存的仍然保持已修改状态,可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。
(5)查看已暂存和未暂存的更新(查看前后之间的变化)
实际上 git status 的显示比较简单,仅仅是列出了修改过的文件,如果要查看具体修改了什么地方,可以用 git diff 命令。用 git diff –cached 查看已经暂存起来的变化:
(6)移动文件(mv)
要在 Git 中对文件改名,可以这么做:
$ git mv file_from file_to
其实,运行 git mv 就相当于运行了下面三条命令:
$ mv README.txt README
$ git rm README.txt
$ git add README
如此分开操作,Git 也会意识到这是一次改名,所以不管何种方式都一样。当然,直接用 git mv 轻便得多,不过有时候用其他工具批处理改名的话,要记得在提交前删除老的文件名,再添加新的文件名。
在 git 中, 每一次提交(commit)的修改, 都会被单独的保存起来. 也可以说 git 的中的所有文件都是一次次修改累积起来的. 文件好比楼房, 每个 commit 记录 了盖楼需添加或者拿走的材料. 整个施工过程也被记录了下来.
比如这里历史上我进行了两次commit,那么在一个项目下进行git log:
>> D:\soft\git\learngit>git log
commit 1b6d23eaf9ad62ea9e468211c15682a6b4b242c6 (HEAD -> master)
Author: Jun <1924153192@qq.com>
Date: Sat Nov 25 19:43:02 2017 +0800
change in 1
commit 0e772909ce9abd01f4fa9b93642ac40db4a44cd1
Author: Jun <1924153192@qq.com>
Date: Thu Nov 23 17:41:19 2017 +0800
change1
另外git log还有许多参数可以带。详细参考链接
现在在上一次commit后,我修改了一下其中一个text.txt文件,添加了一行:
change in test
那么此时我还没有add,更没有commit,此时我执行git diff:
>> D:\soft\git\learngit>git diff
diff --git a/test.txt b/test.txt
index e69de29..9377178 100644
--- a/test.txt
+++ b/test.txt
@@ -0,0 +1 @@
+change in test
可以通过git status来观看文件是否add了,那么假设我add的文件,但是没有commit文件,同样我想看一下差别怎么办呢?
现在我们先add一下上面那个文件:
>> D:\soft\git\learngit>git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
>> D:\soft\git\learngit>git add test.txt
>> D:\soft\git\learngit>git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: test.txt
那么已经 add 了这次修改, 文件变成了 “可提交状态” (staged), 我们可以在 diff 中添加参数 –cached 来查看修改:
>> D:\soft\git\learngit>git diff
>> D:\soft\git\learngit>git diff --cached
diff --git a/test.txt b/test.txt
index e69de29..9377178 100644
--- a/test.txt
+++ b/test.txt
@@ -0,0 +1 @@
+change in test
有时候我们总会忘了什么, 比如已经提交了 commit 却发现在这个 commit 中忘了附上另一个文件.
现在假设我们刚才commit后,修改了1.txt,然后我们又去commit了一下,之后突然想起来1.txt被修改了,还没有add,那么肯定就没有被commit,这个时候我们想add后再commit,同时上一次的commit想忽略掉,可以进行下面这样:
$ git commit -m 'change in 1'
$ git add 1.txt
$ git commit --amend --no-edit # "--no-edit": 不编辑, 直接合并到上一个 commit
$ git log --oneline # "--oneline": 每个 commit 内容显示在一行
>> D:\soft\git\learngit>git log --oneline
9c2c0fb (HEAD -> master) change in test
bbc6d6b change in 2
1b6d23e change in 1
0e77290 change1
比如现在我们修改一下1.txt,然后add一下,之后在回去add之前:
>> D:\soft\git\learngit>git add 1.txt
>> D:\soft\git\learngit>git status -s
M 1.txt #:staged 注意此时的M为绿色,代表已经modeified了
>> D:\soft\git\learngit>git reset 1.txt
Unstaged changes after reset:
M 1.txt
>> D:\soft\git\learngit>git status -s
M 1.txt #:unstaged 注意此时的M为红色,代表还没有modeified
在穿梭到过去的 commit 之前, 我们必须了解 git 是如何一步一步累加更改的.
每个 commit 都有自己的 id 数字号, HEAD 是一个指针, 指引当前的状态是在哪个 commit. 最近的一次 commit 在最右边, 我们如果要回到过去, 就是让 HEAD 回到过去并 reset 此时的 HEAD 到过去的位置.
现在我们把所以的文件全部add后再commit,查看一下现在有几个版本的commit了:
>> D:\soft\git\learngit>git log --oneline
42735bb (HEAD -> master) change in 1.1
9c2c0fb change in test
bbc6d6b change in 2
1b6d23e change in 1
0e77290 change1
最近3次我们都在改1.txt里面的文件,看一下1.txt文件的内容:
change in 1
change
test reset
现在我们想回到上一个版本怎么办呢?两种方式:一种直接
"HEAD^"
另外一种使用它的id号,比如前面的 bbc6d6b 这些。
>> D:\soft\git\learngit>git reset --hard 9c2c0fb
HEAD is now at 9c2c0fb change in test
>> D:\soft\git\learngit>git log --oneline
9c2c0fb (HEAD -> master) change in test
bbc6d6b change in 2
1b6d23e change in 1
0e77290 change1
打开1.txt文件可以看到:
change in 1
change
发现里面的内容是不是也变化了,没错,回到历史版本,里面的内容是会一起变化的。
但是这样我们也发现上面的 change in 1.1也不见了,如果又想回去怎么办呢?使用 git reflog 可以查看所有的head
>> D:\soft\git\learngit>git reflog
9c2c0fb (HEAD -> master) HEAD@{0}: reset: moving to 9c2c0fb
42735bb HEAD@{1}: reset: moving to HEAD
42735bb HEAD@{2}: commit: change in 1.1
9c2c0fb (HEAD -> master) HEAD@{3}: commit (amend): change in test
9ee4d05 HEAD@{4}: commit: change in test
bbc6d6b HEAD@{5}: commit: change in 2
1b6d23e HEAD@{6}: commit: change in 1
0e77290 HEAD@{7}: commit (initial): change1
这样如果我们还想回到change in 1.1,那么直接索引他的编号即可:
>> D:\soft\git\learngit>git reset --hard 42735bb
HEAD is now at 42735bb change in 1.1
>> D:\soft\git\learngit>git log --oneline
42735bb (HEAD -> master) change in 1.1
9c2c0fb change in test
bbc6d6b change in 2
1b6d23e change in 1
0e77290 change1
我们又再次奇迹般的回到了change in 1.1。 有了编号,我们可以穿梭到任意一个版本的文件。此时去看1.txt,它的文件内容也改变了。
之前我们使用 reset 的时候是针对整个版本库, 回到版本库的某个过去. 不过如果我们只想回到某个文件的过去, 又该怎么办呢?
其实 checkout 最主要的用途并不是让单个文件回到过去, 我们之后会继续讲 checkout 在分支 branch 中的应用, 这一节主要讲 checkout 让文件回到过去.
我们现在的版本库中有三个文件:
1.txt
2.txt
test.txt
先看一下历史状态:
>> D:\soft\git\learngit>git log --oneline
42735bb (HEAD -> master) change in 1.1
9c2c0fb change in test
bbc6d6b change in 2
1b6d23e change in 1
0e77290 change1
我们仅仅要对 1.txt 进行回到过去操作, 回到 9c2c0fb: change in test 这一个 commit. 使用 checkout + id 9c2c0fb + – + 文件目录 1.txt, 我们就能将 1.txt 的指针 HEAD 放在这个时刻 9c2c0fb:
>> D:\soft\git\learngit>git checkout 9c2c0fb -- 1.txt
ok来看一下1.txt的内容:
change in 1
change
发现回到了从前。看一下整个commit,发现依然在1.1的版本,唯独1.txt回到了test的版本。
D:\soft\git\learngit>git log --oneline
42735bb (HEAD -> master) change in 1.1
9c2c0fb change in test
bbc6d6b change in 2
1b6d23e change in 1
0e77290 change1
我们在 1.txt 加上一行内容 # I went back to change test 然后 add 并 commit 1.txt:
>> D:\soft\git\learngit>git add 1.txt
>> D:\soft\git\learngit>git commit -m "1.txt went to test and add comment for 1.txt "
[master ad3b7dc] 1.txt went to test and add comment for 1.txt
1 file changed, 1 insertion(+), 1 deletion(-)
>> D:\soft\git\learngit>git log --oneline
ad3b7dc (HEAD -> master) 1.txt went to test and add comment for 1.txt
42735bb change in 1.1
9c2c0fb change in test
bbc6d6b change in 2
1b6d23e change in 1
0e77290 change1
可以看出, 不像 reset 时那样, 我们的 change 1.1 并没有消失, 但是 1.txt 却已经回去了过去, 并改写了未来.
下载 Git 的源代码,进入 contrib/completion 目录,会看到一个 git-completion.bash 文件。将此文件复制到你自己的用户主目录中。,并把下面一行内容添加到你的 .bashrc 文件中:
source ~/.git-completion.bash
Linux 上则复制到 /etc/bash_completion.d/ 目录中。这两处目录中的脚本,都会在 Bash 启动时自动加载。
如果在 Windows 上安装了 msysGit,默认使用的 Git Bash 就已经配好了这个自动补全脚本,可以直接使用。
在输入 Git 命令的时候可以敲两次跳格键(Tab),就会看到列出所有匹配的可用命令建议:
$ git co
commit config
Git 并不会推断你输入的几个字符将会是哪条命令,不过如果想偷懒,少敲几个命令的字符,可以用 git config 为命令设置别名。来看看下面的例子:
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
现在,如果要输入git commit 只需键入 git ci 即可。而随着 Git 使用的深入,会有很多经常要用到的命令,遇到这种情况,不妨建个别名提高效率。
用这种技术还可以创造出新的命令,比方说取消暂存文件时的输入比较繁琐,可以自己设置一下:
$ git config --global alias.unstage 'reset HEAD --'
这样一来,下面的两条命令完全等同:
$ git unstage fileA
$ git reset HEAD fileA
很多时候我们需要给自己或者客户用一个稳定的版本库, 然后同时还在开发另外一个升级版. 自然而然, 我们会想到把这两者分开处理, 用户使用稳定版, 我们开发我们的开发版. 不过 git 的做法却不一样, 它把这两者融合成了一个文件, 使用不同的分支来管理.
分支就是在当前的项目上独立一个head指针出来,有多少个分支就有多少个独立的head,不同的分支之间可以进行不同独立的操作。相当于可以多并行化的对用一个文件进行不同的操作而不会干扰,最后再进行合并即可。
在 Git 中提交时,会保存一个提交(commit)对象,该对象包含一个指向暂存内容快照的指针,包含本次提交的作者等相关附属信息,包含零个或多个指向该提交对象的父对象指针:首次提交是没有直接祖先的,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。
通常情况下,我们不建立分支的时候,就默认在初始化的主分支master上进行的,通常我们会把 master 当作最终的版本。开发新版本或者新属性的时候, 在另外一个分支上进行, 这样就能使开发和使用互不干扰了。
现在有一个项目,下面有两个文件:1.txt和2.txt,其中我commit了两次,没有分支,看一下状态就是:
>> D:\soft\git\test>git log --oneline --graph
* 3c9fcdc (HEAD -> master) change two
* 164e1f8 change one
下面我建立一个分支,使用的命令式 git branch
>> D:\soft\git\test>git branch testing
D:\soft\git\test>git log --oneline --graph
* 3c9fcdc (HEAD -> master, testing) change two
* 164e1f8 change one
假如我想把HEAD切换到testing怎么办呢?此时需要使用 git checkout 命令了。
>> D:\soft\git\test>git checkout testing
Switched to branch 'testing'
>> D:\soft\git\test>git log --oneline --graph
* 3c9fcdc (HEAD -> testing, master) change two
* 164e1f8 change one
现在来说说分支有什么用,说之前我们先看看我的两个文件中的内容:
1.txt:
change 1-1
change 1-2
2.txt:
change 2-1
change 2-2
好,现在我们相在testing分支上修改内容,同时master住分支上先不变,相当于master是一个1.0版本在运行着,而我们正在研发2.0版本,待研发好了再合并到master上去。
简单修改下1.txt的内容如下:
change 1-1
change 1-2
change in testing branch
然后看一下状态:
>> D:\soft\git\test>git status
On branch testing
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: 1.txt
no changes added to commit (use "git add" and/or "git commit -a")
>> D:\soft\git\test>git add .
>> D:\soft\git\test>git commit -m "change in tesing"
[testing c01b5b8] change in tesing
1 file changed, 2 insertions(+), 1 deletion(-)
>> D:\soft\git\test>git log --oneline --graph
* c01b5b8 (HEAD -> testing) change in tesing
* 3c9fcdc (master) change two
* 164e1f8 change one
下面关键来了 ,我们说我们在testing分支下操作的文件,理论上如果我们切换到主分支master下的时候,里面的1.txt的文件没有变化,下面来看一下,先切换分支:
>> D:\soft\git\test>git checkout master
Switched to branch 'master'
之后我们打开了 1.txt 发现如下:
change 1-1
change 1-2
神奇吧,加的 change in testing branch 内容并没有显示出来。
好了假设我们在testing分支上开发完了,想合并到branch上去,可以如下:
>> D:\soft\git\test>git checkout master
Already on 'master'
>> D:\soft\git\test>git merge testing
Updating 3c9fcdc..c01b5b8
Fast-forward
1.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
合并完了我们可以看到说1.txt添加了内容。这个时候我们再去打开看一下 1.txt 发现:
change 1-1
change 1-2
change in testing branch
内容更改了吧。
为了把分支合并过程显示出来,可以如下添加 –no-ff 参数:
>> D:\soft\git\test>git merge --no-ff -m "merge testing to master" testing
Merge made by the 'recursive' strategy.
1.txt | 1 +
1 file changed, 1 insertion(+)
>> D:\soft\git\test>git log --oneline --graph
* 51ebeb2 (HEAD -> master) merge testing to master
|\
| * 5ff866d (testing) change three
|/
* c01b5b8 change in tesing
* 3c9fcdc change two
* 164e1f8 change one
当该testing分支没有使用价值的时候,可以删除它,使用 git branch -d 参数,如下:
>> D:\soft\git\test>git branch -d testing
Deleted branch testing (was 5ff866d).
再来看一下状态:
>> D:\soft\git\test>git log --oneline --graph
* 51ebeb2 (HEAD -> master) merge testing to master
|\
| * 5ff866d change three
|/
* c01b5b8 change in tesing
* 3c9fcdc change two
* 164e1f8 change one
有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起。比如上面这个例子,我在新建了testing分支后,在testing分支中我去修改了1.txt,但是有个人在这个master分支中也跑去修改了1.txt,那么再去合并就会出现问题,下面模拟一下,实现我们建个分支testing,然后在testing修改1.txt,在master分支中也修改1.txt,之后合并看看:
先切换到testing分支,然后人为修改下1.txt
>> D:\soft\git\test>git checkout testing
Switched to branch 'testing'
>> D:\soft\git\test>git add .
>> D:\soft\git\test>git commit -m "change in testing 1.1"
再切换到master分支,然后人为修改下1.txt
>> D:\soft\git\test>git checkout master
Switched to branch 'master'
>> D:\soft\git\test>git add .
>> D:\soft\git\test>git commit -m "change in master"
[master 8cc6917] change in master
1 file changed, 2 insertions(+), 1 deletion(-)
>> D:\soft\git\test>git merge --no-ff -m "merge testing to master" testing
Auto-merging 1.txt
CONFLICT (content): Merge conflict in 1.txt
Automatic merge failed; fix conflicts and then commit the result.
Git 作了合并,但没有提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,可以用 git status 查阅:
>> D:\soft\git\test>git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add ..." to mark resolution)
both modified: 1.txt
no changes added to commit (use "git add" and/or "git commit -a")
任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出 。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。这个时候我们去打开1.txt,可以看到此文件包含类似下面这样的部分:
change 1-1
change 1-2
change in testing branch
change in testing branch
<<<<<<< HEAD
change in master branch
=======
change in testing branch
>>>>>>> testing
这个时候你需要手动去调整了。比如如果这里我把两者都保留,也就是testing分支里面的修改和master里面的修改都保留,这个时候我们只需要下面这三行全部手动删除即可。
<<<<<<< HEAD
=======
>>>>>>> testing
在解决了所有文件里的所有冲突后,运行 git add 将把它们标记为已解决状态。
>> D:\soft\git\test>git add .
>> D:\soft\git\test>git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: 1.txt
可以看到冲突基本解决。我们再来看下状态:
>> D:\soft\git\test>git commit -m "change all"
[master 49f7c12] change all
>> D:\soft\git\test>git log --oneline --graph
* 49f7c12 (HEAD -> master) change all
|\
| * 965c38c (testing) change in tesing 1
* | 8cc6917 change in master
|/
* 51ebeb2 merge testing to master
|\
| * 5ff866d change three
|/
* c01b5b8 change in tesing
* 3c9fcdc change two
* 164e1f8 change one
对于分支,还有一些其他方面的管理方法,下面简单列举一下:
直接使用git branch 命令,不加任何参数的话,它会给出当前所有分支的清单;
$ git branch
iss53
* master
testing
其中带*号的为当前所在分支;
加上 -v 参数,可以查看各个分支最后一个提交对象的详细信息。
本节内容的官方教程讲的很好,很通用,值得了解。这里摘取一下。
现在我们已经学会了新建分支和合并分支,可以(或应该)用它来做点什么呢?在本节,我们会介绍一些利用分支进行开发的工作流程。而正是由于分支管理的便捷,才衍生出了这类典型的工作模式,你可以根据项目的实际情况选择一种用用看。
由于 Git 使用简单的三方合并,所以就算在较长一段时间内,反复多次把某个分支合并到另一分支,也不是什么难事。也就是说,你可以同时拥有多个开放的分支,每个分支用于完成特定的任务,随着开发的推进,你可以随时把某个特性分支的成果并到其他分支中。
许多使用 Git 的开发者都喜欢用这种方式来开展工作,比如仅在 master 分支中保留完全稳定的代码,即已经发布或即将发布的代码。与此同时,他们还有一个名为 develop 或 next 的平行分支,专门用于后续的开发,或仅用于稳定性测试 — 当然并不是说一定要绝对稳定,不过一旦进入某种稳定状态,便可以把它合并到 master 里。这样,在确保这些已完成的特性分支(短期分支,比如之前的 iss53 分支)能够通过所有测试,并且不会引入更多错误之后,就可以并到主干分支中,等待下一次的发布。
本质上我们刚才谈论的,是随着提交对象不断右移的指针。稳定分支的指针总是在提交历史中落后一大截,而前沿分支总是比较靠前(见图)。
或者把它们想象成工作流水线,或许更好理解一些,经过测试的提交对象集合被遴选到更稳定的流水线:
你可以用这招维护不同层次的稳定性。某些大项目还会有个 proposed(建议)或 pu(proposed updates,建议更新)分支,它包含着那些可能还没有成熟到进入 next 或 master 的内容。这么做的目的是拥有不同层次的稳定性:当这些分支进入到更稳定的水平时,再把它们合并到更高层分支中去。再次说明下,使用多个长期分支的做法并非必需,不过一般来说,对于特大型项目或特复杂的项目,这么做确实更容易管理。
在任何规模的项目中都可以使用特性(Topic)分支。一个特性分支是指一个短期的,用来实现单一特性或与其相关工作的分支。可能你在以前的版本控制系统里从未做过类似这样的事情,因为通常创建与合并分支消耗太大。然而在 Git 中,一天之内建立、使用、合并再删除多个分支是常见的事。
我们在上节的例子里已经见过这种用法了。我们创建了 iss53 和 hotfix 这两个特性分支,在提交了若干更新后,把它们合并到主干分支,然后删除。该技术允许你迅速且完全的进行语境切换 — 因为你的工作分散在不同的流水线里,每个分支里的改变都和它的目标特性相关,浏览代码之类的事情因而变得更简单了。你可以把作出的改变保持在特性分支中几分钟,几天甚至几个月,等它们成熟以后再合并,而不用在乎它们建立的顺序或者进度。
现在我们来看一个实际的例子。请看图下,由下往上,起先我们在 master 工作到 C1,然后开始一个新分支 iss91 尝试修复 91 号缺陷,提交到 C6 的时候,又冒出一个解决该问题的新办法,于是从之前 C4 的地方又分出一个分支 iss91v2,干到 C8 的时候,又回到主干 master 中提交了 C9 和 C10,再回到 iss91v2 继续工作,提交 C11,接着,又冒出个不太确定的想法,从 master 的最新提交 C10 处开了个新的分支 dumbidea 做些试验。
现在,假定两件事情:我们最终决定使用第二个解决方案,即 iss91v2 中的办法;另外,我们把 dumbidea 分支拿给同事们看了以后,发现它竟然是个天才之作。所以接下来,我们准备抛弃原来的 iss91 分支(实际上会丢弃 C5 和 C6),直接在主干中并入另外两个分支。最终的提交历史将变成图下这样:
请务必牢记这些分支全部都是本地分支,这一点很重要。当你在使用分支及合并的时候,一切都是在你自己的 Git 仓库中进行的 — 完全不涉及与服务器的交互。
github是一个在线项目托管平台,里面有众多别人开源的项目,非常好。
有时候我们想把我们的项目不光存在本地,还想存在网上,或者共享出来和大家一起使用或者修改之类的,就可以使用github了。国内也有一个在线托管平台 码云,也很强大,提供很多服务。
下面简单介绍如何把自己的项目上传到github上去。
传上去首先得拥有一个账户,这个就不说了,注册一个即可。接着我们需要在github主页新建一个库:
比如这里新建一个后就是这样,这里根据提醒,在自己的本地客服端输入下面的两行,前提是客户端上当前目录要索引到你的项目目录下。
这里还是以前几节的那个例子为例,首先我们来看看这个例子:
运行这两句以后,你的项目就推到了github上了,在github上刷新一下看看:
可以看到有了。因为我们推送的是整个项目,而且可以看到推送的是主分支master(注意项目中的次分支,比如这里有testing分支是没有没推上去的),而且项目里面的隐藏文件夹 .git 也会被推上去。需要注意的是,电脑如果是第一次推的话,可能会弹出让你输入github的账号密码信息,输入进去就好了(以windows系统为例的)。
假设我们想把testing分支也推上去怎么办呢,直接再推送一遍。
>> D:\soft\git\test>git push -u origin testing
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/jungrea/helloworld.git
* [new branch] testing -> testing
Branch 'testing' set up to track remote branch 'testing' from 'origin'.
这个时候我们去看下面的内容,可以看到,分支不同,内容是不一样的,完全和本地的是一模一样。
同时我们可以在网页上观看我们commit的过的历史信息,像下面这样: