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仓库需要两步:

  1. 在命令行中输入git add readme.txt,把文件添加到仓库。
  2. 在命令行中输入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.txtgit commit -m "add distributed"命令把修改提交到仓库。
接着把文件修改为:

Git is a distributed version control system.
Git is free software distributed under the GPL.

输入git add readme.txtgit 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 GPLcommit ide12d147...,输入$ 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 GPLcommit 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 GPLcommit 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.txtgit 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 addgit 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 addgit 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.txtgit 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 addgit 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 addgit 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 addgit 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

恢复现场有两种办法:

  1. git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
  2. 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 版本