Git学习笔记
目录
1.什么是Git?
生活工作或学习中我们有时需要保存一个文件不同时间的多个版本,以便在未来时间我们可以再次查阅。一般情况我们都会复制一份文件,在文件名中加入能够表示时间的文字,以此保证日后查阅时能按照时间找到需要的文件,这样修改的次数多了就会非常麻烦。还有一种情况,我们有时需要多个人编辑同一个文件,我们可以使用文件共享或搭建FTP服务器共享。这种方式如果需要确定文件是谁什么时间编辑的,那就会非常的麻烦。而且如果某人失误损坏了文件,那将是灾难性的。
而Git这个版本控制软件就是为解决这种问题而被设计的。它是一个分布式的版本控制系统,是一个软件。最初由林纳斯·托瓦兹创作,最初的目的是为更好地管理Linux内核开发而设计。Git易于使用,效率高,它支持非线性分支管理文件。
Git的实现是直接记录快照,而不是记录文件的差异对比。其像是把数据看作一组文件快照,每次修改完提交后,就会记一次版本,如果文件没有修改,就会通过链接指向文件,它对待数据就像是一个快照流,我们所有的操作几乎都可以在本地完成。Git有校验文件完整的机制,所有数据在存储之前都会计算校验和,其使用Hash算法通过文件目录和内容来计算校验和。
Git的仓库(.git文件夹),英文名:Repository,仓库用来保存项目的原始数据和对象数据。Git的工作目录,英文名:Working Directory,工作目录是对项目的某个版本提取出来的内容,这些内容供我们使用或修改。当我们修改了某些文件后,对应被修改的文件会在暂存区生成一个列表,用于标记下次提交时要提交的文件。一般的使用流程就是文件在工作目录中修改,被标记在暂存区,执行提交后会提交到仓库中记录一次版本。
区别于我们在本地创建的本地仓库,当然就有存于远程服务器上的远程仓库。其是为多人共享而建立,亦可以看作安全考虑而设置的本地文件的备份。
2.Git的基本操作
本节内容包含Git的安装和基本命令。其基本命令包含初始化仓库、查看仓库状态、暂存操作、本地提交操作、提交本地仓库到远端仓库、从远端仓库拉取仓库数据到本地。
安装Git
CentOS上安装:
yum -y install git
Ubuntu安装:
apt-get install git-all
Windows系统可以去Github项目:Git for Windows/git 的Release下下载对应版本的文件并安装。
安装完Git后我们需要对其进行一些简单的配置,比如配置用户名和邮箱。
git config --global user.name "You Name"
git config --global user.email "youmail@XXX.XX"
Git基本命令
Git命令行的操作类似于Linux shell,首先我们先创建一个Git仓库,比如,我将其创建在E:/git_repos/Git-Tutorial。
使用以下命令创建目录:
cd E:
mkdir git_repos
cd git_repos
mkdir Git-Tutorial
cd Git-Tutorial
以上步骤操作完成后就创建了一个普通的目录,此时这是一个普通的文件夹。使用以下命令初始化Git仓库:
git init
此时系统回显以下信息:
User@PC MINGW64 /e/git_repos/Git-Tutorial
$ git init
Initialized empty Git repository in E:/git_repos/Git-Tutorial/.git/
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
这时可以看到,终端里面看到的是显示一个Master分支;默认状态下Git会自动为我们初始化一个名为Master的分支。
使用以下命令查看目录下的文件:
ll
出现如下提示,表示目录下没有内容,是空的。
total 0
我们可以使用touch命令创建一个文件README.md,然后使用git status命令查看当前Git仓库状态。
touch README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
从Git仓库状态信息里可以看出,当前所在分支为Master分支,没有任何的commits(提交)。另外,其下方显示一个未跟踪的文件列表,其中列出所有的存在目录中但是没有被跟踪的文件,并且Git会提示如何将其包含到暂存中(待提交的列表中)。
我们使用git add README.md命令将文件暂存,再通过git status命令查看状态:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git add README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
这时我们可以看到提示,在Master分支中,暂存中有新增一个文件,README.md,并且提示了,使用git rm --cached ...可以将文件返回上一状态。我们可以试一下:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git rm --cached README.md
rm 'README.md'
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
我们还可以使用git add -A命令实现将目录下所有文件加入到暂存区中:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git add -A
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
目前我们做到了将README.md 文件存于暂存区。当我们需要记录一个版本时,我们便需要做一次提交。使用git commit进行提交,建议其后跟随-m参数,用于备注这次提交的说明。当我们提交后,再次使用git status查看状态:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git commit -m "add README.md"
[master (root-commit) dfaaa0b] add README.md
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git status
On branch master
nothing to commit, working tree clean
提交到GitHub
首先需要注册一个Github账号,然后新建一个仓库,填入自己需要的名字,然后创建。
创建后使用以下命令添加,并将代码push到Github。这期间可能需要输入用户名和密码,将Github账号密码输入即可:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git remote add origin https://github.com/<用户名>.../<仓库名>.git
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git push -u origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 218 bytes | 109.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/<用户名>.../<仓库名>.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
既然有将代码提交到Github,那就需要克隆远程仓库到本地,使用如下命令将仓库git-test克隆到本地,并命名为git-demo:
git clone <远端仓库> <本地名称>
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ cd ..
User@PC MINGW64 /e/git_repos
$ git clone https://github.com/<用户名>.../git-test.git git-demo
Cloning into 'git-demo'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
仓库克隆到本地后,我们便可以对里面的文件进行修改,修改后的文件自然也可以提交到Github。首先我们使用cd git-demo进入这个仓库,使用文本编辑器(比如vi)修改README.md文件内容为自定义内容,比如:
# Hello Word
This is a file for Test/Study.
然后我们使用 git status 查看 git-demo 这个本地仓库的状态,会提示 README.md 文件的状态是 modified ,意思是被修改了。按照提示,我们尝试运行 git add README.md 命令,或使用 git add -A 命令全部添加到暂存区。最后我们运行 git status 确认一下状态(可选),使用 git-commit 提交修改(建议使用-m "说明文本"添加提交说明)。过程如下:
User@PC MINGW64 /e/git_repos/git-demo (master)
$ vi README.md
User@PC MINGW64 /e/git_repos/git-demo (master)
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes 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: README.md
no changes added to commit (use "git add" and/or "git commit -a")
User@PC MINGW64 /e/git_repos/git-demo (master)
$ git add -A
warning: LF will be replaced by CRLF in README.md.
The file will have its original line endings in your working directory
User@PC MINGW64 /e/git_repos/git-demo (master)
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
User@PC MINGW64 /e/git_repos/git-demo (master)
$ git commit -m "Modified README.md"
[master 935539d] Modified README.md
1 file changed, 2 insertions(+)
我们在本地完成了git-demo的提交,接下来便可以使用 git push 命令将仓库同步到Github的远端仓库git-test。然后我们也可以在git-tutorial中使用git-pull拉取新的代码,git pull 后面可以跟随仓库链接和分支名称。需要注意的是,在拉取之前git-tutorial中的数据是旧的,我们可以使用cat命令验证。操作如下:
User@PC MINGW64 /e/git_repos/git-demo (master)
$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 293 bytes | 97.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/<用户名>/git-test.git
dfaaa0b..935539d master -> master
User@PC MINGW64 /e/git_repos/git-demo (master)
$ cd ..
User@PC MINGW64 /e/git_repos
$ ll
total 0
drwxr-xr-x 1 Lexsion 197609 0 1月 13 23:28 git-demo/
drwxr-xr-x 1 Lexsion 197609 0 1月 13 00:15 Git-Tutorial/
User@PC MINGW64 /e/git_repos
$ cd Git-Tutorial
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ ll
total 0
-rw-r--r-- 1 Lexsion 197609 0 1月 13 00:15 README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ cat README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/<用户名>/git-test
dfaaa0b..935539d master -> origin/master
Updating dfaaa0b..935539d
Fast-forward
README.md | 2 ++
1 file changed, 2 insertions(+)
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ cat README.md
# Hello Word
This is a file for Test/Study.
3.Git的分支
软件开发过程中,我们会有新增功能也有修复BUG,这两者可能是同时存在又独立的。在Git中,为我们提供了一个叫做分支(Branch)的东西来处理这种情况,以此保证BUG的修复与新功能的增加不会互相干扰,不同的人处理不同的分支,不必担心其他人破坏了环境。分支是为了将修改记录的整个流程做一个分岔的保存,分支亦可以在未来合并。
我们在创建仓库时,Git会默认创建一个master分支。在此之后的提交,在切换分支之前,都会提交到这个master分支中。Git分支中有一个概念叫做HEAD,它指向当前在使用的分支中的最后一次更新,通常其指向master分支的最后一次提交。我们可以通过移动这个指针来变更当前分支。
我们可以使用 git branch <新分支名字> 来创建新的分支,通过git branch命令查看当前仓库有哪些分支,使用git checkout <分支名> 来选择当前操作的分支,比如我们新建feature1分支并切换到这个分支。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git branch feature1
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git branch
feature1
* master
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git checkout feature1
Switched to branch 'feature1'
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
切换到feature1后,可以看到后面括号中的分支名称已经变成了feature1。接下来就可以编辑这个分支,而不必担心影响其他分支。比如我想在里面新建一个a.txt,编辑一下,加入到仓库并提交。如下:
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
$ touch a.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
$ vi a.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
$ git add a.txt
warning: LF will be replaced by CRLF in a.txt.
The file will have its original line endings in your working directory
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
$ git commit -m "Add a.txt"
[feature1 fcf80e7] Add a.txt
1 file changed, 3 insertions(+)
create mode 100644 a.txt
这时,我们的feature1就成功提交到本地仓库了。如果我们需要再基于feature1建立一个分支feature2,那么就输入git branch <分支名称>命令创建新分支,然后使用git check out <分支名> 切换到这个分支。
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
$ git branch feature2
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
$ git checkout feature2
Switched to branch 'feature2'
我们亦可以使用一条命令完成这个过程,比如我们创建feature3并切换到该分支:
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature2)
$ git checkout -b feature3
Switched to a new branch 'feature3'
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature3)
若要删除一个分支,需要执行git branch -d <分支名> 。如下删除feature2分支:
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature3)
$ git branch -d feature2
Deleted branch feature2 (was fcf80e7).
我们在feature3下检查里面的文件,可以看到继承自feature2的文件都还在。尝试建立一个b.txt文件,编辑一下,加入仓库并提交。然后我们切换到master分支,通过命令 git branch -d <分支名> 删除分支feature3,会发现无法删除。系统会提示我们这个分支其中的更改没有合并到其他分支,如果需要强制删除可以使用 git branch -D <分支名> 强行删除,或考虑合并到其他分支。 如下:
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature3)
$ ll
total 2
-rw-r--r-- 1 Lexsion 197609 33 1月 14 01:22 a.txt
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature3)
$ touch b.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature3)
$ vi b.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature3)
$ git add b.txt
warning: LF will be replaced by CRLF in b.txt.
The file will have its original line endings in your working directory
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature3)
$ git commit -m "add b.txt"
[feature3 a03ddaa] add b.txt
1 file changed, 1 insertion(+)
create mode 100644 b.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature3)
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git branch -d feature3
error: The branch 'feature3' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature3'.
如果我们需要操作合并分支,比如将feature3分支中的代码合并到master分支,我们可以在master分支下运行git merge <分支名>将某个分支合并到当前分支,因为我们本文案例没有代码冲突,系统自动使用Fast-forward方式自动完成了合并,不需要处理冲突。如下:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git merge feature3
Updating 935539d..a03ddaa
Fast-forward
a.txt | 3 +++
b.txt | 1 +
2 files changed, 4 insertions(+)
create mode 100644 a.txt
create mode 100644 b.txt
然后我们运行git push将master的修改提交到远程仓库Github。这时我们在本地使用git branch 可以看到有三个分支,但是在远端仓库中只有一个分支,master分支。此时如果我们需要将其他分支提交到远程仓库,首先可以使用git checkout <分支名>切换到要操作的分支(不切换也可以),使用git push origin <本地分支名称>:<远端分支名称> 将分支提交到远端,冒号与后面的远端分支名可不填,远端的分支会使用一样的名字。如果需要将远端分支删除,使用git push origin :<远端分支名称>删除。如下:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git branch
feature1
feature3
* master
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git checkout feature1
Switched to branch 'feature1'
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
$ git push origin feature1:TestF1
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: Create a pull request for 'TestF1' on GitHub by visiting:
remote: https://github.com/<用户名>/git-test/pull/new/TestF1
remote:
To https://github.com/<用户名>/git-test.git
* [new branch] feature1 -> TestF1
User@PC MINGW64 /e/git_repos/Git-Tutorial (feature1)
$ git push origin :TestF1
To https://github.com/<用户名>/git-test.git
- [deleted] TestF1
4.Git的合并
在本节开始时,老师表示他把之前的几个分支都删除了,只保留了master这个分支,所以开始之前,我们也去删一下。如果不会的话,那应该复习一下了。
查看日志和创建别名
在学习Git的合并之前,先了解一下如何查看日志,查看日志的命令是git log,日志中靠近上方的是最近的提交,往下则是较早的一些提交。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git log
commit a03ddaac380207be15724e1cb21546ff73ea4a93 (HEAD -> master, origin/master)
Author: 用户名<邮箱>
Date: Tue Jan 15 00:52:06 2019 +0800
add b.txt
commit fcf80e78461f6d2b66597097ae459254c1d46b9e
Author: 用户名<邮箱>
Date: Mon Jan 14 01:23:25 2019 +0800
Add a.txt
commit 935539d80a47a0b5dc86afa9791e9919f6830632
Author: 用户名<邮箱>
Date: Sun Jan 13 23:30:52 2019 +0800
Modified README.md
commit dfaaa0b793060603217561b3d7b0e3444c7b198a
Author: 用户名<邮箱>
Date: Sun Jan 13 01:04:07 2019 +0800
add README.md
这种看日志的方式并不方便,我们可以使用git log --oneline 使其以单行的方式显示,这样可能会更加直观一些。每行前面的ID是这次提交的Hash码的一部分,此码可唯一的标示出这次提交。如果提交记录较多,可是后缀 -<数量>来表示自己只取最近的几次提交日志。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git log --oneline
a03ddaa (HEAD -> master, origin/master) add b.txt
fcf80e7 Add a.txt
935539d Modified README.md
dfaaa0b add README.md
如果想看某次提交具体信息,可以使用git show 这个命令来查看。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git show a03ddaa
commit a03ddaac380207be15724e1cb21546ff73ea4a93 (HEAD -> master, origin/master)
Author: 用户名<邮箱>
Date: Tue Jan 15 00:52:06 2019 +0800
add b.txt
diff --git a/b.txt b/b.txt
new file mode 100644
index 0000000..63ace0e
--- /dev/null
+++ b/b.txt
@@ -0,0 +1 @@
+Hello word
在Stack overflow上有人提炼了一些好看的指令。比如我们可以使用 git log --all --decorate --oneline --graph 命令来输出一个好看的格式。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git log --all --decorate --oneline --graph
* a03ddaa (HEAD -> master, origin/master) add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
通过上面的命令我们发现,某些时候需要输入的命令会很长,难以记住。这时我们就要提到git的别名功能。使用这个功能我们可以给长命令定义别名,使用文本编辑器编辑 ~/.gitconfig文件,在里面添加以下内容并保存。
[alias]
dog = log --all --decorate --oneline --graph
这时,我们可以使用git dog 来调用这串长命令。我们在输出的信息中可以看到,最近的一次提交(commit)中,HEAD这个指针指向了master,而括号中逗号右侧的是我们的远程分支。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git dog
* a03ddaa (HEAD -> master, origin/master) add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
通常的合并操作
了解了日志,我们就可以开始学习Git的合并操作。
首先我们基于master分支创建一个分支,命名f1:
git checkout -b f1
使用 ll 命令查看一下分支下有哪些文件:
User@PC MINGW64 /e/git_repos/Git-Tutorial (f1)
$ ll
total 3
-rw-r--r-- 1 Lexsion 197609 36 1月 15 01:59 a.txt
-rw-r--r-- 1 Lexsion 197609 12 1月 15 21:38 b.txt
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
创建一个 fa.txt 并将其加入暂存区,然后提交一下:
touch fa.txt
git add fa.txt
git commit -m "add fa.txt"
这时我们查看一下日志,发现HRAD指针指向了f1,在master分支下最后的一次提交是 add b.txt :
User@PC MINGW64 /e/git_repos/Git-Tutorial (f1)
$ git dog
* 86b7648 (HEAD -> f1) add fa.txt
* a03ddaa (origin/master, master) add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
我们现在模拟的是在f1这个分支上相对于master分支添加了新的内容(fa.txt),然后我们需要将其重新合并到master分支。一般的做法是先切换到master分支,master分支下现在是没有fa.txt这个文件的,然后使用git merge f1 命令将F1合并进来。合并后显示的日志中可以看到使用的合并策略是Fast-forward,这种合并方式不会创建新的提交(commit)默认情况下Git会使用这种方式合并,前提是没有冲突。
User@PC MINGW64 /e/git_repos/Git-Tutorial (f1)
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ ll
total 3
-rw-r--r-- 1 Lexsion 197609 36 1月 15 01:59 a.txt
-rw-r--r-- 1 Lexsion 197609 12 1月 15 21:38 b.txt
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git merge f1
Updating a03ddaa..86b7648
Fast-forward
fa.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 fa.txt
查看日志,我们看到master的最后一次提交是 add b.txt ,在他上面一行变成了HEAD指向master的的信息依然是add fa.txt的信息。没有创建新的提交,信息左侧的*标记也是竖直向下的,没有分叉出现。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git dog
* 86b7648 (HEAD -> master, f1) add fa.txt
* a03ddaa (origin/master) add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
除了Fast-forward这个模式,我们还有其他的模式,可以使用以下命令查看merge的帮助文档了解。
git merge --help
从帮助文档中可以发现,默认的合并策略就是文档中的 --ff,只移动HEAD指针,不产生新的commit。除了这种策略,还有一个 --no-ff的策略。使用no-ff策略时,即使是可以使用Fast-forward方式,Git依然会创建一个合并节点(即会创建一次commit)。帮助文档中提到,如果合并标签,该策略是默认方式。Git在合并时,使用--no-ff策略是一个很好的实践,这种在Fast-forward方式下产生新commit的功能,让我们可以保持原有开发分支整个提交链的完整性。
我们知道 add fa.txt 这项操作实际是在f1分支完成的,但是merge操作后,我们在master分支下,通过log并不能完全看出这次提交是在哪个分支完成的。
接下来我们切换到f1分支,创建一个fb.txt文件:
git checkout f1
touch fb.txt
git add fb.txt
git commit -m "add fb.txt"
然后我们切换回master分支,再次将f1合并到master,这次我们使用 --no-ff 策略,过程中自动弹出一个编辑器,让我们编辑信息,我们可以在里面填写一些需要的信息,这里我保持默认。我们可以在回显的信息中看到合并的策略已经有了变化。
User@PC MINGW64 /e/git_repos/Git-Tutorial (f1)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git merge f1 --no-ff
Merge made by the 'recursive' strategy.
fb.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 fb.txt
我们查看一下日志,日志中可以看出,产生了一条新的提交,信息为Merge branch 'f1',并且左边的线条发生了变化。我们之前master最后一次提交是add b.txt,然后在f1这个分支上,做了一个add fa.txt的提交,然后在master分支下做了一次merge操作。之后我们去f1分支添加了fb.txt做了一次提交,最后又到master做了merge。这种方式我们便在日志中看到了其他分支的操作。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git dog
* bf497d9 (HEAD -> master) Merge branch 'f1'
|\
| * af03df4 (f1) add fb.txt
|/
* 86b7648 add fa.txt
* a03ddaa (origin/master) add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
接下来我们使用以下命令将master和f1两个分支都提交到Github。
git push
git push origin f1:f1
在Github上修改master分支下面的a.txt(添加一行内容update),以此模拟多人协作中其他人修改了文件。现在远端就会有一个新的版本,本地就会是一个相对旧的版本。于是我们本地执行pull操作拉取代码。
git pull
此时,本地的master分支已经拉取到最新版本,此时若f1分支需要同步master分支的变化,就需要切换到f1分支,执行merge master命令。
git checkout f1
git merge master
查看日志,可见以下内容:
User@PC MINGW64 /e/git_repos/Git-Tutorial (f1)
$ git dog
* 40f3061 (HEAD -> f1, origin/master, master) Update a.txt
* bf497d9 Merge branch 'f1'
|\
| * af03df4 (origin/f1) add fb.txt
|/
* 86b7648 add fa.txt
* a03ddaa add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
接下来尝试在master做出修改,添加一个m1.txt:
git checkout master
touch m1.txt
git add m1.txt
git commit -m "add m1.txt"
现在master分支有了一些变化,我们切换到f1分支。如果我们需要将master的分支同步到f1分支,我们可以像之前一样,在f1分支下merge master。这样的话。日志中就会产生一条merge的记录。而git还提供了一个命令叫rebase,此命令会把当前分支移动到指定分支的最后一次的提交。运行以下命令,将master分支的提交并入f1分支:
git checkout f1
git rebase master
然后我们查看日志。可以发现f1产生了新的提交信息,“add m1.txt”。此命令重写了项目的历史,并且不会带来merge commit信息。使用rebase可以让项目历史相对变得简洁,不会出现分岔。这个命令不会产生合并提交,如果这个提交不是必要的,我们便可以选择这种方式。但是这种简洁却会给我们项目的安全和可跟踪性带来不良的后果。如果违法了rebase的黄金法则:绝不在公共分支(一般为master)使用rebase,则会因重写项目历史给工作流带来危险的影响。
User@PC MINGW64 /e/git_repos/Git-Tutorial (f1)
$ git dog
* 6f3cfec (HEAD -> f1, master) add m1.txt
* 40f3061 (origin/master) Update a.txt
* bf497d9 Merge branch 'f1'
|\
| * af03df4 (origin/f1) add fb.txt
|/
* 86b7648 add fa.txt
* a03ddaa add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
关于我们对两种操作使用的选择。
merge是一个合并操作,其能将两个分支的修改合并到一起,默认情况不提交合并中修改的内容。merge提交历史会记录发生了什么,其关注点在真实的提交历史记录上。
rebase没有合并操作,其是将一个分支的最后一次修改,复制到了一个分支的最后一次提交上。rebase的提交历史能够反映这个项目的过程发生了什么,其关注点在开发的过程上。
两个命令都是分支整合工具,无优劣之分,只是功能有所不同,具体的选择需要根据开发需求决定。
分支冲突
首先我们从f1创建分支f2,并在分支f2下修改a.txt文件。将里面原来的update改为“Upgeade”,然后运行git commit提交。此时f1和f2两个分支可以理解为是在做不同方向的修改,两个分支中的a.txt也是不一样的。然后回到f1,修改其中的a.txt(改为Downgrade),这时,f1也有了新的修改。
git checkout -b f2
vim a.txt
git add a.txt
git commit -m "modified a.txt UG"
git checkout f1
vim a.txt
git add a.txt
git commit -m "modified a.txt DG"
然后切换到f2分支,假定f2分支的人需要拉取f1的修改,然后自己再继续修改。f2分支的人运行git merge就会发现有冲突提示。内容表示:自动合并失败,a.txt发生了冲突。
User@PC MINGW64 /e/git_repos/Git-Tutorial (f1)
$ git checkout f2
Switched to branch 'f2'
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2)
$ git merge f1
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
Automatic merge failed; fix conflicts and then commit the result.
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2|MERGING)
$
此时查看a.txt这个文件,发现文件中正文变成了上下两部分,分别是两个不同分支中此文件的的内容。可以使用vi编辑器或其他编辑工具修改文件保留自己需要的部分,或者可以使用git mergetool来处理。
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2|MERGING)
$ cat a.txt
# a.txt
This is a.txt
Test files
<<<<<<< HEAD
Upgrade
=======
Downgrade
>>>>>>> f1
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2|MERGING)
$ git mergetool
This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc codecompare emerge vimdiff
Merging:
a.txt
Normal merge conflict for 'a.txt':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (vimdiff):
还有 4 个文件等待编辑
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2|MERGING)
$ cat a.txt
# a.txt
This is a.txt
Test files
Upgrade
这个问题的处理过程中会生成一个.orig文件,此为冲突现场,没有用可以使用rm命令删除了:
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2|MERGING)
$ ll
total 4
-rw-r--r-- 1 Lexsion 197609 45 1月 21 00:33 a.txt
-rw-r--r-- 1 Lexsion 197609 91 1月 20 23:35 a.txt.orig
-rw-r--r-- 1 Lexsion 197609 12 1月 15 21:38 b.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 15 23:46 fa.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 16 00:39 fb.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 20 22:27 m1.txt
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2|MERGING)
$ rm a.txt.orig
然后git commit提交,查看日志,日志中可以看到明显的分支变化:
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2|MERGING)
$ git commit -m "update a.txt with f2"
[f2 b3012b6] update a.txt with f2
User@PC MINGW64 /e/git_repos/Git-Tutorial (f2)
$ git dog
* b3012b6 (HEAD -> f2) update a.txt with f2
|\
| * 5607205 (f1) Modified a.txt DG
* | 84e558c modified a.txt UG
|/
* 6f3cfec (master) add m1.txt
* 40f3061 (origin/master) Update a.txt
* bf497d9 Merge branch 'f1'
|\
| * af03df4 (origin/f1) add fb.txt
|/
* 86b7648 add fa.txt
* a03ddaa add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
5.Git的回滚与撤销
git reset命令
使用git reset命令可以回退git分支的的版本,reset的中文翻译是重置,但是Git中的reset命令用中文表示像是前往的意思,类似程序设计中的goto。使用reset命令类似于将最后一次提交的指针指向了之前的某一次提交。
要回退master到上一个版本,使用命令git reset空格后接要回退的版本,master后接的“^”标志表示上一版本,如要回退两个版本就添加两个“^”,以此类推:
git reset master^
若回退的版本较远,比如5次提交之前,可以使用:
git reset master~5
以上是一种相对撤销的操作,我们亦可以通过后接提交id来回退指定版本。比如回退到id为b3012b6的那次提交:
git reset b3012b6
git reset命令有多个模式,具体我们可以在help信息中看到。其中常用的模式为:soft、mixed、hard。其默认使用mixed模式,此模式会丢弃暂存区的文件,但是工作目录的文件不动。soft模式工作目录和暂存区的文件都不会丢弃,这看起来像是移动了HEAD的指针。而hard模式会丢弃工作目录和暂存区的文件。
git reset --help
git reset [-q] [<tree-ish>] [--] <paths>…
git reset (--patch | -p) [<tree-ish>] [--] [<paths>…]
EXPERIMENTAL: git reset [-q] [--stdin [-z]] [<tree-ish>]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
以下为使用hard模式的操作,很明显工作目录文件的数量减少了:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ ll
total 3
-rw-r--r-- 1 Lexsion 197609 44 1月 21 23:30 a.txt
-rw-r--r-- 1 Lexsion 197609 12 1月 21 23:30 b.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 21 23:30 fa.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 21 23:30 fb.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 21 23:30 m1.txt
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git dog
* b3012b6 (f2) update a.txt with f2
|\
| * 5607205 (f1) Modified a.txt DG
* | 84e558c modified a.txt UG
|/
* 6f3cfec (HEAD -> master) add m1.txt
* 40f3061 (origin/master) Update a.txt
* bf497d9 Merge branch 'f1'
|\
| * af03df4 (origin/f1) add fb.txt
|/
* 86b7648 add fa.txt
* a03ddaa add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git reset --hard 935539d
HEAD is now at 935539d Modified README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ ll
total 1
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
以下为使用soft模式,可以看出commit被回退了,但是添加到暂存区的文件还是存在的。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ touch test.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git add test.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git commit -m "add test.txt"
[master 29e4eac] add test.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 test.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git dog
* 29e4eac (HEAD -> master) add test.txt
| * b3012b6 (f2) update a.txt with f2
| |\
| | * 5607205 (f1) Modified a.txt DG
| * | 84e558c modified a.txt UG
| |/
| * 6f3cfec add m1.txt
| * 40f3061 (origin/master) Update a.txt
| * bf497d9 Merge branch 'f1'
| |\
| | * af03df4 (origin/f1) add fb.txt
| |/
| * 86b7648 add fa.txt
| * a03ddaa add b.txt
| * fcf80e7 Add a.txt
|/
* 935539d Modified README.md
* dfaaa0b add README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git reset --soft 935539d
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git status
On branch master
Your branch is behind 'origin/master' by 6 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: test.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ ll
total 1
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
-rw-r--r-- 1 Lexsion 197609 0 1月 21 23:38 test.txt
git rest命令类似移动了HEAD的指针,而具体的参数是决定了暂存区和工作目录文件的处理方式。它不是重新设置或删除了提交,只是前往了某次提交之上。也就是说,哪怕我们回退了如此多个版本,在工作目录中文件都删除了,我们依然可以再将其找回来。
使用git reflog命令查看相关的日志,找到本节例程最开始的切换分支操作的记录,使用以下命令回退到本节例程最开始的状态。可以看到,文件全部回来了。
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git reflog
935539d (HEAD -> master) HEAD@{0}: reset: moving to 935539d
29e4eac HEAD@{1}: commit: add test.txt
935539d (HEAD -> master) HEAD@{2}: reset: moving to 935539d
...
6f3cfec HEAD@{9}: checkout: moving from f2 to master
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git reset --hard 6f3cfec
HEAD is now at 6f3cfec add m1.txt
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ ll
total 3
-rw-r--r-- 1 Lexsion 197609 44 1月 22 00:12 a.txt
-rw-r--r-- 1 Lexsion 197609 12 1月 22 00:12 b.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:12 fa.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:12 fb.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:12 m1.txt
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
git revert
撤销某次操作,此次操作之前与之后的commit和history都会保留,并且这次撤销作为一次新的提交。
先运行以下命令新增一个test.c文件并commit:
touch test.c
git add test.c
git commit -m "add test.c"
然后接下来验证操作:
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ ll
total 3
-rw-r--r-- 1 Lexsion 197609 44 1月 22 00:12 a.txt
-rw-r--r-- 1 Lexsion 197609 12 1月 22 00:12 b.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:12 fa.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:12 fb.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:55 m1.txt
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:56 test.c
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git dog
* 8d08f74 (HEAD -> master) add test.c
| * b3012b6 (f2) update a.txt with f2
| |\
| | * 5607205 (f1) Modified a.txt DG
| |/
|/|
| * 84e558c modified a.txt UG
|/
* 6f3cfec add m1.txt
* 40f3061 (origin/master) Update a.txt
* bf497d9 Merge branch 'f1'
|\
| * af03df4 (origin/f1) add fb.txt
|/
* 86b7648 add fa.txt
* a03ddaa add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git revert 8d08f74
[master c7654d5] Revert "add test.c"
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 test.c
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ git dog
* c7654d5 (HEAD -> master) Revert "add test.c"
* 8d08f74 add test.c
| * b3012b6 (f2) update a.txt with f2
| |\
| | * 5607205 (f1) Modified a.txt DG
| |/
|/|
| * 84e558c modified a.txt UG
|/
* 6f3cfec add m1.txt
* 40f3061 (origin/master) Update a.txt
* bf497d9 Merge branch 'f1'
|\
| * af03df4 (origin/f1) add fb.txt
|/
* 86b7648 add fa.txt
* a03ddaa add b.txt
* fcf80e7 Add a.txt
* 935539d Modified README.md
* dfaaa0b add README.md
User@PC MINGW64 /e/git_repos/Git-Tutorial (master)
$ ll
total 3
-rw-r--r-- 1 Lexsion 197609 44 1月 22 00:12 a.txt
-rw-r--r-- 1 Lexsion 197609 12 1月 22 00:12 b.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:12 fa.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:12 fb.txt
-rw-r--r-- 1 Lexsion 197609 0 1月 22 00:55 m1.txt
-rw-r--r-- 1 Lexsion 197609 46 1月 14 00:03 README.md
此命令的格式为git revert 后接版本id,与git reset类似,亦可以后接HEAD^和HEAD~5这种格式,执行命令后会自动产生一次提交,所以需要编辑一下提交信息。命令回显的信息中表示已删除了test.c,我们可以在日志中可以看到有一次新的commit,以前的提交历史都完全存在。
综上所述,我们应该在公共分支上使用revert,避免使用reset,这样日志中就会有信息可供回溯,以此保证良好的可跟踪性。而私人的分支上,就没有什么限制了。revert是用一次新的提交回滚之前的提交,而reset是移动了HEAD指针,使用时应根据需求注意选择。
6.gitignore和fork的同步
gitignore
我们在使用IDE创建工程时,IDE会产生一些配置文件。这些文件并不属于源代码,文件中包含的配置信息可能在另一台计算机上出现错误。或者配置文件中可能存在某些隐私信息不能共享出去。亦或者编译过程中产生的中间文件我们也需要忽略。在Git中,通过gitignore实现了文件忽略。
使用这个功能,需要在工作目录下新建一个名为“ .gitignore ”的文件并将其添加到托管中。然后在文件中添加规则。常用的规则编辑语法举例:
# 忽略一个文件名确定的文件:
666.txt
# 忽略某个扩展名的文件:
*.xx
# 忽略某个目录:
Floder/
# 不忽略某个扩展名的文件:
!*.txt
# 忽略某个目录下的某个扩展名:
/Floder/*.class
通过此方法忽略的前提是新添加的文件还没有被Git托管,添加了“.gitignore”文件后,运行git add -A时会自动按照规则忽略。
如果觉得自己写gitignore文件比较麻烦,可以通过如下网站自动生成。
在线生成忽略文件:https://www.gitignore.io/
fork的同步
有时我们看到好的项目会把仓库fork到自己这里。但是后期原始仓库中的代码更新了,我们fork的仓库还是旧的。我们可能就需要更新一下。
使用以下命令可以看到git 的远程连接:
git remote -v
可以看到默认是一个名为origin的源,我们需要将上游仓库的连接添加进来,名称为upstream,命令格式如下:
git remote add upstream [仓库链接]
fetch命令可以将某个远程仓库拉取到本地,使用fetch命令将上游仓库master分支拉取到本地:
git fetch upstream master
这时使用以下命令便可以查看到远程分支:
git branch -r
然后在我们本地的master分支下运行rebase命令将拉取的上游仓库master分支与其合并,然后使用push命令推送到我们自己的远程仓库:
git rebase upstream/master
git push
需要注意的是,如果我们是做了一些代码共享的情况下,不应该使用rebase,而应该使用merge命令进行合并。push完成后,我们可以使用git log命令查看log确认已经修改。
以上便是本人在学习Git时的笔记,因初学水平有限,如有错误还请各位指正。