Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Git学习笔记

20
Aug
2013

Git学习笔记

By Alex
/ in Work
0 Comments
简介
关于版本控制系统

所谓“版本控制系统”(VCS)是指一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。通常我们使用的VCS都是集中式的版本控制系统(CVCS)——在服务器上集中管理所有文件的所有修订版本,而所有需要协同工作的人们都通过客户点连接到CVCS来获取更新或者提交修订。CVCS著名产品包括VSS、CVS、SVN等。与CVCS相对,本地版本控制系统(LVCS)仅在本地计算机存储文件修订版,这种系统包括RCS,以及很多IDE提供的Local History组件。

CVCS和LVCS面临的共同问题是单点故障,如果系统所在机器的磁盘发生损坏,你将丢失所有数据。为了解决此问题,分布式版本控制系统(DVCS)开始出现,DVCS客户端不仅仅提取最新版本的快照,而且会把整个版本库(Repository,也叫仓库)完整的镜像克隆下来,你本地相当于拥有一个完整的仓库。每一次克隆操作都相当于对版本库进行了一次备份。

关于Git

Git是一个目前非常流行的分布式版本控制系统,它的最初用途是管理Linux的源代码,它是由Linux之父Linus用C语言开发的,Git诞生不久就开始流行。2008年,提供免费Git存储的网站GitHub上线,无数开源项目从Google Code、Source Forge等仓库转到GitHub。

Git有着很多独到的设计理念,这使得它和SVN之类传统的VCS非常不同。

Git的特性
记录快照而非差异

大部分VCS基于Diff的方式来存储信息——记录文件的Base版本,后续版本仅仅记录修改的增量:deltas

而Git不是这样,它的每个版本都包含了对版本库全部文件的快照。为了提高存储效率,没有修改的文件不会再次存储,只会保存指向前一版本文件的链接:

snapshots

本地执行几乎全部操作

在Git中,绝大部分操作都只需要访问本地文件和资源,这意味着你不必忍受网络延时,甚至可以离线工作。

举例来说,使用SVN时,如果你向查看某个文件的修订历史,里必须联网到SVN服务器,而使用Git时,你只需要查询本地磁盘即可。

保证数据完整性

Git在存储任何数据之前,都会计算散列值(SHA-1),在Git数据库中,也是用此散列值而非文件名来引用目标文件。这保证了文件损坏、篡改能够被发现。

此散列值也被作为提交标识符(commit id),由于Git是DVCS,因此不能使用SVN那样的整数作为提交标识符。

一般只能添加数据

你执行的Git操作,几乎都是往Git数据库中插入新的数据,你很难让Git执行任何不可逆的操作或者以任何方式删除数据。文件一旦提交(committed),你就不用担心数据丢失,特别是在定期推送数据库到其它仓库的情况下。

三种状态和三个区域

在Git中,你的每个文件具有三种状态:

  1. 已提交(committed):数据已经安全的保存在本地仓库中
  2. 已修改(modified):修改了文件,但还没保存到本地仓库中
  3. 已暂存(staged):对一个已修改文件的当前版本做了标记,使之包含在下次提交(下个版本)的快照中。暂存区域也被称为索引

与这三种状态相关的是Git的三个区域:

  1. 工作目录(Working Directory):针对仓库特定版本提取出来的目录,供使用和修改
  2. 仓库目录:即 .git 目录,用来保存项目的元数据和对象数据库,这是Git最重要的部分,从其它机器克隆仓库时,拷贝的就是该目录的数据
  3. 暂存区域(Stage):仅仅包含一个文件,它记录下一次将要提交的文件列表信息,一般存放在.git目录中

修改文件并提交的基本流程是:

  1. 在工作目录中修改文件
  2. 暂存文件,将其纳入暂存区域
  3. 提交变更,获取暂存文件列表,永久的保存到Git仓库目录
暂存区域

这个区域是Git不同于其它VCS的特殊概念,所有需要提交到版本库的文件,都先存放在暂存区。提交时一次性的提交暂存区中列出的所有文件。

Git基础知识
版本的表示
修订版表示法 说明
SHA-1散列 每次Commit的唯一标识符
HEAD 最后一次提交的修订版
HEAD^ 倒数第二次提交的修订版
HEAD^^ 倒数第三次提交的修订版
HEAD~100 倒数第100次提交的修订版
安装与配置

最初Git是在Linux上开发的,很长一段时间内它只能在Unix-like的系统上运行,但是现在已经被移植到Windows上。

安装Git

Linux下执行下面的命令安装:

Shell
1
2
3
4
# CentOS
sudo yum install git
# Ubuntu
sudo apt-get install git

Mac OS X的安装包到这里下载:https://git-scm.com/download/mac

Windows版本的安装包到这里下载:https://git-scm.com/download/win,注意Windows版本实际上内置了一个最小化的模拟的Linux环境。你完全可以使用Cygwin、MinGW,在其中安装Git。

构建Git

你也可以选择从源码构建Git:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装依赖
# CentOS
sudo yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel
sudo yum install asciidoc xmlto docbook2x
# Ubuntu
sudo apt-get install libcurl4-gnutls-dev libexpat1-dev gettext  libz-dev libssl-dev
sudo apt-get install asciidoc xmlto docbook2x
 
# 配置和构建
tar -zxf git-2.0.0.tar.gz
cd git-2.0.0
./configure --prefix=/usr
make all doc info
sudo make install install-doc install-html install-info
配置Git

安装完毕后,你需要进行个性化的配置, git config 命令用于执行这些配置。配置信息会保存在以下位置:

  1. /etc/gitconfig 包含系统每个用户、每个仓库的通用配置,通过 git config --system 进行配置时,从此文件读写
  2. ~/.gitconfig 或者 ~/.config/git/config 针对当前用户,通过 git config --global 进行配置时,从此文件读写
  3. .git/config 针对当前仓库

每一级别的配置,会覆盖上一级别的配置。

配置项举例
Shell
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
27
28
29
30
# 列出当前的配置项
git config --list
 
# 设置Git使用的文本编辑器
git config --global core.edito
 
# 设置用户信息
git config --global user.name "alex"
git config --global user.email alex@gmem.cc
 
# 设置凭证缓存时间为1年
git config --global credential.helper 'cache --timeout 31536000'
 
# 为子命令创建别名
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
 
# 禁用压缩
#                大于此尺寸的文件扁平化的存储,不会尝试进行delta压缩
git config --add core.bigFileThreshold 1
 
# 禁止HTTPS证书合法性验证
git config http.sslVerify false
 
 
# 仅当速度小于1KB,且持续600秒,才导致操作中断
git config --global http.lowSpeedLimit 1000
git config --global http.lowSpeedTime 600
日常操作
忽略某些文件

开发过程中,某些工程文件无需纳入版本管理,例如日志文件、IDE设置文件、构建产生的临时文件。我们需要告诉Git,不去跟踪它们,也不让它们出现在未跟踪文件的列表中。这时可以使用 .gitignore 文件。

该文件中的内容,满足以下规则:

  1. 所有空行或者以 # 开头的行都会被忽略
  2. 可以使用标准的glob模式匹配。glob是Shell使用的简化版正则式:
    1. *  匹配0-N个任意字符
    2. [A-Z0-9] 匹配枚举中的单个字符
    3. ? 匹配一个任意字符
    4. ** 匹配任意0-N级中间目录
  3. 模式可以以 / 开头防止递归
  4. 模式可以以 /结尾,表示被排除的是目录
  5. 可以在模式前面加上 ! 表示取反匹配
.gitignore文件的位置

按优先级从高到低:

  1. 命令行提供的忽略设置
  2. 仓库根目录下的 .gitignore 文件:需要被纳入版本控制、并且在clone时分发给其它仓库的忽略设置
  3. $GIT_DIR/info/exclude :针对特定仓库,但是不需要共享的忽略设置
  4. ~/.gitconfig 中 core.excludesFile 配置项所指出的文件:针对所有情况。默认位置 $HOME/.config/git/ignore 
模式举例
模式 说明
*.a 排除当前目录、子目录下任何扩展名为a的文件
!lib.a 不排除lib.a文件,即使上面指定了*.a这个模式
/TODO 排除当前目录下的TODO文件
build/ 排除build目录下的所有文件
doc/*.txt 排除doc目录下的所有文本文件,注意 doc/server/arch.txt不被排除
doc/**/*.pdf 排除doc目录、子目录下所有PDF文件
子命令说明

与Git仓库的日常交互,主要通过 git 命令进行:

子命令 说明
init

初始化一个Git仓库。该命令会在当前目录下创建一个.git子目录,该目录包含初始化Git仓库所需的全部文件:

Shell
1
git init
clone

获得已经存在的Git仓库的拷贝,这个命令与其它VCS,例如SVN的checkout不同,它会获取目标版本库的几乎所有文件(除了某些钩子设置):

Shell
1
2
3
git clone https://github.com/libgit2/libgit2
# 定制新目录的名称
git clone https://github.com/libgit2/libgit2 mylibgit

上述命令会在当前目录下创建一个libgit2目录,其中包含一个.git子目录。你可以定制新建目录的名称。刚克隆后的工作目录,其所有文件都为已跟踪、未修改状态

Git支持多种通信协议,除了上面例子中的HTTPS,还包括GIT、SSH协议等,例如:

Shell
1
2
# 使用SSH协议
git clone user@server:path/to/repo.git
add

把指定文件纳入版本控制(文件变为已跟踪),下次提交,这些文件将被保存到仓库中:

Shell
1
2
3
4
# 通过通配符添加
git add *.c
# 添加多个文件
git add README.md LICENSE

对于已跟踪的文件,修改后未纳入暂存区的,也可以通过该子命令,将其纳入暂存区

添加所有文件:

Shell
1
2
3
4
5
6
7
8
9
10
# 工作树的所有变化提交到暂存区,包括修改、新增文件,不包括删除的文件
git add .
 
# 仅仅监控已经跟踪的文件,包括删除的文件,不包括新增文件
git add -u
git add --update
 
# 上两者的合集  
git add -A
git add --all
commit 

执行提交操作,把暂存文件提交到版本库的当前分支(初始化Git库时,会自动创建唯一的默认分支master):

Shell
1
2
# -m参数用于指定本次提交的说明
git commit -m 'initial project version'

如果提交后,你发现提交说明写错了,或者遗漏了几个文件(忘记git add),可以进行“改正”: 

Shell
1
git commit --amend

如果上次提交以来,你没有修改任何文件,那么,改正和上一次提交会被记录为单次提交 

checkout

以 git checkout -- README.md 的形式使用该子命令时:

  1. 如果README.md修改后,尚未放到暂存区,则撤销为同版本库一致的版本
  2. 如果README.md修改后,被存入链暂存区,然后再次修改,则撤销为暂存区的那个版本

注意  -- ,如果缺少这两个横线,checkout的功能变为切换到另外一个分支:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 切换到testing分支
git checkout testing
 
# 切换到远程跟踪分支,并跟踪之
git checkout --track origin/serverfix
 
# 切换到远程跟踪分支,设置定制的本地分支名
git checkout -b sf origin/serverfix
 
 
# 更多语法
git checkout refs/heads/master        # 等价于 git checkout master
git checkout gmem/master              # 签出指定仓库的分支
git checkout remotes/gmem/master      # 签出远程仓库分支
git checkout refs/tags/1.0            # 签出标签
git checkout 5062ac8...               # 签出CommitID

当切换分支时:

  1. 如果工作目录树中某个文件已经修改,且未Stage,修改会合并到目标分支的代码中
  2. 如果工作目录树中某个文件已经修改,且已Stage,禁止切换
  3. 如果工作目录树没有任何修改,则切换后的工作区与目标分支内容一致
clean

从工作树中移除没有被跟踪的文件

格式:

Shell
1
git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...

选项:

-d 移除未跟踪文件和目录。如果未跟踪目录由另外一个Git仓库管理,则默认不会移除,你需要用-f两次来移除这种目录
-n 仅仅看看执行的结果,不实际执行
-X 仅仅移除被Git忽略的文件

示例:

Shell
1
2
git clean -f        # 移除未跟踪文件,递归子目录执行
git clean -fd       # 移除未跟踪文件和目录,递归子目录执行
reset

可以用来实现:撤销对文件的跟踪(暂存)、撤销并重新提交、永久撤销提交、撤销合并/拉取

格式:

Shell
1
2
3
4
5
# 拷贝tree-ish中的条目到索引(暂存区域)
git reset [-q] [<tree-ish>] [--] <paths>...
git reset (--patch | -p) [<tree-ish>] [--] [<paths>...]
# 设置当前分支的头(HEAD)为指定的commit,并可选的修改暂存区、工作树以便匹配HEAD
git reset [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]

选项:

-q 安静模式,仅报告错误
--soft  本地工作树的文件不被修改,commit和原来HEAD之间的差异会stage,便于下次提交
--mixed 和上面类似,但是commit和原来HEAD之间的差异不被stage
--hard 完全切换到commit状态
--keep 和上面类似,但是本地所作的修改不会被重置掉

示例:

Shell
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
27
28
29
# 取消指定文件的暂存(unstage),如果你:
#    1、对已修改、已跟踪的文件执行该子命令,其状态可能从左M变为右M
#    2、对新add的文件执行该子命令,其状态变为未跟踪
#    3、不指定路径的调用git reset,会导致暂存区域被清空
git status -s
# A  x.c
# M  z.c
git reset HEAD z.c # 默认重置到HEAD修订版
git status -s
# A  x.c
#  M z.c
 
 
# 如果使用--hard [<commit>] 选项调用该子命令,则工作区中对所有已跟踪文件的修改会被丢弃(被指定的修订版代替),
# 相当于执行分支/修订版切换。使用该选项时,不能指定路径
git reset --hard 3628164
# 示例:永久的撤销最近3次提交(HEAD HEAD^ HEAD~2)
git reset --hard HEAD~3
# 示例:将提交撤销,切换到分支修改
git branch refactoring          # 基于master建立分支
git reset --hard HEAD~3         # 撤销master的提交
git checkout refactoring        # 切换到新分支  
 
# 如果使用--soft [<commit>] 选项调用该子命令,则工作区保持不变
# 示例:撤销并重做提交
git commit ...             # 提交
git reset --soft HEAD^     # 撤销提交,版本库变为上次提交
edit ...                   # 应用一些修改
git commit -a -c ORIG_HEAD # 提交,并重用上次的提交对象
revert

撤销现有的提交。你可以给定若干commit,撤销这些commit对分支的修改,同时用新的commit来记录这些这些撤销行为。执行该命令需要工作树是干净的(从HEAD没有任何修改)

如果你需要撤销工作目录中所有尚未提交的修改,请使用git reset

示例:

Shell
1
2
# 撤销最近四次提交,并创建一个新提交记录,此新提交对应被撤销后的分支状态
git revert HEAD~3
status

显示工作区树的状态,列出以下信息:

  1. 索引文件和当前HEAD版本的路径差异
  2. 工作目录树和索引文件的路径差异
  3. 工作目录中尚未被跟踪(未纳入版本控制)的路径

如果工作目录没有未跟踪、已修改的文件,则会显示类似下面的信息:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
git status
# 输出:
# On branch master   当前分支,master是默认分支名
# nothing to commit, working directory clean
 
# 简明的输出状态
git status -s
# 输出:
# ?? 未跟踪的文件
# A  新添加到暂存区的文件
#  M 已经修改,尚未放入暂存区(M靠右)
# M  已经修改,已经放入暂存区(M靠左)
# MM 修改后,放入了暂存区,之后又在工作区被修改了的文件
# D  红色,手工删除了工作区中的文件,但是没有修改暂存区域
# D  绿色,通过git rm删除后的文件
# R  文件被重命名,且尚未提交
# UU 存在未解决的冲突的文件
show

可以显示多种类型的对象,示例:

Shell
1
2
# 显示标签v1.4的作者和打标签的时间
git show v1.4
 diff

显示文件内容改变的详情,被比较的文件可以是:

  1. 工作目录树与索引(暂存区域),默认行为
  2. 工作目录树与一个版本树
  3. 索引与一个版本树
  4. 两个版本树
  5. 磁盘上的两个文件
  6. 两个BLOB文件

举例:

Shell
1
2
# 显示工作区和HEAD版本的README.md文件的差异
git diff HEAD -- README.md

该子命令默认只显示尚未暂存的改动,而不是上次提交以来的所有改动。要比较已暂存区域和上次提交版本之间的差异,需要:

Shell
1
git diff --staged
rm

要从Git仓库移除某个文件,需要将其从已跟踪文件清单(暂存区域)中移除,然后提交。你可以使用 git rm 子命令完成该工作,连带将文件从工作目录删除:

Shell
1
git rm README.md

如果通过Shell手工删除一个文件,则执行status命令会显示“Changes not staged for commi”,执行status -s显示红色D

mv

与其它VCS不同,Git不会显式的跟踪文件移动操作。如果在Git中重命名了某个文件,仓库中的元数据被不会体现这是一个重命名操作。尽管如此,Git能够推断出发生了什么:

Shell
1
2
3
4
5
6
7
8
9
# 移动文件
git mv x.c z.c
# 实际上相当于同时执行三条命令(即使这样分开操作,Git也会识别为一次改名操作):
# mv x.c z.c
# git rm x.c
# git add z.c
git status -s
# 识别为重命名:
# R  x.c -> z.c
log

显示版本提交的历史记录,默认按照时间降序排列

格式:

Shell
1
git log [<options>] [<revision range>] [[--] <path>...]

选项:

--all 就好像所有refs/下的Ref,以及HEAD,都列在<commit>中了。也就是显示所有日志
--graph  在左侧绘制commit history的图形
--pretty 格式化日至条目
--abbrev-commit 不是显示40字节的hex的commit对象名,而是仅仅显示前缀
--oneline  即 --pretty=oneline --abbrev-commit
--decorate[=short|full|auto|no] 打印出每个显示的Commit的Ref name。
   如果指定short,则Ref name的refs/heads/, refs/tags/ and refs/remotes/前缀不被打印
   如果指定full,则打印完整Ref name

Shell
1
2
3
4
5
6
7
8
9
10
11
git log
# commit 32abeb80bd07130d9561de594c32afec1c77ffc1   此次提交的SHA-1散列,也叫提交标识符(commit id)
# Author: Alex Wong <gmem@me.com>                   提交人
# Date:   Mon Aug 22 09:52:38 2016 +0800            提交时间
#                                                   提交说明
#     test
 
git log -p -2   # -p 显示每次提交的内容差异  -2 仅仅显示最近两次提交
 
# 打印所有的日志     单行显示,commit id显示为7位
git --no-pager log --oneline --all
reflog

管理reflog信息。reflog中记录了你所有的命令历史,你可以通过它来查找修订版的标识符(前缀):

Shell
1
2
3
git reflog
# 32abeb8 HEAD@{0}: commit: test
# b6f3018 HEAD@{1}: commit (initial): test

查找到标识符(前缀)后,你可以任意的git reset到特定的修订版

remote

管理远程仓库,远程仓库是指托管在因特网或其他网络中的你的项目的版本库。 你可以有若干个远程仓库,通常有些仓库对你只读,有些则可以读写。 与他人协作涉及管理远程仓库以及根据需要推送(push)或拉取(fetch)数据

执行下面的命令,可以查看远程仓库:

Shell
1
2
3
4
# 列出所有远程仓库的简称
git remote
# 列出简称和URL
git remote -v

执行下面的命令,可以添加一个远程仓库:

Shell
1
2
# git remote add remote-name url
git remote add gs https://github.com/gmemcc/gitstudy

如果你通过git clone创建了本地仓库,那么原始仓库自动命名为origin,并作为远程仓库看待

执行下面的命令,可以重命名一个远程仓库:

Shell
1
git remote rename gs gsgit

执行下面的命令,可以移除一个远程仓库: 

Shell
1
git remote rm gsgit

你可以用下面的命令显示远程仓库的更多信息:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
git remote show origin
# 输出:
#* remote origin
#  Fetch URL: https://github.com/gmemcc/gitstudy
#  Push  URL: https://github.com/gmemcc/gitstudy
#  HEAD branch: master
#  Remote branches:
#    master                               tracked   已经跟踪的分支
#    dev-branch                           tracked
#  Local branch configured for 'git pull':
#    master merges with remote master               本地的master跟踪远程的master,可以pull并merge
#  Local ref configured for 'git push':
#    master pushes to master (up to date)          
fetch

从远程仓库下载对象,示例:

Shell
1
2
# git fetch [remote-name]
git fetch gs

执行完上述命令后,gs库的master分支可以在本地通过gs/master看到,你可以将其合并到自己的某个分支中去 

pull

拉取并整合(integrate)远程仓库或者本地分支。使用该命令可以自动fetch并合并

如果你通过git clone创建了本地仓库,那么本地的master分支自动被设置为跟踪(tracked)远程仓库的默认分支,运行 git pull 通常会拉取数据并合并

相当于git fetch + git merge

push

推送你本地仓库的变更到远程仓库,你必须具有远程仓库的写入权限。此外,你必须确保已经把其他人的push预先pull下来。示例:

Shell
1
2
3
4
5
6
7
8
9
10
# git push [remote-name] [branch-name]
git push origin master
 
# 推送所有Tag
git push --tags
# 推送单个Tag
git push origin 1.0.0
 
# 删除单个Tag
git push origin :1.0.0
tag

与其它VCS一样,Git支持给某一次提交打上标签,以示其重要性。Git 使用两种主要类型的标签:

  1. 轻量标签(lightweight):类似于不会改变的分支,它只是对特定提交的引用
  2. 附注标签(annotated):是Git数据库中完整的对象,记录打标签者的名字、电子邮件地址、日期时间、标签信息,并且可以使用GNU Privacy Guard (GPG)签名、验证

执行下面的命令,可以列出现有的标签:

Shell
1
2
3
4
# 列出所有标签
git tag
# 列出 v1.0.3系列的标签
git tag -l 'v1.0.3*'

执行下面的命令可以创建一个附注标签:

Shell
1
git tag -a v1.4 -m 'my version 1.4'

不使用任何-a、-s、-m选项,则自动创建轻量标签:

Shell
1
git tag v1.4-lw

指定修订版,你可以对历史版本打标签:选项

Shell
1
git tag -a v1.2 9fceb02

下面的命令用于删除本地标签:

Shell
1
git tag --delete 1.0.0

默认情况下,git push不会把标签推送到远程仓库。如果你需要共享标签,可以显式的推送之:

Shell
1
2
3
4
5
6
7
8
9
10
11
# git push [remote-name] [tagname]
git push origin v1.5
 
# 一次性推送多个标签,所有不在远程仓库的标签都被推送:
git push origin --tags
 
 
# 删除远程标签
git push origin :v1.5
# 或者
git push origin :refs/tags/v1.5

你可以从一个标签上创建出分支:

Shell
1
2
3
# git checkout -b [branchname] [tagname]
git checkout -b version2 v2.0.0
# 输出:Switched to a new branch 'version2'
branch

列出、创建或者删除分支。示例:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建一个名为testing的分支
git branch testing
# 删除一个分支
git branch -d testing
 
# 显示所有分支
git branch
#   master
# * testing   当前分支前面有一个星号
 
# 显示每个分支的最后一次提交
git branch -v
 
# 显示已经合并到当前分支的分支。--no-merged显示尚未合并的分支
git branch --merged
merge

执行分支合并——把两个或者更多的开发历史合并到一起。如果合并成功,自动保存到版本库,不需要额外commit调用

在dev分支上执行git merge master,则git会选取master,dev的共同祖先,然后进行三方合并,并形成一个新的commit,最终应用到dev分支

遇见冲突后会直接停止,等待手动解决冲突并重新提交 commit 后,才能再次 merge

rebase

变基,类似于merge,但是不会产生额外的commits

在dev分支上执行git rebase master,会发生:

  1. 定位到共同祖先
  2. 提取dev分支从该祖先往后的所有commits,记为C
  3. 将dev分支指向master分支的最新的commit
  4. 将C应用到此commit后面,C中commits的哈希值会变化

完毕后,dev分支发生变化,master不变。也就是说在dev上执行git rebase master的意思是将dev的基变为master

rebase 遇见冲突后会暂停当前操作,开发者可以:

  1. 手动解决冲突,然后 git rebase --continue 继续
  2. --skip 跳过,当前分支的修改会直接覆盖目标分支的冲突部分
  3. --abort 终止本次rebase操作

rebase 操作会丢弃当前分支已提交的 commit,故不要在已经 push 到远程,和其他人正在协作开发的分支上执行 rebase 操作

cherry-pick

挑选某些现存的提交,将其中的变更应用到当前分支。该命令会把每个提交引入的变化应用到当前分支,并且为每个提交录制一个新的提交对象。执行该命令要求工作区是干净的

此命令可以避免直接合并两个分支导致的混乱

格式:

git cherry-pick [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...

submodule add

子模块用于引用在独立版本库中管理的,当前项目需要依赖的项目。主项目和子模块的提交是相互独立的。

添加一个子模块:

Shell
1
2
3
4
5
# 这命令会在创建和子模块仓库同名的目录
git submodule add https://github.com/gmemcc/tke.git
 
# 指定目录名
git submodule add https://github.com/gmemcc/tke.git  tke-ce

 执行上述命令后,git status会显示 .gitmodules、tke文件等待提交

submodule init

submodule update

克隆带有子模块的项目时,子模块最初仅是空白的目录,你需要做初始化和更新操作:
Shell
1
2
3
4
5
6
7
8
9
# 初始化本地配置文件
git submodule init
 
# 从子模块的仓库拉取文件,然后签出到适当的Commit
git submodule update
 
# 合为一步
 
git submodule update --init

 快捷方式 git clone  --recurse-submodules可以初始化、更新所有子模块,包括嵌套的子模块

 你可以在子模块中手工fetch / merge,但是最简单的是通过下面的命令来抓取子模块的更新:

Shell
1
2
3
4
5
6
7
8
# 更新并checkout所有子模块的master分支
git submodule update --remote
 
# 仅仅更新单个子模块
git submodule update --remote tke-ce
 
# 使用下面的命令来配置,checkout子模块的哪个分支
git config -f .gitmodules submodule.tke-ce.branch stable

git pull会抓取子模块的修改,但不会更新子模块,git log会显示 Submodules changed but not updated。你需要执行submodule update命令来更新子模块。如果希望自动化此过程,你可以:

  1. 使用 git pull --recurse-submodules
  2. 配置submodule.recurse为true

默认情况下,submodule update会抓取更新并更新子目录,但是子模块的仓库处于“游离的 HEAD“状态  —— 没有本地工作分支来跟踪改动

如果希望对子模块进行修改,并发布这些修改到子模块的远程仓库。你需要:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 签出子模块的一个本地分支
cd tke-ce
git checkout release-1.3
 
# 合并上游的修改
git submodule update --remote --merge
# 修改会合并到本地分支
 
# 变基操作
git submodule update --remote --rebase
 
 
# 在推送父仓库之前,检查是否子模块已经推送上去。如果否,则禁止推送
git push --recurse-submodules=check
 
# 总是执行上述检查
git config push.recurseSubmodules check
 
 
# 在推送父仓库之前,尝试先推送子模块做的更改
git push --recurse-submodules=on-demand
submodule sync 在父项目中拉取更新后,可能出现.gitmodules中记录的子模块仓库URL变化的情况。这种情况下需要将新的URL复制到本地:
Shell
1
git submodule sync --recursive

然后从新的URL来更新子模块: git submodule update --init --recursive

describe

根据可用的ref,给予对象一个易读的名字。该命令会寻找一个从对象(默认当前commit)可达的、最近的Tag:

  1. 如果Tag指向commit,则输出就是Tag本身。例如 2.0
  2. 否则,输出为长格式,即Tag-在Tag之后发生的Commit次数-Commit的缩写名。例如 2.0-105.g9b9f95b, g后面的 9b9f95b是commit id的前 7位

格式:

Shell
1
2
3
git describe [--all] [--tags] [--contains] [--abbrev=<n>] [<commit-ish>...]
git describe [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>]
git describe <blob>

选项:

<commit-ish> Commit-ish对象名,默认HEAD
--dirty[=<mark>], --broken[=<mark>] ,描述工作树状态
--all 使用所有在refs/命名空间中找到的ref,而不仅仅是annotated tags,启用对任何已知分支、远程跟踪分支、轻量级(non-annotated)Tag的匹配
--tags 使用所有在refs/tags命名空间中可以找到的tags。启用对轻量级(non-annotated)Tag的匹配
--abbrev=<n> 不使用默认的7位hex作为缩写名
--candidates=<n> 默认最新的10个Tag可以用作生成结果,该选项可以修改
--long 总是输出长格式,即使对象匹配一个Tag
--always 作为fallback,总是生成一个唯一性的缩写名

示例: git describe --dirty --always --tags

分支管理

分支是绝大部分VCS都支持的功能。使用分支,你可以把你的工作从开发主线上分离,避免对其产生干扰。

Git的分支功能非常轻量高效,创建一个分支几乎在瞬间就可以完成,在不同分支之间进行切换,也一样的便捷。Git鼓励在工作流程中频繁的使用分支与合并。

Git分支原理

要理解Git如何处理方式,需要首先明白Git是如何保存数据的。前面我们提到过,Git提交后,保存的是那个时刻文件系统的快照,而不是各文件的差异。

执行提交操作时,Git会创建并保存一个提交对象(Commit object),该对象包含了:

  1. 提交者姓名、邮箱,以及提交的说明
  2. 指向一棵描述本次提交时文件变动状态的SHA-1散列树(快照树)根的指针:在Stage时,每个被暂存文件的SHA-1散列被计算,文件本身以Blob形式保存到Git仓库;在commit时,每个子树的SHA-1散列被计算,并形成树对象保存到Git仓库,该树对象引用了每个暂存文件的SHA-1散列。有了这棵树,可以随时重现本次提交时的状态
  3. 指向父提交(直接位于当前提交之前的提交)对象的指针。首次提交产生的提交对象,没有父对象;后续提交产生的提交对象,具有一个父对象;多个分支合并产生的提交对象,具有多个父对象

举例来说,我们现在有一个目录,其中包含三个文件,现在stage、commit它们,执行完毕后,提交对象与SHA-1散列树、当前版本文件的Blob的关系如下图:

commit-and-tree注意提交对象的tree属性指向了那棵树,后者引用了本次提交所有文件的快照。

此时,执行一些修改,提交;再次执行一些修改,再次提交,每次产生的提交对象,指向了不同的tree:

commits-and-parentsGit中的分支,仅仅是轻量级的、可移动的指向这些提交对象的指针。 Git默认的分支是master,因此你每一次提交,都让master分支指向新的提交对象。

当你创建一个分支testing时,只是创建了另外一个可以独立移动的指针(这是Git分支轻量高效的直接原因):

two-branches那么,Git是如何知道工作目录此时处于master还是testing分支呢?当前所处分支记录在HEAD指针中(注意Git的HEAD和其它VCS的HEAD相当的不同):head-to-master

可以看到,虽然我们创建了testing分支,但是HEAD还是指向master分支,这是因为我们没有切换到testing分支。现在我们切换到testing分支并执行一些修改:

Shell
1
2
3
4
git checkout testing
vim test.rb
git add *
git commit -m "Modify test.rb"

注意checkout会做两件事情:

  1. 移动HEAD指针到testing分支
  2. 回退工作区中的文件,让它与testing指向的提交对象的tree一致

提交后,分支——提交对象关系图变成这样:

advance-testing

这相当有趣,你现在位于testing分支,并且向前继续移动了。而master分支仍然停留在执行分支切换前的那个地方。

那么,我们重新切换回master分支、修改文件并提交,关系图会变成什么样呢?很明显,f30ab会成为两个提交对象共同的父对象。

分支的新建与合并

假设你经历如下的工作流:

  1. 你正在开发某个网站
  2. 为实现某个新的需求(iss-53),创建一个分支
  3. 在这个分支上进行开发 

此时,忽然运营部报回一个严重的缺陷,需要紧急修补。你执行以下流程:

  1. 切换到主分支
  2. 为此严重缺陷创建一个紧急修复分支(hotfix),并在其中修复它
  3. 测试通过后,切换到主分支,然后合并2中的紧急修复分支
  4. 切换回开发分支,继续工作

我们看看,如何结合Git完成上述工作流。

新建分支并修复缺陷

当前,你正在解决公司问题跟踪系统的53号问题,这是一个新增需求。此时Git状态如下:

basic-branching-1

为了实现此新需求,你新建一个分支,并切换到它:

Shell
1
2
git checkout -b iss53
# 等价于git branch iss53 && git checkout iss53

你开始为53号问题编码,并且执行了一次提交:

Shell
1
2
3
4
vim fix53.c
vim main.c
git add *
git commit -m 'fix53 v1'

此时Git的状态变为:basic-branching-3 就在这个时候,电话响了,运营部报告了一个紧急的问题,必须立刻修复。该修复应当是针对master分支的,不应该把处于新需求实现阶段的iss53牵扯进来,因此你需要切换回master分支。在切换前,你应当保证工作目录处于一个干净的状态,你可以:

  1. 最好的处理方式,提交所有iss53的修改,避免未提交的代码在切换后混入master
  2. 或者,保存进度(stashing)

处理好iss53分支后,执行下面的命令切换回master分支,并创建hotfix分支,用于处理紧急问题:

Shell
1
2
3
4
5
6
7
8
9
10
git checkout master
# Switched to branch 'master'
 
git checkout -b hotfix
# Switched to a new branch 'hotfix'
 
# 修复问题
vim main.c
git add *
git commit -m 'main.c v4 hotfix'

提交hotfix后,Git的状态变成:

basic-branching-4

hotfix合并入master

测试完毕后,你需要把hotfix合并到master,以便部署到线上。你可以使用git merge完成合并:

Shell
1
2
3
4
5
6
7
8
# 先切换到接受合并的那个分支,这里是主分支
git checkout master
# 然后,执行merge子命令,指定合并的来源
git merge hotfix
# Updating 3b50562..f9b71c2
# Fast-forward  所谓“快进”,表示master分支是被并入分支的直接上游,因此Git仅仅把master的指针简单的向前移动即可,没有需要解决的冲突
#  main.c | 2 +-  针对main.c有两行变动,新增了一行,删除了一行
#  1 file changed, 1 insertion(+), 1 deletion(-)

合并后,Git的状态变为:basic-branching-5

当发布了紧急修复后,你应该删除hotfix分支,因为master和它指向了同一个提交对象:

Shell
1
2
git branch -d hotfix
# Deleted branch hotfix (3a0874c).

注意:hotfix中的修改并没有合并到iss53中,你可以切换到iss53,然后把master合并进去。或者,完成iss53的开发后,将其合并到master中去。

iss53合并入master

你现在回到iss53,继续实现新需求:

Shell
1
2
3
4
git checkout iss53
vim fix53.c
git add *
git commit -m 'fix53 v2'

提交后,Git的状态变为:

basic-branching-6

你现在已经修复了53号问题,需要将其合并到master分支上去:

Shell
1
2
3
4
5
git checkout master
git merge iss53
# Merge made by the 'recursive' strategy.   递归的合并
# main.c |    1 +
# 1 file changed, 1 insertion(+)

可以注意到,merge子命令的输出和上一次合并不同。这是因为开发历史从更早的地方分叉(diverged)开来,导致master分支(对应的提交对象)不是iss53(对应的提交对象)的祖先,为了完成合并,Git必须进行额外的工作,而不是简单的移动指针。Git会使用两个分支对应的快照,加上它们共同的祖先,执行三方合并:

basic-merging-1

上涂中蓝色边框的,是参与合并的快照。与Fast-forward不同的是,三方合并的结果会形成一个新的快照,并自动创建一个新的提交指向该快照。这个提交的特别之处在于,它具有不止一个父提交:

basic-merging-2

需要注意的是,Git会自动决定把哪个提交作为最优的共同祖先,将其作为合并的基础。而SVN 1.5-则需要手工选择最佳的合并基础。

此时,你已经不需要iss53分支,删除它。

分支合并时的冲突处理

很多时候合并不能顺利的自动完成。如果你在两个不同的分支中,对同一个文件的同一部分进行了不同的修改,则Git无法干净的合并它们:

Shell
1
2
3
4
5
git checkout master
git merge iss53
# Auto-merging main.c
# CONFLICT (content): Merge conflict in main.c
# Automatic merge failed; fix conflicts and then commit the result.

在上面的例子中,Git提示冲突,需要人工介入处理。其时Git已经做了合并,但不是“干净”的,因而它不会执行提交。

在你处理冲突的过程中,可以随时执行 git status -s 来查看那些因为冲突而没有成功合并的文件,那些文件前面显示 UU 标记。

当你打开存在冲突的文件后,可以看到类似下面的内容:

C
1
2
3
4
5
6
7
8
9
10
int main(){
// <<<<<<< HEAD        HEAD版本(即master)对此文件的改动,位于 === 的上半部分
    int i = -1;
    i+=10;
// =======
    int i = FIX53;
    i++;
// >>>>>>> iss53       iss53分支版本对此文件的改动,位于 === 的下半部分
    return i;
}

解决完冲突后, <<< 、 === 、 >>> 都应该被删除。 确认无误后,使用 git add 命令标记冲突已解决。

你也可以利用图形化的工具完成冲突解决,例如: git mergetool  。解决完冲突后,你需要手工完成提交操作。 

基于分支的开发工作流

本节我们列出基于分支的典型工作模式。

长期分支

基于Git的“三方合并”,即使长期、反复的把一个分支合并入另外一个分支,也是可行的。你可以在项目生命周期的不同阶段,拥有多个开放的分支,你可以定期的对它们进行合并。

很多Git用户喜欢所谓渐进稳定分支的分支模型:

  1. 只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码
  2. 创建一些名为develop或者next的平行分支,用来执行后续开发、稳定性测试。这些分支不保证绝对稳定,但是一旦到达稳定状态,它们就可以被合并到master分支
  3. 开发时引入一个新特性(Topic)时,在develop上建立短期的特性分支

这种模型的好处在于,你可以维护不同层次的稳定性。下面的流水线图,形象的描述这种分支模型:

lr-branches-2

特性分支

特性分支是一种短期分支,对任何规模的项目均适用。使用Git时,每天创建、使用、合并、删除多个分支的情况很常见。上面章节的iss53、hotfix就是这样的分支。使用特性分支,你可以快速、完整的进行“上下文切换”——你的工作被分散到不同的流水线中,而每支流水线仅仅与其目标特性相关。你甚至可以基于特性分支创建新的特性分支。

远程分支

所谓远程引用(Remote reference)是指指向你的远程仓库的引用。远程引用可以包括:分支、标签。远程跟踪(remote-tracking)分支是这些远程引用中最为重要的一种。

远程跟踪分支

远程跟踪分支是远程分支的状态的引用,你在本地不能移动它。当你进行网络通信操作时,它会自动移动。远程跟踪分支就像是你上次连接到远程仓库时,远程分支所处状态的书签。

远程跟踪分支以 (remote-name)/(branch) 形式命名。如果你想查看最后一次与origin仓库通信时master分支的状态,你可以查看origin/master分支。 

下面我们用一个例子说明远程跟踪分支如何工作。假设你使用Git服务器git.ourcompany.com,并通过git clone获得仓库的镜像并自动命名远程仓库为origin,自动创建了指向origin/master远程跟踪分支(引用)。Git会在origin/master指向的地方创建本地的master分支,这是你工作的基础:

remote-branches-1

如果你在本地,基于master分支进行一些工作,这期间,其它人推送到git.ourcompany.com并更新了master分支。在重新和远程仓库同步之前,远程/本地Git的状态如下:

remote-branches-2

一旦你执行 git fetch origin ,Git就会连接到远程服务器,抓取本地没有的数据、更新本地数据库,移动origin/master指针到新的位置:

remote-branches-3

手工设置跟踪分支

从远程跟踪(remote-tracking )分支checkout一个本地分支时,会自动创建一个所谓跟踪分支(tracking branch,也叫上游分支,upstream branch)。跟踪分支是与远程分支直接关联的本地分支。如果在一个跟踪分支上执行 git pull ,Git会自动识别去哪个服务器上去抓取数据、抓取到的数据需要合并到哪个本地分支。

你可以用 @{upstream} 或者 @{u} 来代表上游分支:

Shell
1
2
3
git merge @{u}
# 等价于
git merge origin/master

当克隆一个仓库时,一般会创建跟踪origin/master的master分支。然后,如果你想跟踪其它远程分支,可以执行:

Shell
1
2
# 使用-u或者--set-upstream-to,切换当前分支的上游分支
git branch -u origin/serverfix

你可以手工签出并切换到其它任何远程分支:

Shell
1
2
3
4
5
# 签出远程分支为serverfix本地分支,并切换到serverfix分支
git checkout --track origin/serverfix
 
# 签出远程serverfix分支为本地分支sf
git checkout -b sf origin/serverfix
推送

如果你想公开分享一个分支,需要将其推送到具有写入权限的远程仓库上去。本地仓库不会与远程仓库自动同步,你必须显式的推送你想分享的分支:

Shell
1
2
3
4
5
# 向origin服务器推送serverfix分支
git push origin serverfix
# 向origin服务器推送serverfix分支,但是在服务器端命名为awesomebranch
git push origin serverfix:awesomebranch
# 如果不想每一次推送时都输入密码,可以运行git config --global credential.helper cache来设置

你的协作者在下一次从服务器抓取数据 git fetch origin 时,会在本地生成一个远程跟踪分支origin/serverfix。新抓取到的远程跟踪分支不会在本地生成可编辑的副本。 协作者可以:

  1. 通过 git merge origin/serverfix 把你共享的分支合并到他当前的分支中
  2. 或者,手工签出远程分支为本地分支: git checkout -b serverfix origin/serverfix 
拉取

通过git fetch从服务器上抓取本地没有的数据时,并不会修改工作目录的内容。你需要手工的完成合并。

另外一种操作git pull则可以进行自动的合并,大部分情况下git pull相当于git fetch && git merge。只要你设置好跟踪分支,不管它是clone自动创建还是checkout手工创建的,git pull都会查找与当前分支匹配的服务器、远程分支,然后抓取数据、合并。

删除远程分支

假设远程分支的使命已经完成——你和同事已经完成一个特性,并将其合并到远程master仓库中,可以执行下面的命令删除一个远程分支:

Shell
1
2
# 从origin服务器上删除serverfix分支
git push origin --delete serverfix
分支模型

本节介绍一个成功的分支模型:

  1. 主分支:
    1. master,其HEADER必须总是存放production-ready的代码
    2. develop,针对下一个发布版本的、最新的开发状态
  2. 支持分支,用于辅助并行开发、简化特性的跟踪、准备产品发布,这些分支最终会被删除:
    1. 特性分支:开发新的特性/主题。可以从develop衍生,必须合并回develop。命名风格任意(除了master, develop, release-*, hotfix-*),示例:
      Shell
      1
      2
      3
      4
      5
      6
      7
      8
      9
      # 创建分支
      git checkout -b myfeature develop
       
      # 合并回develop
      git checkout develop
      # --no-ff禁止fast-forward,避免丢失特性分支存在的历史,并且将特性分支的所有commit合并在一个组里面
      git merge --no-ff myfeature
      git branch -d myfeature
      git push origin develop 
    2. 发布分支:准备新产品级版本的发布。可以从develop衍生,必须合并回develop+master。命名风格release-*,示例:
      Shell
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      git checkout -b release-1.2 develop
      # 修改反应版本的源码文件
      ./bump-version.sh 1.2
      # 提交新版本
      git commit -a -m "Bumped version number to 1.2"
       
      # 合并到master
      git checkout master
      git merge --no-ff release-1.2
      # 可以考虑使用-s或-u来签名Tag
      git tag -a 1.2
       
      # 删除发布分支
      git branch -d release-1.2
    3. 热修复分支:非计划的紧急修复,可能从master的标记版本的Tag(例如1.0.0)上衍生,必须合并回develop+master。命名风格hotfix-*,示例:
      Shell
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      # 假设当前版本为1.2,出现严重BUG,需要立即修复
      git checkout -b hotfix-1.2.1 master
      # 修改反应版本的源码文件
      ./bump-version.sh 1.2.1
      git commit -a -m "Bumped version number to 1.2.1"
      # 进行BUG修复,提交
      git commit -m "Fixed severe production problem"
       
      # 合并到master
      git checkout master
      git merge --no-ff hotfix-1.2.1
      git tag -a 1.2.1
       
      # 合并到develop
      git checkout develop
      git merge --no-ff hotfix-1.2.1
       
      # 删除热修复分支
      git branch -d hotfix-1.2.1
服务器上的Git

尽管从技术上来说,可以使用个人仓库进行pull/push操作,但是这不是好主意。这样很容易弄乱别人的进度,而且你没开机时别人无法存取。多人协作开发时,最佳方法是使用一台公用的服务器作为Git仓库。

通信协议

要架设Git服务器,首先要选择一种通信协议。Git支持四种不同的协议:本地协议、HTTP协议、SSH协议、Git协议。

本地协议

这是最基本的协议,远程仓库就是磁盘中的一个目录。 通常用在团队成员对一个共享文件系统(例如一个挂载的NFS)具有访问权限的情况下。

克隆一个本地协议的版本库,可以使用如下命令:

Shell
1
git clone /opt/git/project.git

要增加一个基本本地协议的远程仓库到当前Git项目(Git仓库),可以使用:

Shell
1
git remote add local_proj /opt/git/project.git
智能HTTP协议

 Git 1.6.6引入的一种HTTP协议,是最流行的传输协议。可以像SSH协议那样智能的协商、传输数据,又可以像Git协议那样设置匿名服务。

智能HTTP协议与Git协议、SSH协议类似, 只是运行在标准的HTTP/HTTPS端口上并且使用各种HTTP验证机制。该协议的优势:

  1. 不同的访问方式只需要一个URL
  2. 基于口令的认证,避免管理SSH密钥的麻烦
  3. 与SSH协议一样,HTTP非常快捷高效
  4. 一般企业防火墙开放了HTTP/HTTPS端口
SSH协议

由于Linux服务器上一般都默认支持SSH服务,因此透过SSH使用Git非常简单:

Shell
1
2
3
git clone ssh://user@server/project.git
# 或者,更简单的scp格式:
git clone user@server:project.git

该协议的缺点是不能实现匿名访问,即使要读取数据,也需要具有访问主机文件系统的权限,不适用与开源软件。

Git协议

这个协议是最快的,它需要运行一个守护进程,该进场监听9418端口。缺点是没有授权机制。

搭建Git服务器

这里仅仅介绍如何借助Apache 2搭建基于智能HTTP协议的Git服务器。 

启用必要的模块
Shell
1
2
3
4
# 启用mod_cgi、mod_alias、mod_env、mod_auth_digest、mod_auth_basic等模块
a2enmod cgi alias env auth_digest auth_basic
# 重启Apache服务器
service apache2 restart
配置虚拟主机
/etc/apache2/sites-available/git.conf
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
27
28
29
30
31
32
33
34
35
36
37
38
39
<VirtualHost *:443>
    DocumentRoot /opt/git
    ServerName git.gmem.cc
    ErrorLog ${APACHE_LOG_DIR}/error-git.log
    CustomLog ${APACHE_LOG_DIR}/access-git.log combined
 
    SSLEngine on
    SSLCipherSuite AES128+EECDH:AES128+EDH
    SSLCertificateFile /usr/share/ca-certificates/gmem.cc.crt
    SSLCertificateKeyFile /etc/ssl/private/gmem.cc.key
    SSLCertificateChainFile /usr/share/ca-certificates/AlphaSSLCA.crt
 
    SetEnv GIT_PROJECT_ROOT /opt/git
    # 如果不设置下面的环境变量,则只有带git-daemon-export-ok文件的版本库才允许未授权客户端使用
    SetEnv GIT_HTTP_EXPORT_ALL
    SetEnv REMOTE_USER=$REDIRECT_REMOTE_USER
    ScriptAlias  /  /usr/lib/git-core/git-http-backend/
    <Directory "/usr/lib/git-core*">
       Options ExecCGI Indexes
       Order allow,deny
       Allow from all
       Require all granted
    </Directory>
 
    # 下面的配置用于实现写操作授权认证:
    RewriteEngine on
    RewriteCond %{QUERY_STRING} service=git-receive-pack [OR]
    RewriteCond %{REQUEST_URI} /git-receive-pack$
    RewriteRule ^/ - [E=AUTHREQUIRED:yes]
    <LocationMatch "^/">
        Order Deny,Allow
        Deny from env=AUTHREQUIRED
 
        AuthType Digest
        AuthName "Git Access"
        AuthUserFile /opt/git/.htpasswd
        Require valid-user
    </LocationMatch>
</VirtualHost>

添加授权用户:

Shell
1
2
3
4
apt-get install apache2-utils
# 添加一个用户
htdigest -c /opt/git/.htpasswd "Git Access" wangzhen
# 根据提示输入密码即可

启用虚拟主机:

Shell
1
2
a2ensite git
service apache2 reload
使用远程仓库

在服务器上创建Git裸仓库:

Shell
1
2
3
4
5
mkdir /opt/git && cd /opt/git
git init --bare test.git
 
# 如果报错:remote: error: insufficient permission for adding an object to repository database ./objects
chown -R www-data:www-data /opt/git

现在可以通过HTTP协议克隆此仓库了:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
git clone https://wangzhen@git.gmem.cc/test.git
cd test
touch README.md
git add .
git commit -m 'Add README.md'
 
git status
# On branch master
# Your branch is based on 'origin/master', but the upstream is gone.
#  (use "git branch --unset-upstream" to fixup)  这个提示是错误的
 
# 这里提示上游分支不存在。这是因为裸仓库中还没有创建任何分支。我们可以直接推送,在远程仓库上创建分支
git push origin master
# To https://wangzhen@git.gmem.cc/test.git
# * [new branch] master -> master
 
git status
# Your branch is up-to-date with 'origin/master'. 
使用GitHub

GitHub是一个提供Git托管服务的网站,你只需要注册一个用户,就可以将其作为免费的远程仓库使用。需要注意的是,你在GitHub上项目将被完全公开,除非你参与它的付费计划。

新建远程仓库

注册完毕后,进入你的首页,例如https://github.com/gmemcc。点击界面右上角Selection_001的小箭头,选择New Repository,即可新建一个远程仓库。

你需要输入一个仓库名称、可选的描述信息。如果勾选“Initialize this repository with a README”,则master分支被自动创建,并提交一个README文件。如果你想从其它地方导入master分支,不要勾选它。

你可以通过SSH或者HTTPS协议访问新建的仓库。假设新仓库名为my-autosizer,则:

  1. SSH协议的URL:git@github.com:gmemcc/my-autosizer.git
  2. HTTP协议的URL:https://github.com/gmemcc/my-autosizer.git

GitHub建议:任何一个仓库都应该包含  README.md 、 LICENSE.md 、 .gitignore 这三个文件。

本地仓库操作

如果你本地的my-autosizer项目已经在开发中,最好不要勾选“Initialize this repository with a README”。并执行下面的操作:

Shell
1
2
3
4
5
6
7
# 初始化本地仓库
git init
git add README.md
git commit -m "Initial commit"
# 关联到远程仓库
git remote add origin https://github.com/gmemcc/my-autosizer.git
git push -u origin master
通过IntelliJ平台IDE使用GitHub

参见:IntelliJ平台知识集锦

Github
PR流程
  1. Fork原始项目
  2. 最好创建主题分支,在此分支上进行开发
  3. 在主题分支下工作,想要导入上游库的更新时,使用 git rebase。这会将当前分支的base推进到新的起点,而不引入多余的commits
  4. 在主题分支下工作,想要合并其它分支的更新时,可以使用 git merge。注意,如果merge的分支来自远程仓库,每次merge会导致commit操作
  5. 发送Pull Request
  6. 在此PR被关闭之前,可以继续向主题分支下Push代码,所有commits都会自动追加到PR
  7. PR被关闭之后,可以删除本地分支、远程分支
常见问题
IntelliJ相关
Git Log小箭头

向上箭头:说明当前分支有孩子分支,在其他地方(只是图上不方便画出来)
向下箭头:说明当前分支是从之前某个分支延伸而来

零散问题
如果撤销git reset

使用命令: git reset 'HEAD@{1}'

← HTTP协议学习笔记
Next Post →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • 编程语言知识集锦
  • YAML快速参考
  • 基于Kurento搭建WebRTC服务器
  • 背诵营笔记
  • Jenkins知识集锦

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2