不只是版本控制:使用Git搭建你的工作流

警告
本文最后更新于 2022-08-24,文中内容可能已过时。

全文字数:5241

阅读时长:15分钟

开始

在2021 Stack Overflow 最受欢迎的工具调查排名中,有超过90%的开发者选择git,它如今已成为开发人员的基本技能和工作时首选的版本控制系统。本文将介绍git的基本概念和常用操作,教你使用git快速完成自己的工作流。

本文只介绍基本操作和概念,若想对git有更详细的了解,请到git官网(https://git-scm.com)获取更多细节。推荐到官网阅读并下载 Pro Git 中文版阅读(https://book.git-scm.com/book/zh/v2)。有时网络不太好,你可以在公众号消息界面发送 ProGit 即可获得文章编写时的最新pdf文件。

git(/ɡɪt/)是一个分布式版本控制软件软件,最初由Linus Torvalds创作,于2005年以GPL许可协议发布。最初目的是为了更好地管理Linux内核开发而设计。

历史(Wikipedia)

自2002年开始,Linus Torvalds决定使用BitKeeper作为Linux内核主要的版本控制系统用以维护代码。因为BitKeeper为专有软件,这个决定在社群中长期遭受质疑。在Linux社群中,特别是理查德·斯托曼与自由软件基金会的成员,主张应该使用开放源代码的软件来作为Linux内核的版本控制系统。Linus曾考虑过采用现成软件作为版本控制系统(例如Monotone),但这些软件都存在一些问题,特别是性能不佳。现成的方案,如CVS的架构,受到Linus的批评。

2005年,Andrew Tridgell写了一个简单程序,可以连接BitKeeper的仓库,BitKeeper著作权拥有者拉里·麦沃伊认为Andrew对BitKeeper内部使用的协议进行逆向工程,决定收回无偿使用BitKeeper的许可。Linux内核开发团队与BitMover公司进行磋商,但无法解决他们之间的歧见。Linus决定自行开发版本控制系统替代BitKeeper,以十天的时间编写出git第一个版本。

安装配置

  • Linux

在Linux上可以方便地通过自己系统的软件包管理器安装 git,如:

1
2
3
4
5
6
7
# Debian:
sudo apt install git
# Fedora:
sudo yum install git
# Arch Linux:
sudo pacman -S git
# ......
  • Windows

Windows下的安装一般选择直接到官网下载适合自己系统的版本(https://book.git-scm.com/download/win)。

若有winget(Windows下的包管理器),也可以使用:

1
winget install --id Git.Git -e --source winget
  • macOS

一般使用HomeBrew或MacPorts:

1
2
3
4
# HomeBrew:
brew install git
# MacPorts:
sudo port install git

一些概念

版本控制

我们使用git,主要是将它作为一个版本控制工具,那么什么是版本控制?

在手机上安装完软件后,我们经常需要对它们进行升级,或者说版本更新。旧版和新版软件最明显的区别是什么?就是它们的版本号不同。而一般来说,每个版本对应的软件包,它们在界面、功能、体验等方面总有一些差别,除非开发者故意拿一套不变的代码糊弄。同样的,在实际开发过程中,总是要对程序做修改,导致文件前后发生变化。我们把这些软件包,源代码,或者说文件的不同“内容”,叫做不同的版本,可以写个版本号来表示。而版本控制,就是用来记录一个或多个文件内容(版本)变化的系统。

开发程序的过程中,我们用版本控制工具来管理程序的源代码文件。但几乎所有版本控制工具都可以拿来管理 所有文件 的版本。我们可以用它来管理 .kt, .cpp, .rs, .docx, .xlsx, .txt, .png 甚至 .mp4文件。

我们为什么需要版本控制呢?假设你是一位装修设计师,正在设计一个房间,本来房间的基本架构都做好了,某天你突然想改装一下卫生间,之后又想改装一下飘窗,后面发现这样会弄坏承重墙和楼下邻居的天花板,由于你改得一团糟,很难再改回去了。这时如果你使用了版本控制,你可以很轻松地恢复到原来的样子,通常这样比备份多个文件更方便。

分布式版本控制系统

分布式是相对于集中式而言的,它们是描述多人协作的版本控制方式的。假设要开发一个大型软件,这个软件要求连接互联网,可以与人对话,会根据用户手机壳颜色更换主题(/手动滑稽),显然一个人不太可能精通所有方面,所以需要很多人同时开发,写好自己的代码然后合并在一个软件里面。

集中式指的是,这个软件的代码存放在一个中心服务器上,即它的仓库是在一个地方统一管理的,每个程序员在开发时都要连接这个服务器,取出最新的文件然后开发再上传。你可以看到这个软件每个人开发的最新情况是什么样的,也可以在别人把服务器卡死不能工作的时候回家休息一段时间。

分布式,则指的是这个软件的完整代码,分布在每一个开发人员手上,即每个人都需要有这个软件的完整仓库。分布式一般会设置一个仓库作为中心仓库,存放目前开发的主线(应该是用来作为代码交换的中心),在每次工作结束之后,每个人只需要提交自己的改动即可,同时再同步一下别人的改动。你可以按照自己的想法完全修改这个仓库,可以在公司停电的情况下在自家工作,也可以在克隆仓库时花一个多小时(一般只在第一次,且大多数都在几分钟)。

存储文件快照

不同于CVS等版本控制工具,git对待数据的方式是直接记录文件的快照(某个版本的文件本身),而不是记录文件的变化(这是基于差异的版本控制系统 delta-based)。

具体来说,基于差异的控制方式是,只记录每次的改动:假设你有一个香蕉,某天你在香蕉上贴了标签,这作为一个版本,之后你又在香蕉上打蜡,再作为一个版本。那么你现有的三个版本是这样记录的,一个香蕉, 香蕉上贴了标签, 香蕉上打了蜡。

而git则是记录每次的文件本身:还是这根香蕉,使用git记录的话,这三个版本则是这样的,一个香蕉,一个贴了标签的香蕉,一个贴了标签并打了蜡的香蕉。

git的一些特点

  • 几乎都在本地执行。git中绝大多数操作只需要访问本地资源,一般不需要互联网或其他计算机的信息,这让它在工作时具有令人极其舒服的快速。

  • 文件完整性校验。git中所有的数据在存储前都会计算SHA-1值,然后通过哈希值来引用,而不是文件名。这样,你就无法在git不知情的情况下修改文件及内容。同样,git也会发现你在传输过程中发生的信息丢失或文件损坏。

  • 一般只添加数据。我们执行的git操作几乎只向数据库中添加数据,而很难从数据库中删除数据,它几乎不会执行任何可能导致文件不能回复的操作,这让它更加安全。

Git Bash

在Linux/macOS上,运行git只需要在终端执行 git [commands命令] [arguments参数] 之类的命令即可。在Windows下,会提供 git bash 工具,使用 git bash 可以保持命令的一致性。本文的所有git命令均可以在不同平台运行。

全局配置

版本控制系统需要知道每次都是谁对文件做了修改,因此你需要为git配置你的一些信息,每台计算机(严格说是某个计算机的某个系统中的某个用户)只需要全局配置一次,今后的版本控制都将默认使用全局配置,你也可以在特定仓库中使用不同的配置。

进行全局配置的工具为 git config,git将配置存储在一些文件中:

  • Linux/macOS:

    1. 系统配置文件: /etc/gitconfig

      使用 git config --system 进行读写。

    2. 全局配置文件: ~/.gitconfig 或 ~/.config/git/config

    使用 git config --global 进行读写。

    1. 仓库配置文件:仓库/.git/config

      使用 git config --local 进行读写,前提是你已经在此仓库中。

  • Windows(使用命令同上)

    1. 系统配置文件:C:\ProgramFiles\Git\etc\gitconfig (在你的Git安装目录下寻找)
    2. 全局配置文件:C:\Users\用户名.gitconfig
    3. 仓库配置文件:仓库\.git\gitconfig

大多数情况下,我们只需要关注全局配置文件,在极少数的情况下会配置仓库配置文件。以下为配置的命令:

1
2
3
4
5
6
7
# 查看现有配置
git config --list

# 配置用户信息,默认全局配置
# 可使用 --local 代替 --global 来进行仓库配置
git config --global user.name "你的用户名"
git config --global user.email 你的邮箱@mail.com

只需要输入两行命令就可以完成配置,之后就可以使用git来工作了。

工作区

在git中,文件存在三种状态:

  • 已修改 modified
  • 已暂存 staged
  • 已提交 committed

与之对应,git项目就会有三个阶段:

  • 工作区 working directory
  • 暂存区 staging area (Index)
  • git仓库 .git repository

需要知道的是,你的仓库就是在 项目 .git 文件夹下的一些内容,当你克隆仓库时,你不需要复制克隆别的文件,克隆的只是 .git文件夹。

工作区就是你本来的项目文件,它在.git文件夹外。严格来说,在你使用git管理你的项目文件后,之前的项目文件就应该看做是从.git仓库的某个特定版本的数据库中提取出来的文件,放在磁盘上以供修改使用。

暂存区是一个文件,按git的术语也可以叫做索引(Index),它一般放在.git文件夹下,保存了下次将要提交的文件列表信息。比如,你对工作区中的a文件做了修改,那么在你保存后,暂存区中就会记录a文件已经修改,准备下次提交,你可以手动选择是否提交。

git仓库是你提交的内容,你的本地仓库,它在.git文件夹下,存放项目元数据和对象数据库。

使用Git

这里将从头开始创建一个git仓库,并在实际操作中学习如何使用git工作。

创建仓库

在本地创建仓库有两种方式:

  • 将一个本地目录转换为git仓库:
1
2
3
4
5
# 新建文件夹作为仓库
mkdir my_git
cd my_git

git init 		# git init 命令将初始化一个本地仓库
  • 从其他计算机或服务器克隆一个git仓库:
1
2
# 从url地址克隆一个git仓库
git clone [url] (rename)  #可以选择添加rename参数更改目录名

在创建一个git仓库后,对应目录下会出现一个 .git 目录,这里面含有你初始化git仓库所有必要的文件,它是git仓库的骨干。

检查文件状态

你工作目录下的文件只有两种状态:已跟踪未跟踪,它们的区别是是否纳入版本控制。对于已跟踪的文件,git知道要记录它们的版本变化。通过以下命令查看当前文件是否跟踪:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
git status
# 如果有未跟踪的文件你将在Untracked files中看到
# 你还可以看到在暂存区未提交的文件

# 使用以下命令查看更简略的信息
git status -s
git status --short

# 使用 git ststis -s 时:
## ??表示未跟踪的文件
##  A表示新添加到暂存区的文件
##  M表示修改后的文件
##
##   注意,状态码包含两个字符,左侧表示暂存区,右侧表示工作区
## MM表示文件已修改并暂存,暂存后又做了修改,后做的修改未暂存
## A 表示已修改且已暂存

跟踪、暂存文件

在创建仓库之后,我们需要将项目文件添加到版本控制。

1
2
3
4
5
6
7
8
# 使用git add 跟踪未跟踪的文件,可以使用通配符
git add files 		# 跟踪files文件
git add .			# 跟踪所有文件
git add src			# 跟踪src目录下的所有文件

# 在文件修改后,再次使用add命令会将文件添加到暂存区
# 修改文件后及时运行git add命令是一个良好的习惯
git add .

需要注意的是,只有对文件进行跟踪,才可以使用git进行版本控制。每次修改完成后,应该再次使用add将文件添加到暂存区。

忽略掉某些文件

通常来说,为了方便我们会直接运行 git add 命令来将项目所有文件纳入跟踪并添加到暂存区。但有时候,会有一些本地配置文件或生成的中间文件(如gcc编译的目标文件),我们并不需要将其纳入版本控制,但一个一个进行add又有些麻烦。

这种情况下,我们可以在项目根目录(事实上可以在项目内的任何一个目录创建,以起到分别对目录管理)创建一个 .gitignore 文件,在里面列出要忽略的文件格式。

.gitignore文件的格式规范可查看官方文档,这里是一些常用的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 忽略所有的 .a 文件
*.a
# 跟踪所有的 lib.a 文件,这会覆盖之前忽略 .a 文件对 lib.a 文件的影响
!lib.a
# 忽略当前目录下的 TODO 文件
/TODO
# 忽略所有目录下名为 build 的文件夹
build/
# 忽略 app 文件夹下的 build 文件夹
app/build/

查看修改

可以使用 git statue 命令查看文件的修改状态,有时在向仓库提交前我们要查看一些详细的信息,比如某一行修改的具体内容,这时候就可以使用 git diff:

1
2
3
4
5
6
7
8
# 查看尚未暂存的文件的变化:
git diff 
# 修改的部分是与上次已暂存的文件相比较

# 查看已暂存的文件的变化:
git diff --staged
git diff --cached 		# 与上面一条同义
#修改的部分是与上次已提交到git仓库的文件相比较

提交到本地仓库

当把所有修改完成的文件提交到暂存区后,就可以提交到本地仓库了。在此之前,请务必确认所有已修改或新建的文件都有 add 过,可以使用git status查看,否则提交的时候变化不会被包含在里面。使用如下命令进行提交:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 提交到本地仓库

# 提交之前确定运行 git add
git commit
# 运行此命令会打开文本编辑器,你需要在里面填写提交说明
# 良好的提交声明会让版本管理更加有效

git commit -m "Commit Message"
# 运行此命令可以很方便地书写提交信息,更加方便,适合短说明文本

git commit -a
# 运行此命令,git会自动对所有已跟踪的文件执行 git add,使命令更简洁

要注意,提交到仓库的是暂存区的文件快照,如果工作区的修改未提交到暂存区,将会造成遗漏。

移动、移除文件

有时,我们需要git停止对某一文件的跟踪,或者删除某些错误提交到版本库中的文件,或是移动某些文件的位置,这时就需要用到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 移除文件
git rm file
# 运行此命令会删除工作区的本地文件,并撤销git对其的版本跟踪

git rm -f file
# 当要rm的文件已被提交到暂存区时,就要使用 -f (force)指令
# 强制rm,这是git的一种安全措施

# 移除在 git 仓库和暂存区中的文件,而不删除工作区的文件
git rm --cached file
# 这个命令常作为忘记添加 .gitignore文件的补救措施


# 移动文件
# git mv命令与linux的mv命令使用方法基本相同
git mv file files/file
# 将根目录的file文件移动到 files/文件夹下
git mv filea fileb
# 将当前目录的 filea 文件更名为 fileb文件

# 使用 mv 命令 可以保持git对文件的跟踪,
# 而不需要在移动或改名后再次运行 mv, git add, git rm

查看提交历史

当我们想查看项目或文件的提交历史时:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 查看提交历史
git log
# 默认情况下,git log会按时间顺序排列所有提交
# 它带有很多选项,当然你也可以使用 grep 等工具

# 显示每次提交引入的差异
git log -p 		# 或 --patch
# 显示最近2条提交
git log -n
# 显示每次提交的简略统计信息
git log --stat
#用不同格式显示
git log --pretty=[]
# []内可以是 oneline:每个提交放到一行显示
# 还有类似的 short , full ,fuller
# 可以使用 format 来定制记录的显示格式

git log --pretty=oneline --graph
# 使用此命令会用一些字符“图形化”地显示出每次提交和分支之间的关系

撤销操作

在某个阶段,比如正常运行的程序在修改后无法运行,这时我们可能需要撤销对相应文件的修改。撤销操作有可能会导致之前的工作丢失,在执行撤销前一定要注意。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 重新提交
git commit --amend
# 适用于提交后发现某些文件忘记添加或提交信息写错的情况
# 如果暂存区没有文件改变,将只覆盖提交信息
# -amend 选项可以避免因为失误导致仓库版本混乱

git reset HEAD <file>
# 此命令会将git仓库中的file文件复制到暂存区,不会改变工作区
# 以此来撤销对暂存区文件的修改
# 若文件不存在,则在暂存区中删除

git checkout -- <file>
# 此命令会同时还原暂存区和工作区的file文件

远程仓库

为了能在git管理的项目上写作,我们通常会使用远程仓库,比如 Git Hub ,下面介绍如何连接远程仓库。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 查看当前仓库对应的远程仓库
git remote 		# -v选项会显示git保存的简写

# 如果当前仓库还未配置远程仓库,则需要
# 添加远程仓库
git remote add <shortname> <url>
# shortname 设置为你自己对仓库的简写
# url 为远程仓库的地址

# 从远程仓库中同步最新数据
git fetch <remote>
# 会从远程仓库下载最新版本的文件和数据

# 推送到远程仓库
git push origin master
# 会将本地仓库同步到远程仓库的master分支

# 查看远程仓库
git remote show origin

# rm远程仓库
git remote rename name1 name2
git remote remove name1
# 命令会将 name1 仓库更名为name2
# 命令会移除 name1 远程仓库
# 需要你对远程仓库具有读写权限
Buy me a coffee~
支付宝
微信
0%