avatar


版本控制系统Git入门

概述

Git是分布式版本控制系统(Distributed Version Control System,简称DVCS)。为什么说是分布式呢?这是相对于集中式而言的。
集中式版本控制系统,典型代表是SVN,版本库是集中存放在中央服务器的。开发人员工作的时候首先要从中央服务器下载最新的版本,然后开发,开发完后,需要把自己开发的代码提交到中央服务器。
看起来没什么问题啊。
但,一旦中央服务器挂了,代码都提交不了,也就没有所谓的版本管理了。

相比之下,Git是分布式的,即使中央服务器挂了,影响也有限。
为什么?这就和分布式的含义有关了,这里的分布式并不是说Git被部署在多台服务器上。
而是说有两种类型的仓库:本地仓库和远程仓库。

  • 本地仓库:开发人员自己机器上的Git仓库。
  • 远程仓库:远程服务器上的Git仓库。

之间的关系如图所示:
git
解释:

  • clone:克隆,就是将远程仓库复制到本地。
  • push:推送,就是将本地仓库代码上传到远程仓库。
  • pull:拉取,就是将远程仓库代码下载到本地仓库。
  • commit:提交,在自有仓库内提交。

注意看,commit是在自己机器上操作的。这样即使中央服务器挂了,代码的版本管理不受影响,只是不能clone、push和pull。

有不少资料说Git不需要有中央服务器,我认为这个是不妥的。
Git肯定还是需要中央服务器的,不论中央服务器是一台或者多台,总之中央服务器肯定是有的。
Git的分布式是指同时存在本地仓库和远程仓库。

工作流程

Git工作流程

Git的工作流程如下:

  1. clone(克隆):从远程仓库中克隆代码到本地仓库。
  2. checkout(检出):从本地仓库中检出一个仓库分支然后进行修订。
  3. add(添加):在提交前先将代码提交到暂存区。
  4. commit(提交):提交到本地仓库。本地仓库中保存修改的各个历史版本。
  5. pull(拉取):从远程库拉到本地库,自动进行合并(merge),然后放到到工作区,相当于fetch+merge。
  6. push(推送):修改完成后,需要和团队成员共享代码时,将代码推送到远程仓库。

安装

客户端

Windows

下载Git并按安装。

地址:https://git-scm.com/download

macOS

brew install git

在macOS上,我们通过brew安装git。

1
brew install git

在安装完成后,我们可能会发现通过git --version发现没有生效。这时候只需要退出控制台,重新进入即可。

卸载之前的git

如果之前参考过https://www.runoob.com/git/git-install-setup.html,现在需要升级的话,建议先卸载原来的git。

1
sh /usr/local/git/uninstall.sh

此外,在/usr/bin/目录下,还有git。我们直接卸载会发现卸载失败,原因是macOS升级从10.11版本后引入了Rootless机制,即时是使用root身份也无法删除/System/bin/sbin/usr等目录,所以我们还需要关闭Rootless机制。
重启,按住Command+R,进入恢复模式,在顶部菜单栏找到Terminal,打开。输入命令csrutil disable,关闭Rootless机制,然后便可以卸载之前的git。

卸载完成之后,安装git。

1
brew install git

如果brew已经安装成功过git,此时需要执行下面命令

1
brew link --overwrite git

升级完成后建议还是把刚刚关闭的Rootless开启,重启,按住Command+R,进入恢复模式,打开Terminal执行命令csrutil enable

更换brew的源

如果brew的操作特别的慢的话,可以考虑更换brew的源。

打印当前的源

1
2
3
4
cd "$(brew --repo)"
git remote -v
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote -v

切换阿里源

1
2
3
4
5
cd "$(brew --repo)"
git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.aliyun.com/homebrew/homebrew-core.git
brew update

切换腾讯源

1
2
3
4
5
cd "$(brew --repo)"
git remote set-url origin https://mirrors.cloud.tencent.com/homebrew/brew.git
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.cloud.tencent.com/homebrew/homebrew-core.git
brew update

切换清华源

1
2
3
4
5
cd "$(brew --repo)"
git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git
brew update

切换官方原始源

1
2
3
4
5
cd "$(brew --repo)"
git remote set-url origin https://github.com/Homebrew/brew.git
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://github.com/Homebrew/homebrew-core.git
brew update

brew代理

配置临时代理
一般建议配置临时代理,示例代码:

1
export ALL_PROXY=socks5://【地址】:【端口】

部署代理

  1. 下载TcpRoute2,地址:
    https://github.com/GameXG/TcpRoute2
  2. 修改config.toml.example文件名,去除.example后缀。
  3. 修改config.toml.example文件内容,addr=":7070"

服务端

关于服务端,我们可以利用互联网上提供的一些代码托管服务。
最常见的就是Github:https://github.com

如果Github访问不顺畅的话,可以借助工具DevSidecar。
也可以考虑国内的替代品,Gitee(https://gitee.com),以及利用Gitlab,自行搭建Git服务端。

环境配置

此处的环境配置,指的是客户端的环境配置。

设置用户信息

示例代码:

1
2
git config --global user.name "KakaWanYifan"
git config --global user.email "i@m.kakawanyifan.com"

查看配置信息

示例代码:

1
git config --list

运行结果:

1
2
3
4
5
6
7
diff.astextplain.textconv=astextplain
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
filter.lfs.process=git-lfs filter-process
filter.lfs.required=true

【部分运行结果略】

只查看某个配置信息

示例代码:

1
git config user.name

运行结果:

1
KakaWanYifan

配置文件

通过上面的命令设置的信息会保存在当前用户的.gitconfig文件中。
例如:C:\Users\wanyf\.gitconfig

设置代理

设置命令

1
2
git config --global http.proxy http://xxx:8080
git config --global https.proxy https://xxx:8080

需要用户名密码

1
git config --global http.proxy http://【用户名】:【密码】@http://10.10.10.10:8080

查看是否生效

1
git config --get --global http.proxy

删除代理

1
2
git config --global (或 --local) --unset http.proxy
git config --global --unset http.proxy

获取Git仓库

获取Git仓库有两种方式:

  1. 在本地初始化一个Git仓库
  2. 从远程仓库克隆

在本地初始化一个Git仓库

执行步骤如下:

  1. 创建一个空目录作为本地Git仓库
  2. 执行命令git init

如果创建成功,会看到.git文件夹(此文件夹为隐藏文件夹),里面存储了日志信息和文件版本信息等内容。

从远程仓库克隆

此外,还可以从远程仓库克隆。
命令如下:

1
git clone 远程Git仓库地址

本地仓库的操作

文件的状态

Git工作目录下对于文件的修改(增加、删除、更新)会存在几个状态,这些修改的状态会随着我们执行Git的命令而发生变化。

之间的关系如下:
Git文件的状态

解释说明:

  • 新创建的文件会处于未跟踪状态
  • 被修改的已有的文件会处于未暂存状态
  • 通过git add进入暂存状态
  • 通过git commit提交

查看文件状态

git status,查看文件状态。

示例代码:

1
git status

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
On branch master

No commits yet

Untracked files:
(use "git add <file>..." to include in what will be committed)

1.txt
2.txt
3.txt
4.txt
5.txt

nothing added to commit but untracked files present (use "git add" to track)

添加文件到暂存区

git add
例如,将未跟踪的文件加入暂存区。

示例代码:

1
2
git add 1.txt
git status

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: 1.txt

Untracked files:
(use "git add <file>..." to include in what will be committed)

2.txt
3.txt
4.txt
5.txt

提交暂存区的修改到本地仓库

git commit,将暂存区的文件修改提交到本地仓库。

示例代码:

1
2
git commit
git status

运行结果:

1
2
3
4
5
6
7
8
9
10
11
git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)

2.txt
3.txt
4.txt
5.txt

nothing added to commit but untracked files present (use "git add" to track)

利用git commit提交,会打开一个类似VIM编辑器,然后我们输入每次提交描述。也可以通过git commit -m '提交描述'直接输入提交描述。

查看日志

通过git log命令查看日志。

命令形式:git log [option]
options:

  • --all:显示所有分支
  • --pretty=oneline:将提交信息显示为一行
  • --abbrev-commit:使得输出的commitId更简短
  • --graph:以图的形式显示

示例代码:

1
git log

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
commit d46c6bf62dca1e24e0e12717647e67bc273a7ed6 (HEAD -> master, origin/master, origin/HEAD)
Author: kakawanyifan <registermail@qq.com>
Date: Sat Nov 27 22:28:39 2022 +0800

up

commit 651acdf2769ba61a44679c36f2e7eeacc68378e1
Author: kakawanyifan <registermail@qq.com>
Date: Sat Nov 27 19:59:35 2022 +0800

复用

commit a89498107b7d7bc5087750a8adff5cbdc3f161bb
Author: kakawanyifan <registermail@qq.com>
Date: Sat Nov 27 19:28:03 2022 +0800

开始

commit 13997e16ae4725661957525f796d16078306a2e1
Author: KakaWanYifan <registermail@qq.com>
Date: Sat Nov 27 13:20:21 2022 +0800

回退

git reset,回退。

例如,回退到某个commit之前的版本。
git reset --hard commitID

示例代码:

1
git reset 6d977c9932da5931cfd1d910e87c9de29d8ba852

那么,怎么再回去呢?
如果知道commitID的话,再用git reset --hard commitID即可。
如果不知道的话,可以通过git reflog找回。

如果我们只是git add,还没有提交,但是想取消暂存怎么办呢?直接git reset

  • git reset --hard HEAD^
    回退到上个版本。
  • git reset --hard HEAD~n
    回退到前n次提交之前,若n=3,则可以回退到3次提交之前。

将文件添加至忽略列表

有时候我们会有些文件无需纳入Git的管理,也不希望它们总出现在未跟踪文件列表。
比如:日志文件,编译过程中的临时文件等。
在这种情况下,我们可以创建一个名为.gitignore的文件(文件名称固定),列出要忽略的文件模式。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
# no .a files
*.a
# but do track lib.a, even though you're ignoring .a files above
!lib.a
# only ignore the TODO file in the current directory, not subdir/TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory
doc/**/*.pdf
1
**/*.csv

解释说明:**表示任何子目录,*.csv表示任何.csv文件。这样,不论是当前目录下的.csv文件还是子目录中的.csv文件都会被忽略。

远程仓库的操作

查看远程仓库

git remote,会看到每一个远程仓库的简写。

示例代码:

1
git remote

运行结果:

1
origin

解释说明:
origin,是Git克隆的仓库服务器的默认名字

git remote -v,查看远程仓库的地址。
示例代码:

1
git remote -v

运行结果:

1
2
origin	git@github.com:KakaWanYifan/kakawanyifan.com.git (fetch)
origin git@github.com:KakaWanYifan/kakawanyifan.com.git (push)

解释说明:fatch是抓取,push是推送,分别代表抓取和推送的地址。

(在上文,我们查看brew的源,用的也是该命令。)

可以通过git remote show origin,查看更详细的信息。
示例代码:

1
git remote show origin

运行结果:

1
2
3
4
5
6
7
8
9
10
* remote origin
Fetch URL: git@github.com:KakaWanYifan/kakawanyifan.com.git
Push URL: git@github.com:KakaWanYifan/kakawanyifan.com.git
HEAD branch: master
Remote branch:
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)

添加远程仓库

git remote add <shortname> <url> ,添加一个新的远程Git仓库,同时指定一个可以引用的简写。

示例代码:

1
git remote add origin git@github.com:KakaWanYifan/d.git

另外,一个本地仓库可以对应多个远程仓库。

示例代码:

1
2
3
git remote add origin git@github.com:KakaWanYifan/d.git
git remote add my git@github.com:KakaWanYifan/e.git
git remote -v

运行结果:

1
2
3
4
my	git@github.com:KakaWanYifan/e.git (fetch)
my git@github.com:KakaWanYifan/e.git (push)
origin git@github.com:KakaWanYifan/d.git (fetch)
origin git@github.com:KakaWanYifan/d.git (push)

从远程仓库克隆

git clone <URL> [本地目录]
其中[本地目录]可以省略,能自动生成一个目录。

移除无效的远程仓库

git remote rm <shortname>,移除远程仓库。
注意,该操作只在本地仓库生效,对远程仓库没有影响。

从远程仓库中抓取与拉取

git fetch,从远程仓库获取最新版本到本地仓库,不会自动merge。
git pull,从远程仓库获取最新版本并merge到本地仓库。

示例代码:

1
git fetch git@github.com:KakaWanYifan/d.git

解释说明:fetch之后,在目录中并没有相关的文件,需要在手动执行git merge

示例代码:

1
2
git pull git@github.com:KakaWanYifan/d.git
ls

运行结果:

1
1.txt	2.txt	3.txt	4.txt	5.txt

推送到远程仓库

git push [-f] [--set-upstream] [远端名称 [本地分支名][:远端分支名]]

  • 如果远程分支名和本地分支名称相同,则可以只写本地分支。
    git push origin master
  • -f:强制覆盖
  • --set-upstream:推送到远端的同时并且建立起和远端分支的关联关系。
    git push --set-upstream origin master
  • 如果当前分支已经和远端分支关联,则可以省略分支名和远端名。
    所以很多时候,我们只需要命令git push

分支的操作

查看分支

列出所有本地分支

示例代码:

1
git branch

运行结果:

1
* master

列出所有远程分支

示例代码:

1
git branch -r

运行结果:

1
2
origin/HEAD -> origin/master
origin/master

列出所有本地分支和远程分支

示例代码:

1
git branch -a

运行结果:

1
2
3
* master
remotes/origin/HEAD -> origin/master
remotes/origin/master

创建分支

git branch,后面跟着新分支名。

示例代码:

1
2
git branch dev
git branch -a

运行结果:

1
2
3
4
  dev
* master
remotes/origin/HEAD -> origin/master
remotes/origin/master

切换分支

示例代码:

1
git checkout dev

运行结果:

1
Switched to branch 'dev'

推送至远程仓库分支

示例代码:

1
git push origin dev

运行结果:

1
2
3
4
5
6
7
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: Create a pull request for 'dev' on GitHub by visiting:
remote: https://github.com/KakaWanYifan/d/pull/new/dev
remote:
To github.com:KakaWanYifan/d.git
* [new branch] dev -> dev

合并分支

示例代码:

1
git merge dev

运行结果:

1
Already up to date.

注意,有时候合并操作不会如此顺利,如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git就没办法合并它们,同时会提示文件冲突。此时需要我们打开冲突的文件并修复冲突内容,最后执行git add命令来标识冲突已解决。

删除分支

示例代码:

1
git branch -d dev

运行结果:

1
Deleted branch dev (was 7d7666b).

如果要删除的分支中进行了一些修改,但坚持要删除此分支,可以将命令中的-d参数改为-D

如果要删除远程仓库中的分支,可以使用命令

1
git push origin –d branchName

rebase

还有一种操作,rebase,这种操作也可以起到合并分支的效果,但与merge的逻辑不一样。

merge的逻辑

merge的逻辑是找到这两个分支的祖先commit,在两个分支最新的commit进行三方对比合并。

merge的逻辑

如图,共同的祖先是commit2,master分支最新的是commit6,develop分支最新的是commit5,merge会基于2,5,6这三个commit进行对比,对每个文件进行一个哈希计算,这个值一样的话说明文件没有改动过。
commit6和commit2对比,如果文件的哈希值不一样,同时commit5和commit2对比,发现一样,说明只有commit6修改了这个文件,这种情况直接合并,不会提示。
commit6和commit2对比,如果文件的哈希值不一样,同时commit5和commit2对比,哈希值不一样,说明两个分支都对同一个文件修改了,则提示冲突,需要我们手动merge。
最后合并完后会生成一个新的commit7

rebase的逻辑

重新基于一个分支上进行commit,就是会把当前分支从祖先的commit后提交的commit都撤销掉,放到一个缓存里面去,然后基于一个分支的后面,把缓存的commit再按顺序一个个新增到这个分支后面。

rebase的逻辑

例如上图,在develop分支进行rebase master分支,则会把develop分支上的基于和master共同的祖先分支commit2的后面的commit4、5撤销掉,在master的最新的commit6后面重新增加commit4、5上去,这时候develop相当于在master最新的commit上逐步提交了两个commit4、5。

标签的操作

Git可以在某一次提交上打上标签,以示重要。通过标签可以非常方便回到某个时间点。

列出所有标签

示例代码:

1
git tag

运行结果:

1
0.1

创建标签

git tag <标签名>

将标签推送至远程仓库

示例代码:

1
git push origin 0.1

运行结果:

1
2
3
Total 0 (delta 0), reused 0 (delta 0)
To github.com:KakaWanYifan/d.git
* [new tag] 0.1 -> 0.1

检出标签

git checkout -b <新分支名> <标签名>,新建分支,并指向指定的标签。

示例代码:

1
git checkout -b xin 0.1

运行结果:

1
Switched to a new branch 'xin'

删除标签

删除本地标签

示例代码:

1
git tag -d 0.1

运行结果:

1
Deleted tag '0.1' (was 7d7666b)

删除远程标签

示例代码:

1
git push origin :refs/tag/0.1

运行结果:

1
2
3
remote: warning: Deleting a non-existent ref.
To github.com:KakaWanYifan/d.git
- [deleted] refs/tag/0.1

SSH协议传输数据

通过SSH协议传输数据。这样对于Github来说,在国内的话,会更快更顺畅。

步骤

  1. 设置git的user name和email
  2. 检查是否存在SSH Key
  3. 生成SSH Key
  4. GitHub添加SSH Key
  5. 验证

设置git的user name和email

该部分在上文已经讨论过,不再进行讨论,略。

检查是否存在SSH-Key

示例代码:

1
2
cd ~/.ssh
ls

运行结果:

1
id_rsa		id_rsa.pub	known_hosts
  • 该结果说明已经存在了SSH-Key
  • Windows系统的方法相同,但是需要注意,要通过Git Bash

生成SSH-Key

1
ssh-keygen -t rsa -C "第一步配置的邮箱"

期间会有几个确认,一般直接回车即可。
如果SSH Key已经存在的话,会直接覆盖。

在生成之后,通过命令cat id_rsa.pub查看秘钥(ssh-rsa开头的),并拷贝。

GitHub添加SSH Key

GitHub点击用户头像,选择setting,新建一个SSH-Key,把上一步拷贝的秘钥复制进去。

验证

示例代码:

1
ssh -T git@github.com

运行结果:

1
Hi KakaWanYifan! You've successfully authenticated, but GitHub does not provide shell access.

修改

特别的,之前已经是https的链接,现在想要用SSH提交怎么办?
直接修改项目目录下 .git文件夹下的config文件,将地址修改一下就好了。

IDEA中的Git

配置

一般,IDEA会根据我们配置的环境变量,自动去找到Git。如果没有找到,我们可以自行设置。
我们可以点击Test按钮测试是否正确配置。

Git

操作

加入Git管理

如果有一个项目,之前没有加入到Git的管理中,现在想加入Git管理,方式如下:

加入Git

忽略列表

忽略列表

一般在IDEA的Java项目中,需要忽略的有:

  • /.idea/
  • /target/
  • 【工程名】.iml

添加到暂存区

添加到暂存区

提交到本地

在IDEA中,有三个地方可以提交。

在工具栏中,

在工具栏中,"勾"。

在顶部菜单栏中,Commit...

在顶部菜单栏中,"Commit..."

右键工程,GitCommit Directory

右键工程,"Git"、"Commit Directory"

推送到远程

与"提交到本地"类似,推送同样有三个地方,截图略。

因为我们是直接在本地创建的仓库,所以这时候还需要定义远程仓库的地址。
在本文,我们远程仓库的地址是:git@github.com:KakaWanYifan/git.git

(这种格式的地址,就是我们上文讨论的SSH。)

推送到远程

克隆到本地

在IDEA的启动页面选择"Git from VCS"。

Git from VCS

或者,我们可以在顶部菜单栏找到NewProject from Version Control

Project from Version Control

从远程拉取

关于从远程拉取,这个很简单,pull

需要注意的是,在IDEA中,除了pull,还有一个Update projects
关于MergeRebase的区别,我们在上文已经讨论过。

Update projects

版本对比

版本对比

创建和切换分支

创建和切换分支

分支合并

分支合并

对于其他JetBrains的产品,例如PyCharm,整体过程大同小异。

规范

分支规范

关于分支规范,不同公司可能略有差异,但整体上是这样。

分支规范

  1. 最稳定的代码放在master分支上(相当于SVN的trunk分支),我们不要直接在master分支上提交代码,只能在该分支上进行代码合并操作,例如将其它分支的代码合并到master分支上。
  2. 我们日常开发中的代码需要从master分支拉一条develop分支,该分支所有人都能访问,但一般情况下,我们也不会直接在该分支上提交代码,代码同样是从其它分支合并到develop分支上去。
  3. 当我们需要开发某个特性时,需要从develop分支拉出一条feature分支,例如feature-name1feature-name2,在这些分支上并行地开发具体特性。
  4. 当特性开发完毕后,我们决定需要发布某个版本了,此时需要从develop分支上拉出一条qa分支,例如qa-name1-name2,并将需要发布的特性从相关feature分支一同合并到qa分支上,随后将针对qa分支部署测试环境,测试工程师在该分支上做功能测试,开发工程师在该分支上修改bug。
  5. 待测试工程师无法找到任何bug时,我们可继续从master分支拉出一条release分支,此时release的版本号必须根据mastertag版本号来递增,例如release1.0.0,并将qa-name1-name2分支合并到release1.0.0,并部署到预发环境,再次验证以后,均无任何bug,此时可将release分支部署到生产环境。
  6. 待上线完成后,将release分支上的代码同时合并到develop分支与master分支,并在master分支上打一个tag,例如v1.0.0
  7. 当生产环境发现bug时,我们需要从对应的tag上例如(v1.0.0)拉出一条hotfix分支(例如hotfix1.0.1),并在该分支上做bug修复。待bug完全修复后,需将hotfix分支上的代码同时合并到develop分支与master分支。最后,在master分支打tag(例如 v1.0.1)。

管理规范

  1. 上传内容:保证GIT上保存的是"干净"的代码,不得有编译后再次生成的代码,如Java字节码文件和JSP生成文件,也不能有IDE生成文件。
  2. 上传注释:必须加简要的注释,注释的内容应包含开发的模块名称以及功能描述。
    1. 功能提交:[模块名称]功能描述,如:[用户模块]用户列表增加手机号字段显示;
    2. BugFix:[模块名称]Bug-编号:Bug-描述,如:[用户模块]Bug-1203:用户创建保存失败已修复;
  3. 上传质量:提交和合并到分支上的代码尽量保证是自己测试通过的代码,以免影响别的项目/同事。
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/19903
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板