文章目录
Git优势
传统版本控制工具如CVS,SVN等都是集中式版本控制,版本库集中存放在中央处理器中。而Git实现的是分布式版本控制,每个人的电脑上都有一个完整的版本库,这样设计主要有两点好处:
- 脱机状态下也能获取历史版本信息。
- 避免了单点故障问题。
Git常用命令
git init
使用git init
可以将一个文件夹初始化为一个Git项目,初始化完成后该文件夹中会有一个新的隐藏文件夹.git
。
如:将C:\git
初始化为一个Git项目。
$ cd C:\git
$ git init
Initialized empty Git repository in C:/git/.git/
git status
一个Git项目可以分为三个部分:工作区、暂存区和版本库。
工作区 就是当前工作区域,即Git文件夹中的文件。
暂存区 在隐藏文件夹.git中,位于工作区和版本库之间,用于缓存工作区中待提交的文件。
版本库 是最终区域,每次提交到版本库的内容都会被保存为项目的一个版本。
如:在C:\git
中新建一个文件夹test.txt
,用git status
查看
$ git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
test.txt
nothing added to commit but untracked files present (use "git add" to track)
.gitignore
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件模式。
如:忽略所有以 .o 或 .a 结尾的文件
$ cat .gitignore
*.[oa]
git add
git add
命令将工作区中的某项内容复制到暂存区中。
如:将上面添加的C:\git\test.txt
添加到暂存区中。
$ git add test.txt
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test.txt
git commit
git commit
命令将暂存区中的内容提交到到版本库中,用HEAD
指针指向该版本,表示其为当前版本。
执行git commit
后会弹出文本框,在其中输入相应提交的说明。或用git commit --m "XXX"
来提交较短的说明。
如:将上面缓存的C:\git\test.txt
添加到版本库中。
$ git commit
[master (root-commit) 62beab5] new text
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 test.txt
$ git status
On branch master
nothing to commit, working tree clean
git commit -a
命令允许跳过git add
,将所有被track过的文件暂存并提交到版本库。
git commit -amend
命令提交暂存区覆盖当前HEAD
指针指向的版本,是git commit
的“后悔药”。
git diff
gid diff
命令将暂存区中的内容看作原来的版本,将工作区中的内容看作现在的版本,比较他们的区别。
如:修改C:\git\test.txt
中的内容,调用git diff
查看区别
$ git diff
diff --git a/test.txt b/test.txt
index e69de29..88c6620 100644
--- a/test.txt
+++ b/test.txt
@@ -0,0 +1 @@
+something new
\ No newline at end of file
gid diff --cached
或git diff --staged
命令用于比较暂存区和版本库中最新版本之间的区别
如:将修改后的C:\git\test.txt
中的内容add
到暂存区,调用git diff --cached
查看区别,再将暂存区commit
,调用git diff --cached
查看。
$ git diff --staged
$ git add "test.txt"
$ git diff --cached
diff --git a/test.txt b/test.txt
index e69de29..88c6620 100644
--- a/test.txt
+++ b/test.txt
@@ -0,0 +1 @@
+something new
\ No newline at end of file
$ git commit
[master 5a76c73] change 1
1 file changed, 1 insertion(+)
$ git diff --cached
git rm
git rm
用于从工作区和暂存区中删除指定的文件,即让Git不再跟踪指定文件。
如:删除C:\git\test.txt
,调用git status
和git diff
查看,用git rm
删除暂存区中的test.txt
,再次查看,最终用git commit
提交该修改。
$ rm "test.txt"
$ git status
On branch 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: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --git a/test.txt b/test.txt
deleted file mode 100644
index 88c6620..0000000
--- a/test.txt
+++ /dev/null
@@ -1 +0,0 @@
-something new
\ No newline at end of file
$ git rm "test.txt"
rm 'test.txt'
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: test.txt
$ git diff
$ git commit
[master 255b6f5] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
git mv
git mv "old" "new"
命令用于对工作区和暂存区中的文件进行重命名操作,其相当于三条指令:
mv "old" "new"
git rm "old"
git add "new"
git log
git log
命令用于所有显示历史版本信息,每个版本号是通过SHA1计算出的一个大数。
$ git log --pretty=oneline
2c056afb902361dab926a5c31a206b9199b70708 (HEAD -> master) delete
5a76c73329d8f1f9f4a3af9094515159eb91cf4c change 1
62beab5d681565e2671dcba8b65f6a94da59f22b new text
git reset
git reset
命令可实现在不同版本穿梭。
具体如:
git reset HEAD~x
回退到Head
指针的前x
个版本。
git reset 62beab5
回退到以62beab5
开头的版本号所指的版本。
git resrt --soft HEAD~
将HEAD
指针指向HEAD
之前的一个版本。
git reset --mixed HEAD~
或git reset HEAD~
将HEAD
指针指向HEAD
之前的一个版本,并将现所指的版本同步到暂存区中。
git reset --hard HEAD~
将HEAD
指针指向HEAD
之前的一个版本,并将现所指的版本同步到暂存区和工作区中。
验证soft
,mixed
和hard
的区别:
$ git reset --soft HEAD~
$ git diff
$ git diff --cached
diff --git a/test.txt b/test.txt
deleted file mode 100644
index 88c6620..0000000
--- a/test.txt
+++ /dev/null
@@ -1 +0,0 @@
-something new
\ No newline at end of file
$ git reset 2c056a
$ git reset HEAD~
Unstaged changes after reset:
D test.txt
$ git diff
diff --git a/test.txt b/test.txt
deleted file mode 100644
index 88c6620..0000000
--- a/test.txt
+++ /dev/null
@@ -1 +0,0 @@
-something new
\ No newline at end of file
$ git diff --cached
$ git reset 2c056a
$ git reset --hard HEAD~
HEAD is now at 5a76c73 change 1
$ git diff
$ git diff --cached
此外,git reset “version" "path"
会跳过移动HEAD
指针这一步,将版本version
中的path
所指的文件覆盖到暂存区。带有path
的命令只能是--mixed
级别。
git checkout
git checkout
命令用于分支切换,详见git branch
命令。
git checkout – “filename”
git checkout -- "filename"
是让暂存区中的内容同步到工作区。
$ git status
On branch 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: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout -- "test.txt"
$ git status
On branch master
nothing to commit, working tree clean
git branch
git branch
用于显示所有分支。
git branch “bname"
用于创建一个新的分支。
git branch -d
用于删除一个分支。
分支
个人理解,版本库是使用一个类似于LinkedHashMap
的数据结构存储各版本。前面提到HEAD
指针用于指向当前的版本,其实这并不完全正确。在用git log
时,可以看到有(HEAD -> master)
的字样,可见HEAD
指针指向的是一个叫做master
的东西,这个master
才指向一个版本号。那么这个master是什么呢?
这个master
就是一个分支,Git 给主分支的默认名字就叫master
,就像第一个远程仓库的默认名字是origin
一样。HEAD
指针指向一个分支指针,表示当前所在的分支,再由分支指针指向版本号。在Git中,创建一个分支就是创建一个指针,而切换分支就是移动HEAD
指针。
可以想象当版本库中不只有一个分支时,不同的分支向不同的方向发展,版本库中的线性结构会变成树状结构。
git checkout
命令用于切换分支。
git checkout -b
命令用于创建并切换到该分支,相当于git branch
+ git checkout
。
git checkout -b "bname1" "bname2"
可以以一个远端的分支为模板创建一个本地分支。
注意:当工作区或暂存区有未提交的修改时,不能随意切换分支,会报如下错误:
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
test.txt
Please commit your changes or stash them before you switch branches.
Aborting
git stash
上文提到,工作区或暂存区有未提交的修改时不能随意切换分支。那当工作区或暂存区的修改不足以commit,但又不能删除时,就需要用到git stash
命令。
git stash
命令可以处理工作目录的脏的状态 (修改的跟踪文件与暂存改动),然后将未完成的修改保存到一个栈上,你可以在任何时候重新应用这些改动。
git stash list
用于显示stash栈中的所有内容。
git stash apply
用于应用栈中的修改。
git stash drop
用于丢弃栈中元素。
git stash pop
应用修改并丢弃。
git merge
git merge "branchName"
用于将某分支合并到当前分支。
可以想象,合并时有两种情况:
1.待合并分支是当前分支所指版本的直接上游,如下:
* c1e9a79918c87c85369a5989ccef6b98a441ba3f (newBranch) 2nd change
* 044cf1bd7915a9c379b2cc05d25f26789ec6eeaf (HEAD -> master) 1st change
此时执行git merge "branchName"
,向前移动当前分支指针即可:
$ git merge newBranch
Updating 044cf1b..c1e9a79
Fast-forward
test.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --graph --pretty=oneline
* c1e9a79918c87c85369a5989ccef6b98a441ba3f (HEAD -> master, newBranch) 2nd change
* 044cf1bd7915a9c379b2cc05d25f26789ec6eeaf 1st change
我们看到提示中的Fast-Forward
字样,意思是简单将指针快进,但是这种快进式的合并存在一个问题:删除分支后,会丢掉分支信息。如果想保留分支信息,可以加上--no-ff
选项。
$ git merge --no-ff -m "merge with no conflict" newBranch
Merge made by the 'recursive' strategy.
test.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --pretty=oneline --graph
* 2f6b28377a56915165bf7f9d8572cd6ecba622db (HEAD -> master) merge with no conflict
|\
| * c1e9a79918c87c85369a5989ccef6b98a441ba3f (newBranch) 2nd change
|/
* 044cf1bd7915a9c379b2cc05d25f26789ec6eeaf 1st change
* eb4e818f28467c2eaf60d5e071304ea2dc4d29b3 change to 3
* 87806f775eee0c8f01ef46dce18e11074b4b51ce branch master
2.两个分支都有自己新的提交,此时merge会报conflict
$ git merge newBranch
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Automatic merge failed; fix conflicts and then commit the result.
根据提示要fix conflicts,打开文件看到以下内容:
<<<<<<< HEAD
123
=======
12
>>>>>>> newBranch
这是git把两个分支冲突的部分标识出来,简洁易懂。
将文件改成期望的合并后的内容,commit,即可修复冲突。
$ git commit -a -m "merge"
[master c3c2340] merge
$ git log --pretty=oneline --graph
* c3c234078b569d04b714e3f25e1ce151bf994b47 (HEAD -> master) merge
|\
| * c1e9a79918c87c85369a5989ccef6b98a441ba3f (newBranch) 2nd change
* | 51d603739fc50001daa6c2e10f57393e888f1e31 3rd change
|/
* 044cf1bd7915a9c379b2cc05d25f26789ec6eeaf 1st change
* eb4e818f28467c2eaf60d5e071304ea2dc4d29b3 change to 3
* 87806f775eee0c8f01ef46dce18e11074b4b51ce branch master
git rebase
git rebase
和merge
的作用类似,也是用于分支间的合并。但变基不会保留原来的分支形式,而将两个分支整合为一个分支。变基主要用于在本地合并分支时整合形式,避免提交到远程仓库后分支太多过于混乱。
与merge
一样,合并时会出现冲突:
$ git rebase newBranch
First, rewinding head to replay your work on top of it...
Applying: 3rd change
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
M test.txt
Falling back to patching base and 3-way merge...
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Patch failed at 0001 3rd change
The copy of the patch that failed is found in: .git/rebase-apply/patch
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
打开文件,发现和merge出现冲突时效果一摸一样:
<<<<<<< HEAD
123
=======
12
>>>>>>> newBranch
将文件改成期望的合并后的内容,根据提示git add
,git rebase --continue
。
$ git add "test.txt"
Admin@DESKTOP-UD83S84 MINGW64 /c/git (master|REBASE 1/1)
$ git rebase --continue
Applying: 3rd change
Admin@DESKTOP-UD83S84 MINGW64 /c/git (master)
$ git log --pretty=oneline --graph
* 5e47d0da06b5d526963f444d7f1f37df2dc5b963 (HEAD -> master) 3rd change
* c1e9a79918c87c85369a5989ccef6b98a441ba3f (newBranch) 2nd change
* 044cf1bd7915a9c379b2cc05d25f26789ec6eeaf 1st change
* eb4e818f28467c2eaf60d5e071304ea2dc4d29b3 change to 3
* 87806f775eee0c8f01ef46dce18e11074b4b51ce branch master
发现两个分支已经被合并到一个分支中,且没有保留原来的分支格式。