Git学习记录
在Windows上安装Git
前往Git官网下载安装包直接安装即可,安装完在命令行终端输入git --version
可以看到git的版本表示安装成功。
git --version
git version 2.30.0.windows.2
创建版本库
在D盘创建一个名为learngit的文件夹作为版本库(repository),这个文件夹下的所有文件都可以被Git管理起来。
右击learngit文件夹,选择“Git Bash Here”,会弹出一个命令行窗口,在命令行中输入git init
,把learngit文件夹变为Git可以管理的仓库。
$ git init
Initialized empty Git repository in D:/learngit/.git/
执行完命令后,在learngit文件夹下会生成一个.git的隐藏文件夹。
在learngit文件夹中新建一个名为readme.txt的文件,内容如下:
Git is a version control system.
Git is free software.
把一个文件放到Git仓库需要两步:
- 在命令行中输入
git add readme.txt
,把文件添加到仓库。 - 在命令行中输入
git commit -m "write a readme file"
,把文件提交到仓库。-m "xxx"
是本次提交的说明。
$ git commit -m "write a readme file"
[master (root-commit) 9129317] write a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
时光穿梭机
版本回退
把readme.txt文件修改为:
Git is a distributed version control system.
Git is free software.
输入git add readme.txt
和git commit -m "add distributed"
命令把修改提交到仓库。
接着把文件修改为:
Git is a distributed version control system.
Git is free software distributed under the GPL.
输入git add readme.txt
和git commit -m "append GPL"
命令把修改提交到仓库。
输入git log
会显示从最近到最远的提交日志。
$ git log
commit e12d14717b81c177921742e1e5966aa34b4d75b8 (HEAD -> master)
Author: tinfy <42999170+tinfy@users.noreply.github.com>
Date: Mon Mar 15 20:14:28 2021 +0800
append GPL
commit 99d1e27e5ffb34d1e9c45566d916a93d270a7dd3
Author: tinfy <42999170+tinfy@users.noreply.github.com>
Date: Mon Mar 15 20:12:53 2021 +0800
add distributed
commit 9129317ac5714f479b9c53edee14337bb0ea69f9
Author: tinfy <42999170+tinfy@users.noreply.github.com>
Date: Mon Mar 15 20:05:25 2021 +0800
write a readme file
如果嫌输出信息太多,看得眼花缭乱的,可以加上--pretty=oneline
参数:
$ git log --pretty=oneline
e12d14717b81c177921742e1e5966aa34b4d75b8 (HEAD -> master) append GPL
99d1e27e5ffb34d1e9c45566d916a93d270a7dd3 add distributed
9129317ac5714f479b9c53edee14337bb0ea69f9 write a readme file
输出的一大串类似e12d147...
的是commit id
(版本号)。
如果要从当前版本append GPL
回到上一个版本add distributed
,可以使用$ git reset --hard HEAD^
命令:
$ git reset --hard HEAD^
HEAD is now at 99d1e27 add distributed
回到上上个版本就是$ git reset --hard HEAD^^
命令,往上100个版本写100个^比较容易数不过来,所以写成HEAD~100
。
打开readme.txt可以看到里面的内容回到了add distributed
版本。
输入git log
命令,最新的append GPL
版本不见了。
$ git log --pretty=oneline
99d1e27e5ffb34d1e9c45566d916a93d270a7dd3 (HEAD -> master) add distributed
9129317ac5714f479b9c53edee14337bb0ea69f9 write a readme file
如果要回到append GPL
版本,只要命令行没有关掉,就可以往上找到append GPL
的commit id
是e12d147...
,输入$ git reset --hard e12d147
命令可以回到append GPL
版本。版本号没必要写全,前几位就可以了,Git会自动去找。
$ git reset --hard e12d147
HEAD is now at e12d147 append GPL
打开readme.txt,果然又回到了append GPL
版本。
但是如果命令行已经关掉了,再想恢复到append GPL
,就必须找到append GPL
的commit id
。Git提供了一个命令git reflog
用来记录你的每一次命令:
$ git reflog
99d1e27 HEAD@{1}: reset: moving to HEAD^
e12d147 (HEAD -> master) HEAD@{2}: commit: append GPL
99d1e27 HEAD@{3}: commit: add distributed
9129317 HEAD@{4}: commit (initial): write a readme file
现在知道了append GPL
的commit id
是e12d147,又可以回到append GPL
版本了。
工作区和暂存区
工作区(Working Directory):就是在电脑里能看到的目录,learngit文件夹就是一个工作区。
版本库(Repository):工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
前面把readme.txt文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit
就是往master分支上提交更改。
实践出真知。先对readme.txt做个修改,然后在工作区新建一个LICENSE.txt文件,随便添加一些内容。
使用git status
查看一下状态:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: readme.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
LICENSE.txt
no changes added to commit (use "git add" and/or "git commit -a")
Git非常清楚地告诉我们,readme.txt被修改了,而LICENSE.txt还从来没有被添加过,所以它的状态是Untracked。
现在,使用两次命令git add
,把readme.txt和LICENSE.txt都添加后,用git status
再查看一下:
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: LICENSE.txt
modified: readme.txt
现在,暂存区的状态就变成这样了:
所以,git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit
就可以一次性把暂存区的所有修改提交到分支。
$ git commit -m "understand how stage works"
[master 4dc3ba4] understand how stage works
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 LICENSE.txt
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的:
$ git status
On branch master
nothing to commit, working tree clean
现在版本库变成了这样,暂存区就没有任何内容了:
管理修改
Git跟踪并管理的是修改,而非文件。比如新增了一行、删除了一行、更改了某些字符、删了一些又加了一些,都是一个修改,甚至创建一个新文件,也算一个修改。
下面做一个实验,在readme.txt中添加一行:Git tracks changes.
。然后使用git add readme.txt
和git status
命令:
$ git add readme.txt
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
然后,把上面添加的一行修改为Git tracks changes of files.
,再用git commmit
命令提交:
$ git commit -m "git tracks changes"
[master 2cced03] git tracks changes
1 file changed, 2 insertions(+), 1 deletion(-)
git status
命令查看状态:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
怎么第二次的修改没有被提交?这是因为git管理的是修改,第一次修改后,使用git add
命令把修改放入了暂存区,第二次修改后,没有放进暂存区。而git commit
只会提交暂存区的修改,所以第二次的修改没有被提交。
提交后,使用git diff HEAD -- readme.txt
命令查看工作区和版本库之间的区别:
$ git diff HEAD -- readme.txt
diff --git a/readme.txt b/readme.txt
index db28b2c..9a8b341 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
-Git tracks changes.
\ No newline at end of file
+Git tracks changes of files.
\ No newline at end of file
可以发现第二次修改确实没有被提交。如果要继续提交第二次修改,可以使用git add
再git commmit
。
撤销修改
在readme.txt中添加了一行My stupid boss still prefers SVN.
。猛然发现可能会有问题,可以使用git checkout -- readme.txt
命令丢弃工作区的修改。
如果你写了一些胡话,还git add
添加到了暂存区,使用git status
查看一下状态:
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: readme.txt
可以使用git reset HEAD readme.txt
把暂存区的修改撤销掉(unstage),git status
查看一下状态:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
现在暂存区是干净的,工作区有修改。再使用上面学的git checkout -- readme.txt
命令丢弃工作区的修改。git status
查看状态:
$ git status
On branch master
nothing to commit, working tree clean
现在暂存区是干净的,工作区也回到了修改前的版本。
如果你不但写错了东西,还从暂存区提交到了版本库。还是可以使用“版本回退”目录下学到的git reset --hard HEAD^
命令回到修改前的版本。
删除文件
在git中,删除也是一个修改操作。
在learngit文件夹中新建一个test.txt文件,git add
和git commit
提交到版本库。
但是你在learngit文件夹中把test.txt文件给删掉了,使用git status
查看哪些文件被删了:
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
这时有两种情况:
1.你确实是要删掉这个文件,使用git rm test.txt
和git commit -m "remove test.txt"
命令把版本库中的test.txt文件给删掉。
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master ba7145c] remove test.txt
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 test.txt
2.删错了,但是版本库中还有,可以使用git checkout -- test.txt
把误删的文件恢复到最新版本。如果从没被添加到版本库就被删除,是无法恢复的。
远程仓库
添加远程库
Git 远程仓库(Github)配置、创建、连接、删除
在GitHub中创建一个仓库命名为learngit,可以把一个本地仓库和GitHub仓库关联,再把本地仓库的内容推送到GitHub仓库。
在本地仓库下运行命令git remote add origin https://github.com/tinfy/learngit.git
。添加后,远程库的名字就是origin
。然后使用git push -u origin master
把本地仓库的内容推送到GitHub仓库。
由于远程库是空的,第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。git remote rm origin
可以删掉远程连接。
从远程库克隆
使用git clone https://github.com/tinfy/learngit.git
命令可以把GitHub仓库克隆到本地。
分支管理
创建与合并分支
创建一个dev分支,然后切换到dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
-b
表示创建并切换,相当于这两条命令:
git branch dev
git checkout dev
git branch
查看当前分支(当前分支前面会有一个*号):
$ git branch
* dev
master
在当前分支(dev)上在readme.txt文件后添加一行Creating a new branch is quick.
,git add
和git commit
提交。git branch master
切换到master分支。打开readme.txt发现刚才添加的内容不见了,这是因为是在dev分支上提交的,而master分支的提交点没有变。git merge dev
把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating 9aca910..11e4e21
Fast-forward
readme.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
git merge
命令用于合并指定分支到当前分支。
现在可以使用git branch -d dev
删除dev分支了:
$ git branch -d dev
Deleted branch dev (was 11e4e21).
创建并切换到新的dev分支,还可以使用git switch -c dev
命令。
切换到master分支,还可以使用git switch master
命令。
解决冲突
使用git switch -c feature1
创建并切换到新分支feature1
上,修改readme.txt最后一行为Creating a new branch is quick and simple.
,在feature分支上git add
和git commit
提交到版本库。git switch master
切换到master分支,在master分支上修改readme.txt最后一行为Creating a new branch is quick & simple.
,同样把修改提交到版本库,现在分支变成了这样:
尝试git merge feature1
把feature1分支合并到master分支上,显示有冲突:
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
打开readme.txt,内容如下:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick and simple.
>>>>>>> feature1
把后面的五行修改为:Creating a new branch is quick and simple.
,再git add
和git commit
提交到版本库。
现在分支变为了这样:git log --graph
可以查看分支合并图。最后,使用git branch -d feature1
删除feature1分支。
分支管理策略
通常,合并分支时,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息,合并后的分支是这样的:
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。git switch -c dev
创建并切换到dev分支,修改readme.txt并提交到版本库,git switch master
切换到master分支。
使用带--no-ff
参数的git merge
命令把dev分支合并到master分支。
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
合并后,git log
查看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* 3dbaaa3 (HEAD -> master) merge with no-ff
|\
| * 7658400 (dev) add merge
|/
* 0752fac conflict fixed
|\
| * 04ebb72 (feature1) add simple
* | 340328f add &simple
|/
* 11e4e21 branch test
* 9aca910 add test.txt
* ba7145c remove test.txt
* 03ab752 add test.txt
* 2cced03 git tracks changes
* 4dc3ba4 understand how stage works
* e12d147 append GPL
* 99d1e27 add distributed
* 9129317 write a readme file
不使用Fast forward模式,merge后就像这样:
Bug分支
如果临时需要修复一个bug,但现在dev分支上的工作还没提交,还需要一段时间才能全部完成,可以使用git stash
“储藏”工作区,等以后恢复工作区后继续工作,git status
可以看到工作区是干净的。
从master分支创建一个临时分支issue-101,修复bug后提交到版本库,切回到master分支,完成合并,最后删除issue-101分支。
现在接着会dev分支干活,git stash list
可以查看保存的工作现场:
$ git stash list
stash@{0}: WIP on dev: 7658400 add merge
恢复现场有两种办法:
- 用
git stash apply
恢复,但是恢复后,stash内容并不删除,你需要用git stash drop
来删除; - 用
git stash pop
,恢复的同时把stash内容也删了
git stash apply
恢复到指定的stash:
$ git stash apply stash@{0}
On branch dev
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
master分支上的bug修复后,dev分支上还存在相同的bug,怎么办?可以找到在issue101分支上提交的commit id,在dev分支上使用git cherry-pick <commit id>
命令复制在issue101分支上的提交到当前分支。
Feature分支
如果要开发一个新功能,最好新建一个feature分支。
如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>
强行删除。
多人协作
当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。git remote
查看远程库信息:
$ git remote
origin
git remote -v
查看详细的远程库信息:
$ git remote -v
origin https://github.com/tinfy/learngit.git (fetch)
origin https://github.com/tinfy/learngit.git (push)
推送分支
把指定的本地分支推送到远程库对应的远程分支上:
$ git push origin master
Everything up-to-date
抓取分支
把远程库克隆到本地另一个目录下模拟多人合作:
$ git clone git@github.com:tinfy/learngit.git
Cloning into 'learngit'...
remote: Enumerating objects: 35, done.
remote: Counting objects: 100% (35/35), done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 35 (delta 11), reused 35 (delta 11), pack-reused 0
Receiving objects: 100% (35/35), done.
Resolving deltas: 100% (11/11), done.
默认情况下,只能看到本地的master分支。
$ git branch
* master
现在,我的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:
$ git checkout -b dev origin/dev
Switched to a new branch 'dev'
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
他在dev分支上新建了一个env.txt,并把dev分支push到了远程:
$ git add env.txt
$ git commit -m "add env"
[dev bb829be] add env
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 env.txt
$ git push origin dev
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 310 bytes | 310.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:tinfy/learngit.git
7658400..bb829be dev -> dev
我的小伙伴已经向origin/dev分支推送了他的提交,而碰巧我也对同样的文件作了修改,并试图推送:
$ git add env.txt
$ git commit -m "add new env"
[dev 70a4189] add new env
1 file changed, 1 insertion(+)
create mode 100644 env.txt
$ git push origin dev
To github.com:tinfy/learngit.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to 'github.com:tinfy/learngit.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
推送失败,因为我的小伙伴的最新提交和我试图推送的提交有冲突。解决办法:先用git pull
把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:
$ git pull
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 2 (delta 0), pack-reused 0
Unpacking objects: 100% (2/2), 290 bytes | 2.00 KiB/s, done.
From github.com:tinfy/learngit
7658400..bb829be dev -> origin/dev
* [new branch] master -> origin/master
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> dev
git pull
也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:
git branch --set-upstream-to=origin/dev dev
如果git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
。
再执行git pull
,这回git pull
成功,但是合并有冲突,需要手动解决.解决后,提交,再push:
git commit -m "fix env conflict"
git push origin dev
Git命令总结
命令 | 功能 |
---|---|
git init | 把文件夹初始化为 Git 仓库 |
git add [filename] | 添加到暂存区 |
git commit -m “…” | 把暂存区的修改提交到版本库(引号里面添加对此次提交的说明) |
git log | 显示提交日志 |
git log –pretty=oneline | 显示简化的提交日志 |
git reset –hard HEAD^ | 回到上一个版本 |
git reset –hard [commit id] | 回到 commit id 对应的版本 |
git reflog | 显示你最近的命令 |
git status | 查看状态 |
git diff HEAD – [filename] | 查看工作区和版本库之间的区别 |
git checkout – [filename] | 丢弃工作区的修改 |
git reset HEAD [filename] | 撤销暂存区的修改 |
git rm [filename];git commit -m “…” | 删掉了工作区的文件,再执行左边两条命令删掉版本库中的文件 |
git checkout – [filename] | 恢复工作区误删的文件 |
git remote add origin … | 和远程仓库关联,关联名为origin |
git push -u origin master | 把本地的 master 分支推送到远程库的 master 分支上(只有第一次推送才需要加上-u ) |
git push origin test:master | 把本地的 test 分支推送到远程库,作为远程库的 master 分支上 |
git clone … | 克隆 github 仓库到本地 |
git checkout -b dev | 创建并切换到 dev 分支 |
git switch -c dev | 创建并切换到 dev 分支(推荐) |
git branch dev | 创建 dev 分支 |
git checkout dev | 切换到 dev 分支 |
git switch dev | 切换到 dev 分支(推荐) |
git branch | 查看当前分支 |
git merge dev | 把 dev 分支合并到当前分支 |
git branch -d dev | 删除 dev 分支 |
git log –graph | 查看分支合并图 |
git merge –no-ff | 禁用 fast forward 模式合并分支 |
git stash | 保存工作现场 |
git stash list | 查看保存的工作现场列表 |
git stash apply … | 恢复某个工作现场 |
git stash drop | 删掉工作现场 |
git stash pop | 恢复现场的同时把 stash 内容删掉 |
git cherry-pick [commit id] | 在当前分支上复制 commit id 对应的提交 |
git branch -D dev | 强行删除没有被合并的分支 dev |
git remote | 查看远程库信息 |
git remote -v | 查看详细的远程库信息 |
git update-git-for-windows | 更新 Git 版本 |